// Finished in earlier steps — reuse, don't rewrite. export type Vec = { x: number; y: number }; export const vec = (x: number, y: number): Vec => ({ x, y }); 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 }; /** Step 05, finished. */ 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 }; } /** Step 06, finished. A moving point vs a static box. */ 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) { return null; } if (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 }; }