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,
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
}
)

View file

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

View file

@ -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,

View file

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

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