GJK & EPA Contact Physics
Gilbert-Johnson-Keerthi (GJK Algorithm): Standard all-pairs mesh collision checking scales as O(N · M), far too slow for 60 FPS physics engines. The GJK algorithm converts contact detection into a question of whether the Minkowski difference A - B contains the origin (0, 0, 0). By iteratively evaluating directional support functions, GJK converges in near O(1) time.
GJK Geometric Invariant:
1def support_mapping(shape, direction):2 """Returns the extreme point of a convex shape in given direction d."""3 return max(shape, key=lambda pt: np.dot(pt, direction))45def gjk_collision_test(shape_a, shape_b):6 """7 Gilbert-Johnson-Keerthi (GJK) convex collision detection.8 Evaluates support points on Minkowski Difference: C = A - B.9 Iteratively builds a 1-simplex (segment), 2-simplex (triangle), or 3-simplex (tetrahedron).10 Shapes A and B collide if and only if the simplex encloses the origin (0, 0, 0).11 """12 # Direction d = B.center - A.center13 # Simplex initialized with support(A, d) - support(B, -d)14 return "Collision True iff Origin in Simplex"
Expanding Polytope Algorithm (EPA Penetration Depth): GJK only answers a binary question: are the two bodies colliding? It cannot provide contact impulses. The Expanding Polytope Algorithm (EPA) takes the simplex from GJK enclosing the origin and iteratively inflates it outward towards the surface of the Minkowski difference. The closest facet to the origin yields the exact contact normal vector and minimum translation distance required to resolve penetration.
EPA Contact Resolution:
1def epa_penetration_vector(initial_simplex, shape_a, shape_b, tol=1e-4):2 """3 Expanding Polytope Algorithm (EPA) for collision contact manifolds.4 Takes the terminating GJK simplex enclosing the origin.5 Iteratively finds the closest facet to origin, shoots ray in facet normal direction,6 queries support mapping, and expands the polytope until convergence.7 Returns: penetration_depth (scalar) and contact_normal (unit vector).8 """9 polytope = list(initial_simplex)10 while True:11 closest_facet, normal, dist = find_closest_facet_to_origin(polytope)12 p = support(shape_a, normal) - support(shape_b, -normal)13 d = np.dot(p, normal)14 if d - dist < tol:15 return dist, normal # Exact penetration depth and separation direction16 polytope = expand_polytope_with_point(polytope, p)