Files
2026-09-04 13:57:52 +02:00
..
2026-08-27 12:46:42 +02:00
2026-08-08 18:35:57 +02:00
2026-07-11 17:48:28 +02:00
WIP
2026-09-04 13:57:52 +02:00

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?

  1. 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. Return null immediately.
  2. 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.
  3. entry >= 1 or exit <= 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