Collision Systems 101 — AABB & SAT from Scratch
Broadphase pruning, narrowphase math, and resolution strategies for browser games.
Collision detection is two problems: broadphase (what might collide) and narrowphase (do they actually collide, and how?). Get broadphase wrong → slow. Get narrowphase wrong → jitter, tunneling, and sad players. Let’s build AABB checks for rectangles and a minimal SAT (Separating Axis Theorem) for polygons.
1. Broadphase — AABB Checks
// utils/collision.js
export function aabb(ax,ay,aw,ah, bx,by,bw,bh){
return ax < bx+bw && ax+aw > bx && ay < by+bh && ay+ah > by;
}
Use AABB as your first pass. Cheap, O(1), works for all boxes. In practice: wrap sprites in AABBs, test overlaps before running heavier SAT.
2. Narrowphase — SAT
SAT says: two convex polygons do not overlap if there exists an axis where their projections don’t overlap. Otherwise, they overlap. The axis giving the smallest overlap gives the MTV (resolution vector).
export function project(poly, axis){
let min = Infinity, max = -Infinity;
for(const [x,y] of poly){
const dot = x*axis.x + y*axis.y;
if(dot < min) min = dot;
if(dot > max) max = dot;
}
return {min,max};
}
export function sat(polyA, polyB){
let overlap = Infinity, axisOut=null;
const polys = [polyA, polyB];
for(const poly of polys){
for(let i=0;i<poly.length;i++){
const j=(i+1)%poly.length;
const edge = { x: poly[j][0]-poly[i][0], y: poly[j][1]-poly[i][1] };
const axis = { x: -edge.y, y: edge.x };
const len = Math.hypot(axis.x,axis.y); axis.x/=len; axis.y/=len;
const projA = project(polyA, axis);
const projB = project(polyB, axis);
if(projA.max < projB.min || projB.max < projA.min){
return null; // separating axis found → no collision
}
const o = Math.min(projA.max, projB.max) - Math.max(projA.min, projB.min);
if(o < overlap){ overlap=o; axisOut={...axis}; }
}
}
return { overlap, axis:axisOut }; // MTV = axis * overlap
}
3. Resolution — Push Apart
function resolveSAT(bodyA, bodyB, sat){
const mtv = { x: sat.axis.x * sat.overlap, y: sat.axis.y * sat.overlap };
// Push A opposite MTV (half each for fairness)
bodyA.x -= mtv.x/2; bodyA.y -= mtv.y/2;
bodyB.x += mtv.x/2; bodyB.y += mtv.y/2;
}
Apply MTV half to each body or fully to the one with lower mass/inverse mass.
4. Performance & Variants
- Spatial Hashing: bucket entities into a grid → only test neighbors.
- Sweep & Prune: sort by X axis, only test overlapping intervals.
- Circle-Circle: trivial check, perfect for projectiles.
5. Demo — Drag Polygons
Exercise for readers: Build two convex polygons in canvas, allow drag, and color overlap region red when SAT detects collision. 40 lines total.