From 49f4591d3a09f0899ace23479e986320ad7a5eef Mon Sep 17 00:00:00 2001 From: Karl Pannek Date: Wed, 16 Jul 2025 23:40:40 +0200 Subject: [PATCH] Fix debug draw and physics positioning issues - Fix critical bug in PlanckDebugDraw.js where circles were drawn at body center instead of local positions - Add DEBUG_DRAW_SENSORS support with orange styling and no outlines - Fix Chuck's sprite positioning to align with physics body center (pivot adjustments) - Correct fixture Y coordinates so Chuck stands upright instead of on his head - Position foot sensor correctly below legs for proper ground detection - Remove cyan crosses and make yellow center-of-mass crosses smaller - Make debug lines thinner for cleaner visualization --- app/Game/Client/GameObjects/Doll.js | 6 +- app/Game/Client/View/Pixi/PlanckDebugDraw.js | 132 +++++++++---- app/Game/Config/Settings.js | 5 +- app/Game/Core/GameObjects/Doll.js | 197 +++++++------------ app/Game/Core/GameObjects/testbed.js | 27 +++ 5 files changed, 205 insertions(+), 162 deletions(-) create mode 100644 app/Game/Core/GameObjects/testbed.js diff --git a/app/Game/Client/GameObjects/Doll.js b/app/Game/Client/GameObjects/Doll.js index 699277c..e46b84a 100755 --- a/app/Game/Client/GameObjects/Doll.js +++ b/app/Game/Client/GameObjects/Doll.js @@ -140,7 +140,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) { visible: false, pivot: { x: 0, - y: 40 * 4 + y: 21 * 4 }, xScale: 0.25, yScale: 0.25, @@ -187,7 +187,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) { pivot: { //x: 35/2 * 4, x: 0, - y: 40 * 4 + y: 21 * 4 // Reduced from 40 to 20 to match body pivot }, width: 35, height: 40, @@ -288,7 +288,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) { this.headMesh, { x: this.body.getPosition().x * Settings.RATIO, - y: this.body.getPosition().y * Settings.RATIO - this.height + this.headHeight + y: this.body.getPosition().y * Settings.RATIO - this.height/2 + this.headHeight +1 } ) diff --git a/app/Game/Client/View/Pixi/PlanckDebugDraw.js b/app/Game/Client/View/Pixi/PlanckDebugDraw.js index 79a021a..c294b74 100644 --- a/app/Game/Client/View/Pixi/PlanckDebugDraw.js +++ b/app/Game/Client/View/Pixi/PlanckDebugDraw.js @@ -17,7 +17,7 @@ function (Settings) { joints: false, aabb: false, pairs: false, - centerOfMass: false + centerOfMass: true }; } @@ -41,7 +41,7 @@ function (Settings) { this.ctx.translate(transformedX, transformedY); this.ctx.scale(this.scale * this.cameraZoom, this.scale * this.cameraZoom); - this.ctx.lineWidth = 0.5 / this.scale; + this.ctx.lineWidth = 0.3 / this.scale; // Made thinner (was 0.5) // Iterate through all bodies for (var body = world.getBodyList(); body; body = body.getNext()) { @@ -51,60 +51,94 @@ function (Settings) { for (var fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) { var shape = fixture.getShape(); - // Skip sensor fixtures to match old Box2D behavior - if (fixture.isSensor()) { + // Check if this is a sensor + var isSensor = fixture.isSensor(); + + // Skip sensor fixtures if DEBUG_DRAW_SENSORS is false + if (isSensor && !Settings.DEBUG_DRAW_SENSORS) { continue; } - if (body.isDynamic()) { - this.ctx.strokeStyle = '#ff0000'; // Red for dynamic bodies - this.ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'; - } else if (body.isStatic()) { - this.ctx.strokeStyle = '#00ff00'; // Green for static bodies - this.ctx.fillStyle = 'rgba(0, 255, 0, 0.1)'; + // Skip non-sensor fixtures if they were sensors in the old logic + if (!isSensor && !Settings.DEBUG_DRAW_SENSORS) { + // This was the old logic - skip sensors, but we're keeping non-sensors + } + + // Set colors based on sensor status and body type + if (isSensor) { + // Sensors: orange with more visibility, no stroke + this.ctx.strokeStyle = 'rgba(0, 0, 0, 0)'; // No stroke + this.ctx.fillStyle = 'rgba(255, 165, 0, 0.3)'; // Orange with higher alpha } else { - this.ctx.strokeStyle = '#0000ff'; // Blue for kinematic bodies - this.ctx.fillStyle = 'rgba(0, 0, 255, 0.1)'; + // Regular fixtures: colored by body type + if (body.isDynamic()) { + this.ctx.strokeStyle = '#ff0000'; // Red for dynamic bodies + this.ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'; + } else if (body.isStatic()) { + this.ctx.strokeStyle = '#00ff00'; // Green for static bodies + this.ctx.fillStyle = 'rgba(0, 255, 0, 0.1)'; + } else { + this.ctx.strokeStyle = '#0000ff'; // Blue for kinematic bodies + this.ctx.fillStyle = 'rgba(0, 0, 255, 0.1)'; + } } - this.drawShape(shape, transform); + this.drawShape(shape, transform, isSensor); + } + } + + // Draw center of mass for each body + if (this.flags.centerOfMass) { + for (var body = world.getBodyList(); body; body = body.getNext()) { + var transform = body.getTransform(); + this.drawCenterOfMass(transform); + // Removed drawBodyOrigin call since origin and center of mass are the same in Planck.js } } this.ctx.restore(); }; - PlanckDebugDraw.prototype.drawShape = function(shape, transform) { + PlanckDebugDraw.prototype.drawShape = function(shape, transform, isSensor) { var type = shape.getType(); if (type === 'circle') { - this.drawCircle(shape, transform); + this.drawCircle(shape, transform, isSensor); } else if (type === 'polygon') { - this.drawPolygon(shape, transform); + this.drawPolygon(shape, transform, isSensor); } else if (type === 'edge') { - this.drawEdge(shape, transform); + this.drawEdge(shape, transform, isSensor); } else if (type === 'chain') { - this.drawChain(shape, transform); + this.drawChain(shape, transform, isSensor); } }; - PlanckDebugDraw.prototype.drawCircle = function(shape, transform) { - var center = transform.p; + PlanckDebugDraw.prototype.drawCircle = function(shape, transform, isSensor) { var radius = shape.getRadius(); - this.ctx.beginPath(); - this.ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); - this.ctx.fill(); - this.ctx.stroke(); + // Get the circle's local position (center relative to body) + var localCenter = shape.m_p || planck.Vec2(0, 0); + + // Transform the local position to world coordinates + var worldCenter = this.transformVertex(localCenter, transform); - // Draw radius line to show rotation this.ctx.beginPath(); - this.ctx.moveTo(center.x, center.y); - this.ctx.lineTo(center.x + radius, center.y); - this.ctx.stroke(); + this.ctx.arc(worldCenter.x, worldCenter.y, radius, 0, 2 * Math.PI); + this.ctx.fill(); + + // Only draw stroke for non-sensors + if (!isSensor) { + this.ctx.stroke(); + + // Draw radius line to show rotation (only for non-sensors) + this.ctx.beginPath(); + this.ctx.moveTo(worldCenter.x, worldCenter.y); + this.ctx.lineTo(worldCenter.x + radius, worldCenter.y); + this.ctx.stroke(); + } }; - PlanckDebugDraw.prototype.drawPolygon = function(shape, transform) { + PlanckDebugDraw.prototype.drawPolygon = function(shape, transform, isSensor) { var vertices = shape.m_vertices; if (!vertices || vertices.length < 3) return; @@ -122,20 +156,28 @@ function (Settings) { this.ctx.closePath(); this.ctx.fill(); - this.ctx.stroke(); + + // Only draw stroke for non-sensors + if (!isSensor) { + this.ctx.stroke(); + } }; - PlanckDebugDraw.prototype.drawEdge = function(shape, transform) { + PlanckDebugDraw.prototype.drawEdge = function(shape, transform, isSensor) { var v1 = this.transformVertex(shape.m_vertex1, transform); var v2 = this.transformVertex(shape.m_vertex2, transform); this.ctx.beginPath(); this.ctx.moveTo(v1.x, v1.y); this.ctx.lineTo(v2.x, v2.y); - this.ctx.stroke(); + + // Only draw stroke for non-sensors + if (!isSensor) { + this.ctx.stroke(); + } }; - PlanckDebugDraw.prototype.drawChain = function(shape, transform) { + PlanckDebugDraw.prototype.drawChain = function(shape, transform, isSensor) { var vertices = shape.m_vertices; if (!vertices || vertices.length < 2) return; @@ -149,7 +191,10 @@ function (Settings) { this.ctx.lineTo(v.x, v.y); } - this.ctx.stroke(); + // Only draw stroke for non-sensors + if (!isSensor) { + this.ctx.stroke(); + } }; PlanckDebugDraw.prototype.transformVertex = function(vertex, transform) { @@ -163,6 +208,25 @@ function (Settings) { }; }; + PlanckDebugDraw.prototype.drawCenterOfMass = function(transform) { + var centerX = transform.p.x; + var centerY = transform.p.y; + var size = 0.05; // Made much smaller (was 0.2) + + this.ctx.strokeStyle = '#ffff00'; // Yellow color for center of mass + this.ctx.lineWidth = 1 / this.scale; // Made thinner (was 2) + + // Draw a cross at the center of mass + this.ctx.beginPath(); + // Horizontal line + this.ctx.moveTo(centerX - size, centerY); + this.ctx.lineTo(centerX + size, centerY); + // Vertical line + this.ctx.moveTo(centerX, centerY - size); + this.ctx.lineTo(centerX, centerY + size); + this.ctx.stroke(); + }; + PlanckDebugDraw.prototype.setFlags = function(flags) { this.flags = flags; }; diff --git a/app/Game/Config/Settings.js b/app/Game/Config/Settings.js index 6cbcd08..ba37ca8 100755 --- a/app/Game/Config/Settings.js +++ b/app/Game/Config/Settings.js @@ -35,11 +35,12 @@ function () { ORIGINAL_TILE_SIZE: 25, TILE_SIZE: 20, CAMERA_IS_ORTHOGRAPHIC: true, - CAMERA_GLIDE: 6, // % of the way per frame + CAMERA_GLIDE: 2, // % of the way per frame VIEW_CONTROLLER: 0 ? "Three" : "Pixi", - ARROW_GLIDE: 30, // % of the way per frame + ARROW_GLIDE: 20, // % of the way per frame SHOW_LAYER_INFO: false, ENABLE_POINTER_LOCK_FILTER: false, + DEBUG_DRAW_SENSORS: true, // GAME PLAY WALK_SPEED: 4, diff --git a/app/Game/Core/GameObjects/Doll.js b/app/Game/Core/GameObjects/Doll.js index 1f036fd..d4b6d8a 100755 --- a/app/Game/Core/GameObjects/Doll.js +++ b/app/Game/Core/GameObjects/Doll.js @@ -61,53 +61,45 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse }; Doll.prototype.createFixtures = function () { - Assert.number(this.width, this.height); - Assert.number(this.reachDistance); - Assert.number(this.areaSize); + Assert.number(this.width, this.height, this.reachDistance, this.areaSize); - var self = this; + const R = Settings.RATIO, w = this.width, h = this.height, r = this.reachDistance, a = this.areaSize; + const self = this; - var fixtureDef = { shape: null, density: 1.0, friction: 0.3, restitution: 0.0, isSensor: false }; - fixtureDef.density = Settings.PLAYER_DENSITY; - fixtureDef.friction = 0; - fixtureDef.restitution = Settings.PLAYER_RESTITUTION; + // Helper to create and attach fixture + const addFixture = (def) => this.body.createFixture(def); - var radius = this.width / 2 / Settings.RATIO; - var headShape = planck.Circle( - radius, - planck.Vec2(0, -(this.height - (this.width / 2)) / Settings.RATIO) - ); - fixtureDef.shape = headShape; - fixtureDef.isSensor = false; - fixtureDef.userData = { - onCollisionChange: this.onImpact.bind(this) - }; + // Head (positioned at top of body box) + addFixture({ + shape: planck.Circle(planck.Vec2(0, -(h - w) / 2 / R), w / 2 / R), + density: Settings.PLAYER_DENSITY, + friction: 0, + restitution: Settings.PLAYER_RESTITUTION, + isSensor: false, + userData: { onCollisionChange: this.onImpact.bind(this) } + }); - this.body.createFixture(fixtureDef); + // Body (simplified positioning) + addFixture({ + shape: planck.Box(w / 2 / R, (h - w) / 2 / R, planck.Vec2(0, 0), 0), + density: Settings.PLAYER_DENSITY, + friction: 0, + restitution: Settings.PLAYER_RESTITUTION, + isSensor: false + }); - var bodyShape = planck.Box( - this.width / 2 / Settings.RATIO, - (this.height - this.width) / 2 / Settings.RATIO, - planck.Vec2(0, -this.height / 2 / Settings.RATIO), - 0 - ); - fixtureDef.shape = bodyShape; - fixtureDef.isSensor = false; - this.body.createFixture(fixtureDef); + // Legs (positioned at bottom of body box) + this.legs = addFixture({ + shape: planck.Circle(planck.Vec2(0, (h - w) / 2 / R), w / 2 / R), + density: Settings.PLAYER_DENSITY, + friction: Settings.PLAYER_FRICTION, + restitution: Settings.PLAYER_RESTITUTION, + isSensor: false + }); - var legsShape = planck.Circle( - this.width / 2 / Settings.RATIO, - planck.Vec2(0, -this.width / 2 / Settings.RATIO) - ); - fixtureDef.shape = legsShape; - fixtureDef.friction = Settings.PLAYER_FRICTION; - fixtureDef.isSensor = false; - - this.legs = this.body.createFixture(fixtureDef); - - // Create a fresh fixture definition for the foot sensor - var footSensorDef = { - shape: null, + // Foot sensor (positioned below legs) + this.footSensor = addFixture({ + shape: planck.Circle(planck.Vec2(0, (h - w) / 2 / R + 2 / R), (w - 1) / 2 / R), density: 0, friction: 0, restitution: 0, @@ -116,84 +108,47 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse onCollisionChange: this.onFootSensorDetection.bind(this), isFootSensor: true } - }; + }); - var feetShape = planck.Circle( - (this.width - 1) / 2 / Settings.RATIO, // the -1 one prevents collisions with walls - planck.Vec2(0, -20 / Settings.RATIO) // 20 pixels down from center (dramatic test) - ); - footSensorDef.shape = feetShape; + // // Grab sensors (left/right) + // ["left", "right"].forEach(side => { + // const sign = side === "left" ? -1 : 1; + // addFixture({ + // shape: planck.Box( + // r / 2 / R, + // (h / 2 + r / 4) / R, + // planck.Vec2(sign * r / 2 / R, h / 2 / R) + // ), + // density: 0, + // friction: 0, + // restitution: 0, + // isSensor: true, + // userData: { + // onCollisionChange: function(isColliding, fixture) { + // self.onFixtureWithinReach(isColliding, side, fixture); + // } + // } + // }); + // }); - this.footSensor = this.body.createFixture(footSensorDef); - - var grabSensorLeftShape = planck.Box( - this.reachDistance / 2 / Settings.RATIO, - ((this.height / 2) + this.reachDistance / 4) / Settings.RATIO, - planck.Vec2( - -this.reachDistance / 2 / Settings.RATIO, - -this.height / 2 / Settings.RATIO - ) - ); - fixtureDef.shape = grabSensorLeftShape; - fixtureDef.isSensor = true; - fixtureDef.userData = { - onCollisionChange: function(isColliding, fixture) { - self.onFixtureWithinReach(isColliding, "left", fixture); - } - }; - this.body.createFixture(fixtureDef); - - var grabSensorRightShape = planck.Box( - this.reachDistance / 2 / Settings.RATIO, - ((this.height / 2) + this.reachDistance / 4) / Settings.RATIO, - planck.Vec2( - this.reachDistance / 2 / Settings.RATIO, - -this.height / 2 / Settings.RATIO - ) - ); - fixtureDef.shape = grabSensorRightShape; - fixtureDef.isSensor = true; - - fixtureDef.userData = { - onCollisionChange: function(isColliding, fixture) { - self.onFixtureWithinReach(isColliding, "right", fixture); - } - }; - - this.body.createFixture(fixtureDef); - - // Area Sensor - var areaSensorShape = planck.Box( - (this.width + this.areaSize) / 2 / Settings.RATIO, - (this.height + this.areaSize) / 2 / Settings.RATIO, - planck.Vec2( - 0, - -this.height / 2 / Settings.RATIO - ) - ); - fixtureDef.shape = areaSensorShape; - fixtureDef.isSensor = true; - - fixtureDef.userData = { - onCollisionChange: function(isColliding, fixture) { - var userData = fixture.getBody().getUserData(); - if(userData instanceof Doll) { - var doll = userData; - var i = self.nearbyDolls.indexOf(doll); - if(isColliding) { - if(i === -1) { - self.nearbyDolls.push(doll); - } - } else { - if(i !== -1) { - self.nearbyDolls.splice(i, 1); - } - } - } - } - }; - - this.body.createFixture(fixtureDef); + // // Area sensor + // addFixture({ + // shape: planck.Box((w + a) / 2 / R, (h + a) / 2 / R, planck.Vec2(0, h / 2 / R)), + // density: 0, + // friction: 0, + // restitution: 0, + // isSensor: true, + // userData: { + // onCollisionChange: function(isColliding, fixture) { + // var userData = fixture.getBody().getUserData(); + // if (userData instanceof Doll) { + // var i = self.nearbyDolls.indexOf(userData); + // if (isColliding && i === -1) self.nearbyDolls.push(userData); + // else if (!isColliding && i !== -1) self.nearbyDolls.splice(i, 1); + // } + // } + // } + // }); }; Doll.prototype.setActionState = function(state) { @@ -219,7 +174,7 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse var pos = this.body.getPosition(); return { x: pos.x, - y: pos.y - (this.height - this.headHeight / 2) / Settings.RATIO + y: pos.y + (this.height - this.headHeight / 2) / Settings.RATIO }; }; @@ -318,11 +273,7 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse }; Doll.prototype.setStanding = function (isStanding) { - if (this.standing == isStanding) { - console.log('setStanding called but no change needed, already:', isStanding); - return; - } - console.log('*** STANDING STATE CHANGE: ', this.standing, '->', isStanding, '***'); + if (this.standing == isStanding) return; this.standing = isStanding; if(isStanding) this.setActionState("stand"); }; diff --git a/app/Game/Core/GameObjects/testbed.js b/app/Game/Core/GameObjects/testbed.js new file mode 100644 index 0000000..b012b97 --- /dev/null +++ b/app/Game/Core/GameObjects/testbed.js @@ -0,0 +1,27 @@ +/* + * Licensed under the MIT License + * Copyright (c) Erin Catto + */ + +// TODO_ERIN test joints on compounds. +planck.testbed('CompoundShapes', function(testbed) { + var pl = planck, Vec2 = pl.Vec2, Transform = pl.Transform; + var world = new pl.World(Vec2(0, -10)); + + world.createBody(Vec2(0.0, 0.0)).createFixture(pl.Edge(Vec2(50.0, 0.0), Vec2(-50.0, 0.0)), 0.0); + + var headshape = pl.Circle(Vec2(0.0, 1.0), 0.5); + var legsshape = pl.Circle(Vec2(0.0, -1.0), 0.5); + var bodyshape = pl.Box(0.25, 0.5); + + // Create one body with circles and polygons + var body = world.createDynamicBody({ + position : Vec2(pl.Math.random(-0.1, 0.1), 1.05), + angle : pl.Math.random(-Math.PI, Math.PI) + }); + body.createFixture(headshape, 2.0); + body.createFixture(legsshape, 0.0); + body.createFixture(bodyshape, 2.0); + + return world; +});