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
This commit is contained in:
Karl Pannek 2025-07-16 23:40:40 +02:00
parent d584065757
commit 49f4591d3a
5 changed files with 205 additions and 162 deletions

View file

@ -140,7 +140,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
visible: false, visible: false,
pivot: { pivot: {
x: 0, x: 0,
y: 40 * 4 y: 21 * 4
}, },
xScale: 0.25, xScale: 0.25,
yScale: 0.25, yScale: 0.25,
@ -187,7 +187,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
pivot: { pivot: {
//x: 35/2 * 4, //x: 35/2 * 4,
x: 0, x: 0,
y: 40 * 4 y: 21 * 4 // Reduced from 40 to 20 to match body pivot
}, },
width: 35, width: 35,
height: 40, height: 40,
@ -288,7 +288,7 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
this.headMesh, this.headMesh,
{ {
x: this.body.getPosition().x * Settings.RATIO, 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
} }
) )

View file

@ -17,7 +17,7 @@ function (Settings) {
joints: false, joints: false,
aabb: false, aabb: false,
pairs: false, pairs: false,
centerOfMass: false centerOfMass: true
}; };
} }
@ -41,7 +41,7 @@ function (Settings) {
this.ctx.translate(transformedX, transformedY); this.ctx.translate(transformedX, transformedY);
this.ctx.scale(this.scale * this.cameraZoom, this.scale * this.cameraZoom); 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 // Iterate through all bodies
for (var body = world.getBodyList(); body; body = body.getNext()) { for (var body = world.getBodyList(); body; body = body.getNext()) {
@ -51,11 +51,26 @@ function (Settings) {
for (var fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) { for (var fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {
var shape = fixture.getShape(); var shape = fixture.getShape();
// Skip sensor fixtures to match old Box2D behavior // Check if this is a sensor
if (fixture.isSensor()) { var isSensor = fixture.isSensor();
// Skip sensor fixtures if DEBUG_DRAW_SENSORS is false
if (isSensor && !Settings.DEBUG_DRAW_SENSORS) {
continue; continue;
} }
// 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 {
// Regular fixtures: colored by body type
if (body.isDynamic()) { if (body.isDynamic()) {
this.ctx.strokeStyle = '#ff0000'; // Red for dynamic bodies this.ctx.strokeStyle = '#ff0000'; // Red for dynamic bodies
this.ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'; this.ctx.fillStyle = 'rgba(255, 0, 0, 0.1)';
@ -66,45 +81,64 @@ function (Settings) {
this.ctx.strokeStyle = '#0000ff'; // Blue for kinematic bodies this.ctx.strokeStyle = '#0000ff'; // Blue for kinematic bodies
this.ctx.fillStyle = 'rgba(0, 0, 255, 0.1)'; 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(); this.ctx.restore();
}; };
PlanckDebugDraw.prototype.drawShape = function(shape, transform) { PlanckDebugDraw.prototype.drawShape = function(shape, transform, isSensor) {
var type = shape.getType(); var type = shape.getType();
if (type === 'circle') { if (type === 'circle') {
this.drawCircle(shape, transform); this.drawCircle(shape, transform, isSensor);
} else if (type === 'polygon') { } else if (type === 'polygon') {
this.drawPolygon(shape, transform); this.drawPolygon(shape, transform, isSensor);
} else if (type === 'edge') { } else if (type === 'edge') {
this.drawEdge(shape, transform); this.drawEdge(shape, transform, isSensor);
} else if (type === 'chain') { } else if (type === 'chain') {
this.drawChain(shape, transform); this.drawChain(shape, transform, isSensor);
} }
}; };
PlanckDebugDraw.prototype.drawCircle = function(shape, transform) { PlanckDebugDraw.prototype.drawCircle = function(shape, transform, isSensor) {
var center = transform.p;
var radius = shape.getRadius(); var radius = shape.getRadius();
// 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);
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); this.ctx.arc(worldCenter.x, worldCenter.y, radius, 0, 2 * Math.PI);
this.ctx.fill(); this.ctx.fill();
// Only draw stroke for non-sensors
if (!isSensor) {
this.ctx.stroke(); this.ctx.stroke();
// Draw radius line to show rotation // Draw radius line to show rotation (only for non-sensors)
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.moveTo(center.x, center.y); this.ctx.moveTo(worldCenter.x, worldCenter.y);
this.ctx.lineTo(center.x + radius, center.y); this.ctx.lineTo(worldCenter.x + radius, worldCenter.y);
this.ctx.stroke(); this.ctx.stroke();
}
}; };
PlanckDebugDraw.prototype.drawPolygon = function(shape, transform) { PlanckDebugDraw.prototype.drawPolygon = function(shape, transform, isSensor) {
var vertices = shape.m_vertices; var vertices = shape.m_vertices;
if (!vertices || vertices.length < 3) return; if (!vertices || vertices.length < 3) return;
@ -122,20 +156,28 @@ function (Settings) {
this.ctx.closePath(); this.ctx.closePath();
this.ctx.fill(); this.ctx.fill();
// Only draw stroke for non-sensors
if (!isSensor) {
this.ctx.stroke(); 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 v1 = this.transformVertex(shape.m_vertex1, transform);
var v2 = this.transformVertex(shape.m_vertex2, transform); var v2 = this.transformVertex(shape.m_vertex2, transform);
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.moveTo(v1.x, v1.y); this.ctx.moveTo(v1.x, v1.y);
this.ctx.lineTo(v2.x, v2.y); this.ctx.lineTo(v2.x, v2.y);
// Only draw stroke for non-sensors
if (!isSensor) {
this.ctx.stroke(); this.ctx.stroke();
}
}; };
PlanckDebugDraw.prototype.drawChain = function(shape, transform) { PlanckDebugDraw.prototype.drawChain = function(shape, transform, isSensor) {
var vertices = shape.m_vertices; var vertices = shape.m_vertices;
if (!vertices || vertices.length < 2) return; if (!vertices || vertices.length < 2) return;
@ -149,7 +191,10 @@ function (Settings) {
this.ctx.lineTo(v.x, v.y); this.ctx.lineTo(v.x, v.y);
} }
// Only draw stroke for non-sensors
if (!isSensor) {
this.ctx.stroke(); this.ctx.stroke();
}
}; };
PlanckDebugDraw.prototype.transformVertex = function(vertex, transform) { 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) { PlanckDebugDraw.prototype.setFlags = function(flags) {
this.flags = flags; this.flags = flags;
}; };

View file

@ -35,11 +35,12 @@ function () {
ORIGINAL_TILE_SIZE: 25, ORIGINAL_TILE_SIZE: 25,
TILE_SIZE: 20, TILE_SIZE: 20,
CAMERA_IS_ORTHOGRAPHIC: true, 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", 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, SHOW_LAYER_INFO: false,
ENABLE_POINTER_LOCK_FILTER: false, ENABLE_POINTER_LOCK_FILTER: false,
DEBUG_DRAW_SENSORS: true,
// GAME PLAY // GAME PLAY
WALK_SPEED: 4, WALK_SPEED: 4,

View file

@ -61,53 +61,45 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse
}; };
Doll.prototype.createFixtures = function () { Doll.prototype.createFixtures = function () {
Assert.number(this.width, this.height); Assert.number(this.width, this.height, this.reachDistance, this.areaSize);
Assert.number(this.reachDistance);
Assert.number(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 }; // Helper to create and attach fixture
fixtureDef.density = Settings.PLAYER_DENSITY; const addFixture = (def) => this.body.createFixture(def);
fixtureDef.friction = 0;
fixtureDef.restitution = Settings.PLAYER_RESTITUTION;
var radius = this.width / 2 / Settings.RATIO; // Head (positioned at top of body box)
var headShape = planck.Circle( addFixture({
radius, shape: planck.Circle(planck.Vec2(0, -(h - w) / 2 / R), w / 2 / R),
planck.Vec2(0, -(this.height - (this.width / 2)) / Settings.RATIO) density: Settings.PLAYER_DENSITY,
); friction: 0,
fixtureDef.shape = headShape; restitution: Settings.PLAYER_RESTITUTION,
fixtureDef.isSensor = false; isSensor: false,
fixtureDef.userData = { userData: { onCollisionChange: this.onImpact.bind(this) }
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( // Legs (positioned at bottom of body box)
this.width / 2 / Settings.RATIO, this.legs = addFixture({
(this.height - this.width) / 2 / Settings.RATIO, shape: planck.Circle(planck.Vec2(0, (h - w) / 2 / R), w / 2 / R),
planck.Vec2(0, -this.height / 2 / Settings.RATIO), density: Settings.PLAYER_DENSITY,
0 friction: Settings.PLAYER_FRICTION,
); restitution: Settings.PLAYER_RESTITUTION,
fixtureDef.shape = bodyShape; isSensor: false
fixtureDef.isSensor = false; });
this.body.createFixture(fixtureDef);
var legsShape = planck.Circle( // Foot sensor (positioned below legs)
this.width / 2 / Settings.RATIO, this.footSensor = addFixture({
planck.Vec2(0, -this.width / 2 / Settings.RATIO) shape: planck.Circle(planck.Vec2(0, (h - w) / 2 / R + 2 / R), (w - 1) / 2 / R),
);
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,
density: 0, density: 0,
friction: 0, friction: 0,
restitution: 0, restitution: 0,
@ -116,84 +108,47 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse
onCollisionChange: this.onFootSensorDetection.bind(this), onCollisionChange: this.onFootSensorDetection.bind(this),
isFootSensor: true isFootSensor: true
} }
}; });
var feetShape = planck.Circle( // // Grab sensors (left/right)
(this.width - 1) / 2 / Settings.RATIO, // the -1 one prevents collisions with walls // ["left", "right"].forEach(side => {
planck.Vec2(0, -20 / Settings.RATIO) // 20 pixels down from center (dramatic test) // const sign = side === "left" ? -1 : 1;
); // addFixture({
footSensorDef.shape = feetShape; // 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); // // Area sensor
// addFixture({
var grabSensorLeftShape = planck.Box( // shape: planck.Box((w + a) / 2 / R, (h + a) / 2 / R, planck.Vec2(0, h / 2 / R)),
this.reachDistance / 2 / Settings.RATIO, // density: 0,
((this.height / 2) + this.reachDistance / 4) / Settings.RATIO, // friction: 0,
planck.Vec2( // restitution: 0,
-this.reachDistance / 2 / Settings.RATIO, // isSensor: true,
-this.height / 2 / Settings.RATIO // userData: {
) // onCollisionChange: function(isColliding, fixture) {
); // var userData = fixture.getBody().getUserData();
fixtureDef.shape = grabSensorLeftShape; // if (userData instanceof Doll) {
fixtureDef.isSensor = true; // var i = self.nearbyDolls.indexOf(userData);
fixtureDef.userData = { // if (isColliding && i === -1) self.nearbyDolls.push(userData);
onCollisionChange: function(isColliding, fixture) { // else if (!isColliding && i !== -1) self.nearbyDolls.splice(i, 1);
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);
}; };
Doll.prototype.setActionState = function(state) { Doll.prototype.setActionState = function(state) {
@ -219,7 +174,7 @@ function (Parent, Exception, planck, Settings, CollisionDetector, Item, nc, Asse
var pos = this.body.getPosition(); var pos = this.body.getPosition();
return { return {
x: pos.x, 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) { Doll.prototype.setStanding = function (isStanding) {
if (this.standing == isStanding) { if (this.standing == isStanding) return;
console.log('setStanding called but no change needed, already:', isStanding);
return;
}
console.log('*** STANDING STATE CHANGE: ', this.standing, '->', isStanding, '***');
this.standing = isStanding; this.standing = isStanding;
if(isStanding) this.setActionState("stand"); if(isStanding) this.setActionState("stand");
}; };

View file

@ -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;
});