time for a snapshot

This commit is contained in:
Schluffe
2026-07-11 17:48:28 +02:00
parent 135b7c3042
commit 7262752e16
47 changed files with 1712 additions and 61 deletions
+47
View File
@@ -0,0 +1,47 @@
# Step 04 — AABB & the discrete overlap test
## Concept
**AABB** = **A**xis-**A**ligned **B**ounding **B**ox: a rectangle whose sides are
parallel to the x and y axes (never rotated). They're cheap to test, which is why
almost every 2D engine — including yours — uses them as the base collision shape.
We represent one as a corner plus a size:
```
type AABB = { x, y, w, h } // (x,y) = top-left corner, w = width, h = height
```
So the box spans `x .. x+w` horizontally and `y .. y+h` vertically.
### Two boxes overlap when they overlap on BOTH axes
This is the key insight you'll reuse for the rest of the workshop. Think of each
box as a **shadow on the x-axis** and a **shadow on the y-axis**. Two boxes
intersect only if *both* pairs of shadows intersect:
```
overlapX: a.x < b.x + b.w AND b.x < a.x + a.w
overlapY: a.y < b.y + b.h AND b.y < a.y + a.h
overlap = overlapX AND overlapY
```
Hold onto "collision = the AND of two 1D tests." In a few steps you'll do the
exact same thing, but with **time** instead of space, and that's the whole trick
behind swept collision.
### The discrete trap (why this test alone isn't enough)
`aabbOverlap` only answers "are they overlapping *right now*?" If a fast object
jumps from one side of a thin wall to the other in a single frame, it never
overlaps the wall at any sampled instant — so this test says "no collision" and
the object tunnels straight through. Steps 05+ fix that by testing the *path*,
not the endpoints. Feel the gap here first; it's why everything after exists.
## Task
Implement `pointInAABB` and `aabbOverlap` in `aabb.ts`.
```sh
bun test workshop/steps/04-aabb
```
+28
View File
@@ -0,0 +1,28 @@
import { expect, test } from "bun:test";
import { type AABB, aabbOverlap, pointInAABB } from "./aabb.ts";
const box: AABB = { x: 10, y: 10, w: 20, h: 20 }; // spans 10..30 in both axes
test("point inside the box", () => {
expect(pointInAABB({ x: 15, y: 15 }, box)).toBe(true);
});
test("point outside the box", () => {
expect(pointInAABB({ x: 5, y: 15 }, box)).toBe(false);
expect(pointInAABB({ x: 15, y: 35 }, box)).toBe(false);
});
test("overlapping boxes", () => {
const other: AABB = { x: 20, y: 20, w: 20, h: 20 };
expect(aabbOverlap(box, other)).toBe(true);
});
test("separated on the x axis only -> no overlap", () => {
const other: AABB = { x: 40, y: 10, w: 5, h: 20 };
expect(aabbOverlap(box, other)).toBe(false);
});
test("separated on the y axis only -> no overlap", () => {
const other: AABB = { x: 10, y: 40, w: 20, h: 5 };
expect(aabbOverlap(box, other)).toBe(false);
});
+18
View File
@@ -0,0 +1,18 @@
export type Vec = { x: number; y: number };
/** Axis-aligned box: (x,y) is the top-left corner, w/h are width/height. */
export type AABB = { x: number; y: number; w: number; h: number };
/** Is the point inside (or on the edge of) the box? */
export function pointInAABB(p: Vec, box: AABB): boolean {
return (
p.x >= box.x && p.x <= box.x + box.w && p.y >= box.y && p.y <= box.y + box.h
);
}
/** Do the two boxes overlap right now? (discrete test) */
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
);
}