ehm
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
// The whole kernel you've built, steps 01-10, finished. Reuse it.
|
||||
export type Vec = { x: number; y: number };
|
||||
export type AABB = { x: number; y: number; w: number; h: number };
|
||||
export type Span = { entry: number; exit: number };
|
||||
export type Hit = { time: number; normal: Vec };
|
||||
|
||||
/** A hair of slack so we stop just short of a wall instead of inside it. */
|
||||
export const EPSILON = 1e-4;
|
||||
|
||||
export const vec = (x: number, y: number): Vec => ({ x, y });
|
||||
export const add = (a: Vec, b: Vec): Vec => ({ x: a.x + b.x, y: a.y + b.y });
|
||||
export const sub = (a: Vec, b: Vec): Vec => ({ x: a.x - b.x, y: a.y - b.y });
|
||||
export const scale = (a: Vec, s: number): Vec => ({ x: a.x * s, y: a.y * s });
|
||||
export const dot = (a: Vec, b: Vec): number => a.x * b.x + a.y * b.y;
|
||||
|
||||
/** Step 04, finished. Strict overlap — merely touching edges is false. */
|
||||
export function aabbOverlap(a: AABB, b: AABB): boolean {
|
||||
return (
|
||||
a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h
|
||||
);
|
||||
}
|
||||
|
||||
export function sweepInterval(
|
||||
p: number,
|
||||
v: number,
|
||||
min: number,
|
||||
max: number,
|
||||
): Span | null {
|
||||
if (v === 0) {
|
||||
return min <= p && p <= max ? { entry: -Infinity, exit: Infinity } : null;
|
||||
}
|
||||
let t1 = (min - p) / v;
|
||||
let t2 = (max - p) / v;
|
||||
if (t1 > t2) {
|
||||
[t1, t2] = [t2, t1];
|
||||
}
|
||||
return { entry: t1, exit: t2 };
|
||||
}
|
||||
|
||||
export function rayVsAABB(p: Vec, v: Vec, box: AABB): Hit | null {
|
||||
const spanX = sweepInterval(p.x, v.x, box.x, box.x + box.w);
|
||||
const spanY = sweepInterval(p.y, v.y, box.y, box.y + box.h);
|
||||
if (spanX === null || spanY === null) {
|
||||
return null;
|
||||
}
|
||||
const entry = Math.max(spanX.entry, spanY.entry);
|
||||
const exit = Math.min(spanX.exit, spanY.exit);
|
||||
if (entry > exit || entry >= 1 || exit <= 0) {
|
||||
return null;
|
||||
}
|
||||
const normal =
|
||||
spanX.entry > spanY.entry
|
||||
? { x: v.x > 0 ? -1 : 1, y: 0 }
|
||||
: { x: 0, y: v.y > 0 ? -1 : 1 };
|
||||
return { time: entry, normal };
|
||||
}
|
||||
|
||||
export function sweptAABB(a: AABB, v: Vec, b: AABB): Hit | null {
|
||||
const inflated: AABB = {
|
||||
x: b.x - a.w,
|
||||
y: b.y - a.h,
|
||||
w: b.w + a.w,
|
||||
h: b.h + a.h,
|
||||
};
|
||||
return rayVsAABB({ x: a.x, y: a.y }, v, inflated);
|
||||
}
|
||||
|
||||
export function slide(v: Vec, normal: Vec): Vec {
|
||||
return sub(v, scale(normal, dot(v, normal)));
|
||||
}
|
||||
|
||||
/** Step 10, finished — your loop, exactly as you wrote it. */
|
||||
export function moveAndSlide(box: AABB, v: Vec, walls: AABB[]): Vec {
|
||||
let pos: Vec = { x: box.x, y: box.y };
|
||||
let vel: Vec = { x: v.x, y: v.y };
|
||||
|
||||
let timeLeft = 1;
|
||||
let i = 0;
|
||||
while (i++ < 4 && timeLeft > 0) {
|
||||
const move = scale(vel, timeLeft);
|
||||
let nearest: Hit = {
|
||||
normal: { x: 0, y: 0 },
|
||||
time: 2,
|
||||
};
|
||||
|
||||
for (const wall of walls) {
|
||||
const hit = sweptAABB(
|
||||
{
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
w: box.w,
|
||||
h: box.h,
|
||||
},
|
||||
move,
|
||||
wall,
|
||||
);
|
||||
if (hit !== null) {
|
||||
nearest = hit.time < nearest.time ? hit : nearest;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearest.time === 2) {
|
||||
pos = add(pos, move);
|
||||
break;
|
||||
} else {
|
||||
pos = add(pos, scale(move, Math.max(0, nearest.time - EPSILON)));
|
||||
vel = slide(vel, nearest.normal);
|
||||
timeLeft = timeLeft * (1 - nearest.time);
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Step 12, finished — your penetrationVector, exactly as you wrote it. */
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user