48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
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);
|
|
}
|