Skip to main content

box3d_rust/distance/
gjk.rs

1// GJK distance (b3ShapeDistance) and support functions from distance.c.
2// SPDX-FileCopyrightText: 2026 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use super::simplex::{
6    compute_witness_points, get_metric, solve_simplex2, solve_simplex3, solve_simplex4, write_cache,
7};
8use super::types::{DistanceInput, DistanceOutput, ShapeProxy, Simplex, SimplexCache};
9use crate::math_functions::{
10    add, blend2, blend3, cross, distance, dot, is_normalized, length_squared,
11    make_matrix_from_quat, max_float, mul_mv, mul_sv, neg, normalize, sub, transpose, Vec3,
12    VEC3_ZERO,
13};
14
15const MAX_SIMPLEX_VERTICES: i32 = 4;
16const MAX_GJK_ITERATIONS: i32 = 32;
17
18/// Support index for a shape proxy along an axis.
19///
20/// We move the first vertex into the origin for improved precision.
21/// This is necessary since we don't have shape transforms and
22/// vertices can potentially be far away from the origin (large).
23/// (b3GetProxySupport)
24pub fn get_proxy_support(proxy: &ShapeProxy, axis: Vec3) -> i32 {
25    let count = proxy.count;
26    debug_assert!(count > 0);
27
28    // We move the first vertex into the origin for improved precision.
29    let origin = proxy.points[0];
30    let mut max_index = 0;
31    let mut max_projection = 0.0;
32
33    for index in 1..count {
34        // We subtract the first vertex since we are shifting into the origin.
35        let projection = dot(axis, sub(proxy.points[index as usize], origin));
36        if projection > max_projection {
37            max_index = index;
38            max_projection = projection;
39        }
40    }
41
42    max_index
43}
44
45/// Support index for a point cloud along an axis. (b3GetPointSupport)
46pub fn get_point_support(points: &[Vec3], axis: Vec3) -> i32 {
47    let count = points.len() as i32;
48    debug_assert!(count > 0);
49
50    // We move the first vertex into the origin for improved precision.
51    let origin = points[0];
52    let mut max_index = 0;
53    let mut max_projection = 0.0;
54
55    for index in 1..count {
56        // We subtract the first vertex since we are shifting into the origin.
57        let projection = dot(axis, sub(points[index as usize], origin));
58        if projection > max_projection {
59            max_index = index;
60            max_projection = projection;
61        }
62    }
63
64    max_index
65}
66
67/// Compute the closest points between two shapes represented as point clouds.
68/// `cache` is input/output. On the first call set `SimplexCache::count` to zero.
69/// The query runs in frame A, so the witness points and normal are returned in
70/// frame A. The underlying GJK algorithm may be debugged by passing in debug
71/// simplexes; pass `None` normally. (b3ShapeDistance)
72pub fn shape_distance(
73    input: &DistanceInput,
74    cache: &mut SimplexCache,
75    mut simplexes: Option<&mut [Simplex]>,
76) -> DistanceOutput {
77    // The query runs in frame A using the relative pose of B in A.
78    let xf = input.transform;
79
80    // Use matrices for faster math
81    let m = make_matrix_from_quat(xf.q);
82    let mt = transpose(m);
83
84    let proxy_a = &input.proxy_a;
85    let proxy_b = &input.proxy_b;
86
87    // Compute initial simplex from cache
88    debug_assert!(cache.count as i32 <= MAX_SIMPLEX_VERTICES);
89
90    let mut simplex = Simplex::default();
91
92    simplex.count = cache.count as i32;
93    for i in 0..cache.count as usize {
94        let index1 = cache.index_a[i] as i32;
95        let index2 = cache.index_b[i] as i32;
96
97        debug_assert!(0 <= index1 && index1 < proxy_a.count);
98        debug_assert!(0 <= index2 && index2 < proxy_b.count);
99
100        let vertex1 = proxy_a.points[index1 as usize];
101        let vertex2 = add(mul_mv(m, proxy_b.points[index2 as usize]), xf.p);
102
103        simplex.vertices[i].index_a = index1;
104        simplex.vertices[i].index_b = index2;
105        simplex.vertices[i].w_a = vertex1;
106        simplex.vertices[i].w_b = vertex2;
107        simplex.vertices[i].w = sub(vertex2, vertex1);
108        simplex.vertices[i].a = 0.0;
109    }
110
111    // Compute the new simplex metric, if it is substantially
112    // different than the old metric flush the simplex.
113    if simplex.count > 0 {
114        let metric1 = cache.metric;
115        let metric2 = get_metric(&simplex);
116
117        // todo the tetrahedron metric can be negative
118        if 2.0 * metric1 < metric2 || metric2 < 0.5 * metric1 || metric2 < f32::EPSILON {
119            // Flush the simplex
120            simplex.count = 0;
121        }
122    }
123
124    // If the cache is invalid or empty
125    if simplex.count == 0 {
126        let vertex1 = proxy_a.points[0];
127        let vertex2 = add(mul_mv(m, proxy_b.points[0]), xf.p);
128
129        simplex.count = 1;
130        simplex.vertices[0].index_a = 0;
131        simplex.vertices[0].index_b = 0;
132        simplex.vertices[0].w_a = vertex1;
133        simplex.vertices[0].w_b = vertex2;
134        simplex.vertices[0].w = sub(vertex2, vertex1);
135        simplex.vertices[0].a = 0.0;
136    }
137
138    let mut backup = Simplex::default();
139
140    let mut simplex_index = 0usize;
141    if let Some(buffer) = simplexes.as_mut() {
142        if simplex_index < buffer.len() {
143            buffer[simplex_index] = simplex;
144            simplex_index += 1;
145        }
146    }
147
148    let mut distance_output = DistanceOutput::default();
149
150    // Keep track of squared distance
151    let mut distance_sq = f32::MAX;
152
153    let mut normal = VEC3_ZERO;
154
155    // Run GJK
156    let mut iteration = 0i32;
157    while iteration < MAX_GJK_ITERATIONS {
158        // Solve simplex
159        let solved = match simplex.count {
160            1 => {
161                simplex.vertices[0].a = 1.0;
162                true
163            }
164            2 => solve_simplex2(&mut simplex),
165            3 => solve_simplex3(&mut simplex),
166            4 => solve_simplex4(&mut simplex),
167            _ => {
168                debug_assert!(false, "Should never get here!");
169                false
170            }
171        };
172
173        if !solved {
174            // No progress - reconstruct last simplex
175            debug_assert!(backup.count != 0);
176            simplex = backup;
177            break;
178        }
179
180        if let Some(buffer) = simplexes.as_mut() {
181            if simplex_index < buffer.len() {
182                buffer[simplex_index] = simplex;
183                simplex_index += 1;
184                distance_output.iterations = iteration;
185                distance_output.simplex_count = simplex_index as i32;
186            }
187        }
188
189        if simplex.count == MAX_SIMPLEX_VERTICES {
190            // Overlap
191            let (local_point_a, local_point_b) = compute_witness_points(&simplex);
192            distance_output.point_a = local_point_a;
193            distance_output.point_b = local_point_b;
194            return distance_output;
195        }
196
197        // Assure distance progression
198        let old_distance_sq = distance_sq;
199
200        // Compute closest point
201        let closest_point = match simplex.count {
202            1 => simplex.vertices[0].w,
203            2 => blend2(
204                simplex.vertices[0].a,
205                simplex.vertices[0].w,
206                simplex.vertices[1].a,
207                simplex.vertices[1].w,
208            ),
209            3 => blend3(
210                simplex.vertices[0].a,
211                simplex.vertices[0].w,
212                simplex.vertices[1].a,
213                simplex.vertices[1].w,
214                simplex.vertices[2].a,
215                simplex.vertices[2].w,
216            ),
217            4 => add(
218                blend2(
219                    simplex.vertices[0].a,
220                    simplex.vertices[0].w,
221                    simplex.vertices[1].a,
222                    simplex.vertices[1].w,
223                ),
224                blend2(
225                    simplex.vertices[2].a,
226                    simplex.vertices[2].w,
227                    simplex.vertices[3].a,
228                    simplex.vertices[3].w,
229                ),
230            ),
231            _ => {
232                debug_assert!(false, "Should never get here!");
233                VEC3_ZERO
234            }
235        };
236
237        distance_sq = dot(closest_point, closest_point);
238
239        if distance_sq >= old_distance_sq {
240            // No progress - reconstruct last simplex
241            debug_assert!(backup.count != 0);
242            simplex = backup;
243            break;
244        }
245
246        // Build new tentative support point
247        let search_direction = match simplex.count {
248            1 => {
249                // v = -A
250                neg(simplex.vertices[0].w)
251            }
252            2 => {
253                // v = (AB x AO) x AB
254                let a = simplex.vertices[0].w;
255                let b = simplex.vertices[1].w;
256                let ab = sub(b, a);
257                cross(cross(ab, neg(a)), ab)
258            }
259            3 => {
260                // v = AB x AC or v = AC x AB
261                let a = simplex.vertices[0].w;
262                let b = simplex.vertices[1].w;
263                let c = simplex.vertices[2].w;
264                let ab = sub(b, a);
265                let ac = sub(c, a);
266                let n = cross(ab, ac);
267                if dot(n, a) < 0.0 {
268                    n
269                } else {
270                    neg(n)
271                }
272            }
273            _ => {
274                debug_assert!(false, "Should never get here!");
275                VEC3_ZERO
276            }
277        };
278
279        if length_squared(search_direction) < 1000.0 * f32::MIN_POSITIVE {
280            // The origin is probably contained by a line segment or triangle.
281            // Thus the shapes are overlapped.
282            let (local_point_a, local_point_b) = compute_witness_points(&simplex);
283            distance_output.point_a = local_point_a;
284            distance_output.point_b = local_point_b;
285            debug_assert!(distance(local_point_a, local_point_b) < f32::EPSILON);
286            return distance_output;
287        }
288
289        normal = neg(search_direction);
290
291        // Get new support points
292        let search_direction1 = search_direction;
293        let index_a = get_proxy_support(&input.proxy_a, neg(search_direction1));
294        let support_a = input.proxy_a.points[index_a as usize];
295        let search_direction2 = mul_mv(mt, search_direction);
296        let index_b = get_proxy_support(&input.proxy_b, search_direction2);
297        let support_b = add(mul_mv(m, input.proxy_b.points[index_b as usize]), xf.p);
298
299        // Save current simplex and add new vertex - this can fail if we detect cycling
300        backup = simplex;
301
302        // Check for duplicate support points. This is the main termination criteria.
303        let mut duplicate = false;
304        for i in 0..simplex.count as usize {
305            if simplex.vertices[i].index_a == index_a && simplex.vertices[i].index_b == index_b {
306                duplicate = true;
307                break;
308            }
309        }
310
311        if duplicate {
312            break;
313        }
314
315        let count = simplex.count as usize;
316        simplex.vertices[count].index_a = index_a;
317        simplex.vertices[count].index_b = index_b;
318        simplex.vertices[count].w_a = support_a;
319        simplex.vertices[count].w_b = support_b;
320        simplex.vertices[count].w = sub(support_b, support_a);
321        simplex.count += 1;
322
323        iteration += 1;
324    }
325
326    normal = normalize(normal);
327    if !is_normalized(normal) {
328        // Treat as overlap
329        return distance_output;
330    }
331
332    // Build witness points and safe cache
333    let (local_point_a, local_point_b) = compute_witness_points(&simplex);
334    write_cache(cache, &simplex);
335
336    // Results stay in frame A
337    distance_output.point_a = local_point_a;
338    distance_output.point_b = local_point_b;
339    distance_output.distance = distance(local_point_a, local_point_b);
340    distance_output.normal = normal;
341    distance_output.iterations = iteration;
342    distance_output.simplex_count = simplex_index as i32;
343
344    // Apply radii if requested
345    if input.use_radii {
346        let r_a = input.proxy_a.radius;
347        let r_b = input.proxy_b.radius;
348        distance_output.distance = max_float(0.0, distance_output.distance - r_a - r_b);
349
350        // Keep closest points on perimeter even if overlapped, this way the points move smoothly.
351        distance_output.point_a = add(distance_output.point_a, mul_sv(r_a, normal));
352        distance_output.point_b = sub(distance_output.point_b, mul_sv(r_b, normal));
353    }
354
355    distance_output
356}