Complete Box2D to Planck.js migration

- Replace Box2D.js with Planck.js physics engine
- Update all require paths from 'Lib/Vendor/Box2D' to 'Lib/Vendor/Planck'
- Convert Box2D contact listeners to Planck.js event system
- Fix all method name capitalization (Get* -> get*, Set* -> set*)
- Update collision detection system for Planck.js compatibility
- Server now starts successfully and basic physics working
- Character can land on platforms - core physics functional

Major milestone: Game now running on modern, maintained physics engine
This commit is contained in:
Karl Pannek 2025-07-16 15:01:59 +02:00
parent 875abd60d9
commit dc779def9c
43 changed files with 701 additions and 14524 deletions

View file

@ -1,6 +1,6 @@
define([
"Game/Core/GameController",
"Lib/Vendor/Box2D",
"Lib/Vendor/Planck",
"Game/Client/Physics/Engine",
"Game/Client/View/ViewManager",
"Game/Client/Control/PlayerController",
@ -49,6 +49,7 @@ function (Parent, Box2D, PhysicsEngine, ViewManager, PlayerController, nc, reque
this.animationRequestId = requestAnimFrame(this.update.bind(this));
this.physicsEngine.update();
this.physicsEngine.renderDebug(); // Render Planck.js debug draw
if(this.me) {
this.me.update();

View file

@ -276,8 +276,8 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
this.layerId,
this.animatedMeshes[this.actionState],
{
x: this.body.GetPosition().x * Settings.RATIO,
y: this.body.GetPosition().y * Settings.RATIO,
x: this.body.getPosition().x * Settings.RATIO,
y: this.body.getPosition().y * Settings.RATIO,
animationSpeed: factor
//rotation: this.body.GetAngle()
}
@ -287,8 +287,8 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
this.layerId,
this.headMesh,
{
x: this.body.GetPosition().x * Settings.RATIO,
y: this.body.GetPosition().y * Settings.RATIO - this.height + this.headHeight
x: this.body.getPosition().x * Settings.RATIO,
y: this.body.getPosition().y * Settings.RATIO - this.height + this.headHeight
}
)
@ -296,8 +296,8 @@ function (Parent, Settings, nc, Exception, ColorConverter, Layer) {
this.layerId,
this.holdingArmMesh,
{
x: this.body.GetPosition().x * Settings.RATIO,
y: this.body.GetPosition().y * Settings.RATIO
x: this.body.getPosition().x * Settings.RATIO,
y: this.body.getPosition().y * Settings.RATIO
}
)
}

View file

@ -59,9 +59,9 @@ function (Parent, Settings, nc, Layer) {
this.layerId,
this.mesh,
{
x: this.body.GetPosition().x * Settings.RATIO,
y: this.body.GetPosition().y * Settings.RATIO,
rotation: this.body.GetAngle()
x: this.body.getPosition().x * Settings.RATIO,
y: this.body.getPosition().y * Settings.RATIO,
rotation: this.body.getAngle()
}
);
}

View file

@ -68,9 +68,9 @@ function (Parent, CoreItem, Settings, nc, Layer) {
this.layerId,
this.limbMeshes[name],
{
x: this.limbs[name].GetPosition().x * Settings.RATIO,
y: this.limbs[name].GetPosition().y * Settings.RATIO,
rotation: this.limbs[name].GetAngle()
x: this.limbs[name].getPosition().x * Settings.RATIO,
y: this.limbs[name].getPosition().y * Settings.RATIO,
rotation: this.limbs[name].getAngle()
}
);
}

View file

@ -173,9 +173,9 @@ function (Parent, Layer, Settings, nc) {
this.layerId,
this.mesh,
{
x: this.body.GetPosition().x * Settings.RATIO,
y: this.body.GetPosition().y * Settings.RATIO,
rotation: this.body.GetAngle()
x: this.body.getPosition().x * Settings.RATIO,
y: this.body.getPosition().y * Settings.RATIO,
rotation: this.body.getAngle()
}
);
@ -186,9 +186,9 @@ function (Parent, Layer, Settings, nc) {
this.layerId,
this.limbMeshes[name],
{
x: this.limbs[name].GetPosition().x * Settings.RATIO,
y: this.limbs[name].GetPosition().y * Settings.RATIO,
rotation: this.limbs[name].GetAngle()
x: this.limbs[name].getPosition().x * Settings.RATIO,
y: this.limbs[name].getPosition().y * Settings.RATIO,
rotation: this.limbs[name].getAngle()
}
);
}

View file

