Initializing 3D Canvas...

GJK & EPA Contact Physics

5 min read•2 pages

GJK Geometric Invariant:

1. Minkowski Difference Locus: Shapes A and B overlap if and only if the origin 0 is in A - B. 2. Support Function: S_A(d) finds the extremal point in direction d, avoiding full mesh storage. 3. Evolving Simplex: Maintains at most 4 points (tetrahedron in 3D) that systematically traps the origin.
python
1import numpy as np
2
3def support(shape_a, shape_b, d):
4 """Minkowski Difference support point: S_{A-B}(d) = S_A(d) - S_B(-d)."""
5 pt_a = max(shape_a, key=lambda p: np.dot(p, d))
6 pt_b = max(shape_b, key=lambda p: np.dot(p, -d))
7 return np.asarray(pt_a) - np.asarray(pt_b)
8
9def gjk_collision_test(shape_a, shape_b, max_iter=25):
10 """
11 Gilbert-Johnson-Keerthi (GJK) 2D convex collision detection.
12 Iteratively builds a 2-simplex (triangle) in Minkowski Difference C = A - B
13 to test whether C encloses the origin (0, 0).
14 """
15 d = np.mean(shape_b, axis=0) - np.mean(shape_a, axis=0)
16 if np.linalg.norm(d) < 1e-8:
17 d = np.array([1.0, 0.0])
18 simplex = [support(shape_a, shape_b, d)]
19 d = -simplex[0]
20
21 for _ in range(max_iter):
22 a = support(shape_a, shape_b, d)
23 if np.dot(a, d) < 0:
24 return False, simplex # Separating axis found
25 simplex.append(a)
26 if len(simplex) == 2:
27 ab, ao = simplex[0] - a, -a
28 d = np.array([-ab[1], ab[0]])
29 if np.dot(d, ao) < 0:
30 d = -d
31 elif len(simplex) == 3:
32 c, b, a = simplex
33 ab, ac, ao = b - a, c - a, -a
34 ab_perp = np.array([-ab[1], ab[0]]) * (-1 if np.dot([-ab[1], ab[0]], ac) > 0 else 1)
35 ac_perp = np.array([-ac[1], ac[0]]) * (-1 if np.dot([-ac[1], ac[0]], ab) > 0 else 1)
36 if np.dot(ab_perp, ao) > 0:
37 simplex.pop(0)
38 d = ab_perp
39 elif np.dot(ac_perp, ao) > 0:
40 simplex.pop(1)
41 d = ac_perp
42 else:
43 return True, simplex # Origin enclosed inside 2-simplex
44 return False, simplex

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.

Body B Distance (Negative = Intersecting)
0.80
3 min read•1 page

EPA Contact Resolution:

1. Polytope Inflation: Expands triangular facets outwards along their outward-pointing normal vectors. 2. Closest Facet Distance: Measures the perpendicular distance from the origin (0,0,0) to the nearest edge/face. 3. Physics Contact Manifold: Feeds penetration depth and contact normal directly into the LCP impulse solver.
python
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 direction
16 polytope = expand_polytope_with_point(polytope, p)

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 Expansion Iteration
3.00

Related Concepts