2.9 KiB
Step 06 — Ray vs AABB (two sweeps become one hit)
This is where step 04 ("collision = the AND of two 1D tests") and step 05 ("the entry/exit time of one sweep") finally fuse. A moving point vs a static box.
Concept
A point at p moves by v over the frame. A static box has a left/right edge
(its x-interval) and a top/bottom edge (its y-interval). The point is inside the
box only while it's inside the x-interval and the y-interval at the same
time.
So run sweepInterval twice:
spanX = sweepInterval(p.x, v.x, box.x, box.x + box.w) // the x-edges
spanY = sweepInterval(p.y, v.y, box.y, box.y + box.h) // the y-edges
Each gives you a time-window [entry, exit] during which the point is inside
that one axis's strip. You're inside the box during the overlap of the two
windows:
entry = max(spanX.entry, spanY.entry) // inside the box once you're inside the LAST axis
exit = min(spanX.exit, spanY.exit) // out of the box once you leave the FIRST axis
Read those two lines until they feel obvious — they're the whole algorithm:
- You're only truly inside the box once you've entered both strips, so the
real entry is the later of the two entries →
max. - You leave the box the instant you exit either strip → the earlier
exit →
min.
When is there NO hit?
- A span is
null— on some axis the point isn't moving and is already outside that strip. It can never be inside the box. Returnnullimmediately. entry > exit— the two windows never overlap. The point is inside one strip, then the other, but never both at once. That's the classic "flies past the corner" miss.entry >= 1orexit <= 0— the windows overlap, but not during this frame (it's entirely in the future, or entirely in the past). Not our problem this frame.
The normal (which wall did we hit?)
When you do collide, you also want to know which face you hit, so the response
later can push you back the right way. That's the normal — a unit vector
pointing out of the surface you struck.
The trick: the axis you entered last is the axis you actually hit. Compare the two entry times — whichever is larger is the blocking axis:
- if
spanX.entry > spanY.entry→ you hit a vertical wall (left/right face). The normal is horizontal, pointing back against your x-motion:normal = { x: v.x > 0 ? -1 : 1, y: 0 }. - otherwise → you hit a horizontal wall (top/bottom). The normal is vertical:
normal = { x: 0, y: v.y > 0 ? -1 : 1 }.
(Moving right and hitting something → the surface pushes you left → normal -1.
That sign rule is all there is to it.)
Task
Implement rayVsAABB(p, v, box) in ray.ts. Return { time, normal } for the
entry, or null for any of the three no-hit cases. sweepInterval is provided
in given.ts — reuse it, don't re-derive it.
bun test workshop/steps/06-ray-vs-aabb