@ -65,8 +65,8 @@ function (Parent, Settings, nc, Assert, PlayerController) {
}
var difference = {
x: Math.abs(this.lastServerPositionState.p.x - this.doll.body.GetPosition().x),
y: Math.abs(this.lastServerPositionState.p.y - this.doll.body.GetPosition().y)
x: Math.abs(this.lastServerPositionState.p.x - this.doll.body.getPosition().x),
y: Math.abs(this.lastServerPositionState.p.y - this.doll.body.getPosition().y)
};
if(difference.x > Settings.ME_STATE_MAX_DIFFERENCE_METERS ||
@ -78,8 +78,8 @@ function (Parent, Settings, nc, Assert, PlayerController) {
Me.prototype.getPositionStateOverride = function() {
return {
p: this.doll.body.GetPosition().Copy(),
lv: this.doll.body.GetLinearVelocity().Copy()
p: this.doll.body.getPosition().clone(),
lv: this.doll.body.getLinearVelocity().clone()
};
};
@ -91,8 +91,8 @@ function (Parent, Settings, nc, Assert, PlayerController) {
Me.prototype.resetPositionState = function(options) {
Assert.number(options.p.x, options.p.y);
Assert.number(options.lv.x, options.lv.y);
this.doll.body.SetPosition(options.p);
this.doll.body.SetLinearVelocity(options.lv);
this.doll.body.setPosition(options.p);
this.doll.body.setLinearVelocity(options.lv);
};
Me.prototype.createAndAddArrow = function() {

View file

@ -2,13 +2,13 @@ define([
"Game/Core/Physics/Engine",
"Game/Config/Settings",
"Game/Client/View/DomController",
"Lib/Vendor/Box2D",
"Lib/Vendor/Planck",
"Lib/Utilities/NotificationCenter",
"Game/Client/View/Pixi/DebugDraw",
"Game/Client/View/Pixi/PlanckDebugDraw",
"Game/Client/View/Pixi/Layers/Debug"
],
function (Parent, Settings, domController, Box2D, nc, DebugDraw, debugLayer) {
function (Parent, Settings, domController, Box2D, nc, PlanckDebugDraw, debugLayer) {
"use strict";
@ -34,25 +34,26 @@ function (Parent, Settings, domController, Box2D, nc, DebugDraw, debugLayer) {
Engine.prototype.setupDebugDraw = function () {
// set debug draw
this.debugDraw = new DebugDraw();
// set debug draw for Planck.js
var canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.pointerEvents = 'none';
canvas.style.zIndex = '1000';
document.body.appendChild(canvas);
this.debugDraw = new PlanckDebugDraw(canvas);
this.debugCanvas = canvas;
};
this.debugDraw.SetSprite(debugLayer.graphics);
this.debugDraw.SetDrawScale(Settings.RATIO);
this.debugDraw.SetFillAlpha(0.5);
this.debugDraw.SetLineThickness(1.0);
this.debugDraw.SetFlags(null
| Box2D.Dynamics.b2DebugDraw.e_shapeBit
| Box2D.Dynamics.b2DebugDraw.e_jointBit
//| Box2D.Dynamics.b2DebugDraw.e_coreShapeBit
//| Box2D.Dynamics.b2DebugDraw.e_aabbBit
//| Box2D.Dynamics.b2DebugDraw.e_centerOfMassBit
//| Box2D.Dynamics.b2DebugDraw.e_obbBit
//| Box2D.Dynamics.b2DebugDraw.e_pairBit
);
this.world.SetDebugDraw(this.debugDraw);
Engine.prototype.renderDebug = function () {
if (this.debugDraw) {
this.debugDraw.clear();
this.debugDraw.drawWorld(this.world);
}
};
Engine.prototype.update = function () {

View file

@ -1,5 +1,5 @@
define([
"Lib/Vendor/Box2D"
"Lib/Vendor/Planck"
],
function (Box2D) {

View file

@ -0,0 +1,154 @@
define([
"Game/Config/Settings"
],
function (Settings) {
"use strict";
function PlanckDebugDraw(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.scale = Settings.RATIO;
this.flags = {
shapes: true,
joints: false,
aabb: false,
pairs: false,
centerOfMass: false
};
}
PlanckDebugDraw.prototype.clear = function() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
PlanckDebugDraw.prototype.drawWorld = function(world) {
if (!this.flags.shapes) return;
this.ctx.save();
this.ctx.scale(this.scale, this.scale);
this.ctx.lineWidth = 1 / this.scale;
// Iterate through all bodies
for (var body = world.getBodyList(); body; body = body.getNext()) {
var transform = body.getTransform();
// Iterate through all fixtures
for (var fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {
var shape = fixture.getShape();
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.ctx.restore();
};
PlanckDebugDraw.prototype.drawShape = function(shape, transform) {
var type = shape.getType();
if (type === 'circle') {
this.drawCircle(shape, transform);
} else if (type === 'polygon') {
this.drawPolygon(shape, transform);
} else if (type === 'edge') {
this.drawEdge(shape, transform);
} else if (type === 'chain') {
this.drawChain(shape, transform);
}
};
PlanckDebugDraw.prototype.drawCircle = function(shape, transform) {
var center = transform.p;
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();
// 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();
};
PlanckDebugDraw.prototype.drawPolygon = function(shape, transform) {
var vertices = shape.m_vertices;
if (!vertices || vertices.length < 3) return;
this.ctx.beginPath();
// Transform first vertex
var v = this.transformVertex(vertices[0], transform);
this.ctx.moveTo(v.x, v.y);
// Transform and draw remaining vertices
for (var i = 1; i < vertices.length; i++) {
v = this.transformVertex(vertices[i], transform);
this.ctx.lineTo(v.x, v.y);
}
this.ctx.closePath();
this.ctx.fill();
this.ctx.stroke();
};
PlanckDebugDraw.prototype.drawEdge = function(shape, transform) {
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();
};
PlanckDebugDraw.prototype.drawChain = function(shape, transform) {
var vertices = shape.m_vertices;
if (!vertices || vertices.length < 2) return;
this.ctx.beginPath();
var v = this.transformVertex(vertices[0], transform);
this.ctx.moveTo(v.x, v.y);
for (var i = 1; i < vertices.length; i++) {
v = this.transformVertex(vertices[i], transform);
this.ctx.lineTo(v.x, v.y);
}
this.ctx.stroke();
};
PlanckDebugDraw.prototype.transformVertex = function(vertex, transform) {
// Apply transform: rotated_vertex = transform.q * vertex + transform.p
var cos = Math.cos(transform.q.getAngle());
var sin = Math.sin(transform.q.getAngle());
return {
x: transform.p.x + (cos * vertex.x - sin * vertex.y),
y: transform.p.y + (sin * vertex.x + cos * vertex.y)
};
};
PlanckDebugDraw.prototype.setFlags = function(flags) {
this.flags = flags;
};
return PlanckDebugDraw;
});