This commit is contained in:
Schluffe
2026-07-30 22:06:28 +02:00
parent 7262752e16
commit ec9f2e2a2d
13 changed files with 873 additions and 68 deletions
+46 -1
View File
@@ -133,6 +133,51 @@ function drawBox(b, fill, stroke) {
ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w - 1, b.h - 1);
}
}
/**
* How to push `a` OUT of `b`, by the smallest possible single-axis move.
*
* Returns the push as a vector to ADD to a's position, or null if the boxes
* don't strictly overlap (merely touching edges is not overlapping).
*
* There are four ways out (left, right, up, down) — measure all four
* distances and return the shortest one. See the README.
*/
function penetrationVector(a, b) {
const outLeft = a.x + a.w - b.x;
const outRight = b.x + b.w - a.x;
const outUp = a.y + a.h - b.y;
const outDown = b.y + b.h - a.y;
const minX = Math.min(outLeft, outRight);
const minY = Math.min(outUp, outDown);
if (minX > 0 && minY > 0) {
return minX > minY
? { x: 0, y: outDown > outUp ? -minY : minY }
: { x: outLeft > outRight ? minX : -minX, y: 0 };
}
return null;
}
/**
* moveAndSlide, but immune to the overlap trap: first push `box` out of any
* wall it is already inside (plus an EPSILON of slack, same idea as the
* backoff in the loop), THEN run the normal sweep loop from the safe spot.
*
* Everything you need is penetrationVector above and given.ts.
*/
function safeMoveAndSlide(box, v, walls) {
for (const wall of walls) {
const pv = penetrationVector(box, wall);
if (pv) {
box.x += pv.x > 0 ? pv.x + EPSILON : pv.x - EPSILON;
box.y += pv.y > 0 ? pv.y + EPSILON : pv.y - EPSILON;
}
}
return moveAndSlide(box, v, walls);
}
let then = performance.now();
function frame(now) {
@@ -156,7 +201,7 @@ function frame(now) {
// full-frame displacement is velocity * delta — exactly what moveAndSlide expects
const displacement = scale(velocity, delta);
const next = moveAndSlide(player, displacement, walls);
const next = safeMoveAndSlide(player, displacement, walls);
player.x = next.x;
player.y = next.y;