87 lines
2.2 KiB
TypeScript
87 lines
2.2 KiB
TypeScript
import {
|
|
type AABB,
|
|
EPSILON,
|
|
moveAndSlide,
|
|
penetrationVector,
|
|
type Vec,
|
|
} from "./given.ts";
|
|
|
|
export type Resolution = { x: number; y: number; settled: boolean };
|
|
|
|
/**
|
|
* Push `box` out of EVERY wall — even when one push shoves it into another.
|
|
*
|
|
* One pass over the walls is not enough: a push is a teleport, and a teleport
|
|
* can land you inside a wall the loop already cleared. So: run whole passes
|
|
* over the wall list, applying pushes (with the EPSILON slack — on the pushed
|
|
* axis only!), until a full pass finds nothing to fix. That pass proves you're
|
|
* settled.
|
|
*
|
|
* But some arrangements have NO free spot (a gap narrower than the box), and
|
|
* the passes would ping-pong forever. Cap them — 8 is plenty — and if the cap
|
|
* fires, report `settled: false`. You already believe in caps: your step-10
|
|
* loop has one for exactly the same reason.
|
|
*
|
|
* Returns where the box ended up and whether it truly got free.
|
|
*/
|
|
export function resolveOverlaps(box: AABB, walls: AABB[]): Resolution {
|
|
const res: Resolution = {
|
|
x: box.x,
|
|
y: box.y,
|
|
settled: true,
|
|
};
|
|
|
|
const count = walls.length;
|
|
let index = 0;
|
|
let tries = 0;
|
|
|
|
while (index < count) {
|
|
const pv = penetrationVector(box, walls[index++]);
|
|
|
|
if (pv) {
|
|
if (pv.x !== 0) {
|
|
box.x += pv.x > 0 ? pv.x + EPSILON : pv.x - EPSILON;
|
|
}
|
|
if (pv.y !== 0) {
|
|
box.y += pv.y > 0 ? pv.y + EPSILON : pv.y - EPSILON;
|
|
}
|
|
|
|
res.x = box.x;
|
|
res.y = box.y;
|
|
res.settled = false;
|
|
|
|
index = 0;
|
|
tries += 1;
|
|
} else {
|
|
res.settled = true;
|
|
}
|
|
|
|
if (tries > 7) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Step 12's safeMoveAndSlide, rebuilt on resolveOverlaps.
|
|
*
|
|
* Settled -> sweep from the safe spot, business as usual.
|
|
* Crushed (not settled) -> the sweep would be handed an overlapping box and
|
|
* you know exactly what it does with one of those. Don't feed it. Our crush
|
|
* policy: the walls win — the box stays where the resolver left it, finite,
|
|
* and waits for the gap to open.
|
|
*/
|
|
export function safeMoveAndSlide(box: AABB, v: Vec, walls: AABB[]): Vec {
|
|
const res = resolveOverlaps(box, walls);
|
|
if (res.settled) {
|
|
return moveAndSlide(box, v, walls);
|
|
}
|
|
|
|
return {
|
|
x: box.x,
|
|
y: box.y,
|
|
};
|
|
}
|