// The whole kernel you've built, steps 01-09, 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; 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))); }