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) { 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; } } } return moveAndSlide(box, v, walls); }