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
+47
View File
@@ -0,0 +1,47 @@
import { type AABB, EPSILON, moveAndSlide, type Vec } from "./given.ts";
/**
* 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.
*/
export function penetrationVector(a: AABB, b: AABB): Vec | null {
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.
*/
export function safeMoveAndSlide(box: AABB, v: Vec, walls: AABB[]): Vec {
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);
}