Collision Detection
Making objects bounce correctly requires solving two problems: which pairs of objects might be touching (broad phase), and exactly how they are touching (narrow phase). The algorithms behind AABBs, BVH trees, SAT, GJK, and EPA form the backbone of every physics engine.
1. The Two-Phase Pipeline
With N objects, naïve O(N²) pair testing is too slow. Modern engines split collision into two phases:
2. Broad Phase — BVH and AABB Trees
Axis-Aligned Bounding Boxes (AABB)
Each shape is wrapped in a box aligned to world axes. Two AABBs overlap iff they overlap on all 3 axes simultaneously — tested with 6 comparisons.
Bounding Volume Hierarchy (BVH)
A BVH is a binary tree where each node stores an AABB enclosing all its children. Construction strategy (top-down SAH):
- Choose split axis (longest axis of current AABB).
- Sort primitives along that axis.
- Find split position minimising Surface Area Heuristic (SAH): cost = N_left · Area_left + N_right · Area_right.
- Recurse on each half until leaf size ≤ threshold (~4 triangles).
Traversal: start at root. If no overlap with node AABB, prune entire subtree. Otherwise recurse into children. Average case O(log N) per query; output is O(K) candidate pairs where K is actual overlaps.
3. Narrow Phase — SAT
The Separating Axis Theorem states that two convex shapes are not intersecting if and only if there exists a separating axis — a direction along which their projections do not overlap.
For two convex polygons A and B, the candidate separating axes are the edge normals of both shapes. Algorithm:
- For each edge normal of A, project all vertices of A and B onto that axis.
- If the projected intervals don't overlap → shapes are separated → return false (no collision).
- Repeat for edge normals of B.
- If all axes have overlap → shapes ARE colliding. Track minimum overlap axis → penetration normal.
For 3D convex polyhedra: test face normals of both shapes PLUS cross products of edge pairs (edge from A × edge from B). Total axes = F_A + F_B + E_A × E_B. Early exit on first separating axis makes this fast in practice.
4. GJK Algorithm
The Gilbert-Johnson-Keerthi algorithm is the industry standard for convex shape collision in 3D. Core insight: two convex shapes A and B are not intersecting iff the origin is not contained in the Minkowski difference A ⊕ (−B).
The Minkowski difference M = {a − b | a ∈ A, b ∈ B} is also convex. GJK finds the point in M closest to the origin using only a support function: for a direction d, return the vertex in M that goes furthest in that direction.
GJK builds a simplex (point → line → triangle → tetrahedron in 3D) approaching the origin. At each step it asks: does the current simplex contain the origin? If yes, collision. If no, find next search direction toward origin and add a new support point. Converges in O(1) iterations for most shapes.
Support functions are trivially fast for sphere (r·d̂), capsule, box (sign(d)·halfExtents), and convex hull (O(√N) hill climbing). This is why GJK handles arbitrary convex shapes without precomputing the Minkowski difference.
5. EPA — Contact Manifolds
GJK tells you if shapes collide, but not the penetration depth or contact normal. The Expanding Polytope Algorithm builds on the GJK simplex when a collision is detected:
- Start with the simplex from GJK (tetrahedron containing origin).
- Find the face of the polytope closest to the origin (minimum penetration direction).
- Expand the polytope by finding the support point in the face's outward normal direction.
- Replace the face with two new triangles connecting to the new point.
- Repeat until no new support point is further out → convergence. The closest face gives the penetration normal and depth.
With normal and depth, you can build a contact manifold: the set of contact points (for stable simulation of resting objects, especially boxes, you need 1–4 contact points, not just one). Clip against Voronoi regions of both shapes to find the contact patch.
6. Continuous Collision Detection
Fast objects can tunnel through thin objects between frames (a bullet passing through a wall). Continuous collision detection (CCD) solves this by sweeping shapes along their trajectories.
- Swept sphere/capsule vs plane: Exact closed-form solution. Used for player character controllers.
- Conservative advancement (Mirtich): Iteratively advance both shapes toward collision using a lower bound on distance. Guaranteed to converge without tunnelling.
- Speculative contacts (Catto): At each frame, compute the closest point distance. If the object moves more than that distance in a frame, generate a speculative contact. Cheaper than true CCD; used in Box2D and Jolt.
CCD adds cost — most engines gate it: only fast/small objects (high velocity-to-size ratio) enable CCD, toggled by a "bullet" flag.
7. Implementation Notes
- Sleeping: Objects that haven't moved in N frames are marked sleeping and excluded from collision until a neighbour wakes them. Massive performance win for large scenes.
- Layers and masks: Bitfield collision layer (e.g., Physics layer, Trigger layer, Player layer). Pair tested only if layers have overlapping bit: (layerA & maskB) != 0.
- Island solving: Build a graph of colliding body pairs. Solve each connected component (island) independently using an iterative constraint solver (PGS, XPBD, or TGS).
- Numerical tolerances: GJK/EPA need careful epsilon handling. Separation of 1e-6 m vs 0 matters. Use "allowed penetration" (slop) ~0.005 m to avoid jitter.
- Popular libraries: Jolt Physics (C++, open source, used by Horizon Zero Dawn), Bullet (C++, MIT), PhysX (NVIDIA), Box2D (2D). For WebAssembly: Jolt WASM, Ammo.js (Bullet port).