Skip to main content

rg3d_physics/
gjk_epa.rs

1/// Gilbert-Johnson-Keerthi (GJK) intersection test +  Expanding Polytope
2/// Algorithm (EPA) implementations.
3///
4/// "Implementing GJK", Casey Muratori:
5/// https://www.youtube.com/watch?v=Qupqu1xe7Io
6///
7/// "GJK + Expanding Polytope Algorithm - Implementation and Visualization"
8/// https://www.youtube.com/watch?v=6rgiPrzqt9w
9///
10/// Some ideas about contact point generation were taken from here
11/// https://www.gamedev.net/forums/topic/598678-contact-points-with-epa/
12
13use rg3d_core::{
14    math::vec3::Vec3,
15    math,
16};
17use crate::convex_shape::ConvexShape;
18
19pub const GJK_MAX_ITERATIONS: usize = 64;
20pub const EPA_TOLERANCE: f32 = 0.0001;
21pub const EPA_MAX_ITERATIONS: usize = 64;
22pub const EPA_MAX_LOOSE_EDGES: usize = 32;
23pub const EPA_MAX_FACES: usize = 64;
24
25/// Vertex in space of Minkowski sum
26#[derive(Copy, Clone)]
27pub struct MinkowskiVertex {
28    /// World space position of vertex of shape A. This position will be used
29    /// to compute contact point in world space.
30    shape_a_world_space: Vec3,
31
32    /// Minkowski difference between point in shape A and point in shape B.
33    /// This position will be used to do all simplex and polytope operations.
34    /// https://en.wikipedia.org/wiki/Minkowski_addition
35    minkowski_dif: Vec3,
36}
37
38impl Default for MinkowskiVertex {
39    fn default() -> Self {
40        Self {
41            shape_a_world_space: Default::default(),
42            minkowski_dif: Default::default(),
43        }
44    }
45}
46
47#[derive(Copy, Clone)]
48struct PolytopeTriangle {
49    vertices: [MinkowskiVertex; 3],
50    normal: Vec3,
51}
52
53impl Default for PolytopeTriangle {
54    fn default() -> Self {
55        Self {
56            vertices: Default::default(),
57            normal: Default::default(),
58        }
59    }
60}
61
62#[derive(Copy, Clone)]
63struct PolytopeEdge {
64    begin: MinkowskiVertex,
65    end: MinkowskiVertex,
66}
67
68impl Default for PolytopeEdge {
69    fn default() -> Self {
70        Self {
71            begin: Default::default(),
72            end: Default::default(),
73        }
74    }
75}
76
77impl PolytopeEdge {
78    /// Returns true if edges are equal in combination like this:
79    /// ab = ba
80    fn eq_ccw(&self, other: &PolytopeEdge) -> bool {
81        self.begin.minkowski_dif == other.end.minkowski_dif &&
82            self.end.minkowski_dif == other.begin.minkowski_dif
83    }
84}
85
86pub struct Simplex {
87    /// Vertices of simplex.
88    /// Important: a is most recently added point (closest to origin)!
89    a: MinkowskiVertex,
90    b: MinkowskiVertex,
91    c: MinkowskiVertex,
92    d: MinkowskiVertex,
93    /// Rank of simplex
94    /// 1 - point
95    /// 2 - line
96    /// 3 - triangle
97    /// 4 - tetrahedron
98    rank: usize,
99}
100
101impl Default for Simplex {
102    fn default() -> Self {
103        Self {
104            a: Default::default(),
105            b: Default::default(),
106            c: Default::default(),
107            d: Default::default(),
108            rank: 0,
109        }
110    }
111}
112
113impl Simplex {
114    fn update_triangle(&mut self) -> Vec3 {
115        let ca = self.c.minkowski_dif - self.a.minkowski_dif;
116        let ba = self.b.minkowski_dif - self.a.minkowski_dif;
117
118        // Direction to origin
119        let ao = -self.a.minkowski_dif;
120
121        self.rank = 2;
122
123        let triangle_normal = ba.cross(&ca);
124
125        if ba.cross(&triangle_normal).is_same_direction_as(&ao) {
126            // Closest to edge AB
127            self.c = self.a;
128            return ba.cross(&ao).cross(&ba);
129        }
130
131        if triangle_normal.cross(&ca).is_same_direction_as(&ao) {
132            // Closest to edge AC
133            self.b = self.a;
134            return ca.cross(&ao).cross(&ca);
135        }
136
137        self.rank = 3;
138
139        if triangle_normal.is_same_direction_as(&ao) {
140            // Above triangle
141            self.d = self.c;
142            self.c = self.b;
143            self.b = self.a;
144            return triangle_normal;
145        }
146
147        // Below triangle
148        self.d = self.b;
149        self.b = self.a;
150
151        -triangle_normal
152    }
153
154    fn update_tetrahedron(&mut self) -> Result<(), Vec3> {
155        // Point a is tip of pyramid, BCD is the base (counterclockwise winding order)
156
157        // Direction to origin
158        let ao = -self.a.minkowski_dif;
159
160        // Plane-test origin with 3 faces. This is very inaccurate approach and
161        // it would be better to add additional checks for each face of tetrahedron
162        // to select search direction more precisely. In this case we assume that
163        //we always will produce triangles as final simplex.
164        self.rank = 3;
165
166        let ba = self.b.minkowski_dif - self.a.minkowski_dif;
167        let ca = self.c.minkowski_dif - self.a.minkowski_dif;
168
169        let abc_normal = ba.cross(&ca);
170        if abc_normal.is_same_direction_as(&ao) {
171            // In front of ABC
172            self.d = self.c;
173            self.c = self.b;
174            self.b = self.a;
175            return Err(abc_normal);
176        }
177
178        let da = self.d.minkowski_dif - self.a.minkowski_dif;
179        let acd_normal = ca.cross(&da);
180        if acd_normal.is_same_direction_as(&ao) {
181            // In front of ACD
182            self.b = self.a;
183            return Err(acd_normal);
184        }
185
186        let adb_normal = da.cross(&ba);
187        if adb_normal.is_same_direction_as(&ao) {
188            // In front of ADB
189            self.c = self.d;
190            self.d = self.b;
191            self.b = self.a;
192            return Err(adb_normal);
193        }
194
195        // Otherwise origin is inside tetrahedron
196        Ok(())
197    }
198}
199
200fn de_gjk_support(shape1: &ConvexShape, shape1_position: Vec3, shape2: &ConvexShape, shape2_position: Vec3, dir: &Vec3) -> MinkowskiVertex
201{
202    let shape_a_world_space = shape1.get_farthest_point(shape1_position, *dir);
203    let b = shape2.get_farthest_point(shape2_position, -*dir);
204
205    MinkowskiVertex {
206        shape_a_world_space,
207        minkowski_dif: shape_a_world_space - b,
208    }
209}
210
211pub fn gjk_is_intersects(shape1: &ConvexShape, shape1_position: Vec3, shape2: &ConvexShape, shape2_position: Vec3) -> Option<Simplex> {
212    // This is good enough heuristic to choose initial search direction
213    let mut search_dir = shape1_position - shape2_position;
214
215    if search_dir.sqr_len() == 0.0 {
216        search_dir.x = 1.0;
217    }
218
219    // Get initial point for simplex
220    let mut simplex = Simplex::default();
221    simplex.c = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
222    search_dir = -simplex.c.minkowski_dif; // Search in direction of origin
223
224    // Get second point for a line segment simplex
225    simplex.b = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
226
227    if !simplex.b.minkowski_dif.is_same_direction_as(&search_dir) {
228        return None;
229    }
230
231    let cb = simplex.c.minkowski_dif - simplex.b.minkowski_dif;
232
233    // Search perpendicular to line segment towards origin
234    search_dir = cb.cross(&(-simplex.b.minkowski_dif)).cross(&cb);
235
236    // Origin is on this line segment - fix search direction.
237    if search_dir.sqr_len() == 0.0 {
238        // Perpendicular with x-axis
239        search_dir = cb.cross(&Vec3::new(1.0, 0.0, 0.0));
240        if search_dir.sqr_len() == 0.0 {
241            // Perpendicular with z-axis
242            search_dir = cb.cross(&Vec3::new(0.0, 0.0, -1.0));
243        }
244    }
245
246    simplex.rank = 2;
247    for _ in 0..GJK_MAX_ITERATIONS {
248        simplex.a = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
249
250        if !simplex.a.minkowski_dif.is_same_direction_as(&search_dir) {
251            return None;
252        }
253
254        simplex.rank += 1;
255        if simplex.rank == 3 {
256            search_dir = simplex.update_triangle();
257        } else {
258            match simplex.update_tetrahedron() {
259                Ok(_) => return Some(simplex),
260                Err(dir) => search_dir = dir,
261            }
262        }
263    }
264
265    // No convergence - no intersection
266    None
267}
268
269fn epa_compute_contact_point(closest_triangle: PolytopeTriangle) -> Vec3 {
270    // Project origin onto triangle's plane
271    let proj = closest_triangle.normal.scale(
272        closest_triangle.vertices[0].minkowski_dif.dot(&closest_triangle.normal));
273
274    // Find barycentric coordinates of the projection in Minkowski difference space
275    let (u, v, w) = math::get_barycentric_coords(
276        &proj, &closest_triangle.vertices[0].minkowski_dif,
277        &closest_triangle.vertices[1].minkowski_dif,
278        &closest_triangle.vertices[2].minkowski_dif);
279
280    // Use barycentric coordinates to get projection in world space and sum all
281    // vectors to get world space contact point
282    closest_triangle.vertices[0].shape_a_world_space.scale(u) +
283        closest_triangle.vertices[1].shape_a_world_space.scale(v) +
284        closest_triangle.vertices[2].shape_a_world_space.scale(w)
285}
286
287pub struct PenetrationInfo {
288    pub penetration_vector: Vec3,
289    pub contact_point: Vec3,
290}
291
292pub fn epa_get_penetration_info(simplex: Simplex, shape1: &ConvexShape, shape1_position: Vec3,
293                                shape2: &ConvexShape, shape2_position: Vec3) -> Option<PenetrationInfo> {
294    let mut triangles = [PolytopeTriangle::default(); EPA_MAX_FACES];
295
296    // Reconstruct polytope from tetrahedron simplex points.
297
298    // ABC
299    let ba = simplex.b.minkowski_dif - simplex.a.minkowski_dif;
300    let ca = simplex.c.minkowski_dif - simplex.a.minkowski_dif;
301
302    let abc_normal = ba.cross(&ca);
303    if let Some(abc_normal) = abc_normal.normalized() {
304        triangles[0] = PolytopeTriangle {
305            vertices: [simplex.a, simplex.b, simplex.c],
306            normal: abc_normal,
307        };
308    } else {
309        return None;
310    }
311
312    // ACD
313    let da = simplex.d.minkowski_dif - simplex.a.minkowski_dif;
314
315    let acd_normal = ca.cross(&da);
316    if let Some(acd_normal) = acd_normal.normalized() {
317        triangles[1] = PolytopeTriangle {
318            vertices: [simplex.a, simplex.c, simplex.d],
319            normal: acd_normal,
320        };
321    } else {
322        return None;
323    }
324
325    // ADB
326    let adb_normal = da.cross(&ba);
327    if let Some(adb_normal) = adb_normal.normalized() {
328        triangles[2] = PolytopeTriangle {
329            vertices: [simplex.a, simplex.d, simplex.b],
330            normal: adb_normal,
331        };
332    } else {
333        return None;
334    }
335
336    // BDC
337    let db = simplex.d.minkowski_dif - simplex.b.minkowski_dif;
338    let cb = simplex.c.minkowski_dif - simplex.b.minkowski_dif;
339
340    let bdc_normal = db.cross(&cb);
341    if let Some(bdc_normal) = bdc_normal.normalized() {
342        triangles[3] = PolytopeTriangle {
343            vertices: [simplex.b, simplex.d, simplex.c],
344            normal: bdc_normal,
345        };
346    } else {
347        return None;
348    }
349
350    let mut triangle_count = 4;
351    let mut closest_triangle_index = 0;
352
353    for _ in 0..EPA_MAX_ITERATIONS {
354        // Find triangle that is closest to origin
355        let mut min_dist = triangles[0].vertices[0].minkowski_dif.dot(&triangles[0].normal);
356        closest_triangle_index = 0;
357        for (i, triangle) in triangles.iter().enumerate().take(triangle_count).skip(1) {
358            let dist = triangle.vertices[0].minkowski_dif.dot(&triangle.normal);
359            if dist < min_dist {
360                min_dist = dist;
361                closest_triangle_index = i;
362            }
363        }
364
365        // Search normal to triangle that's closest to origin
366        let closest_triangle = triangles[closest_triangle_index];
367        let search_dir = closest_triangle.normal;
368        let new_point = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
369
370        let distance_to_origin = new_point.minkowski_dif.dot(&search_dir);
371        if distance_to_origin - min_dist < EPA_TOLERANCE {
372            return Some(PenetrationInfo {
373                penetration_vector: closest_triangle.normal.scale(distance_to_origin),
374                contact_point: epa_compute_contact_point(closest_triangle),
375            });
376        }
377
378        // Loose edges after we remove triangle must give us list of edges we have
379        // to stitch with new point to keep polytope convex.
380        let mut loose_edge_count = 0;
381        let mut loose_edges = [PolytopeEdge::default(); EPA_MAX_LOOSE_EDGES];
382
383        // Find all triangles that are facing new point and remove them
384        let mut i = 0;
385        while i < triangle_count {
386            let triangle = triangles[i];
387
388            // If triangle i faces new point, remove it. Also search for adjacent edges of it
389            // and remove them too to maintain loose edge list in correct state (see below).
390            let to_new_point = new_point.minkowski_dif - triangle.vertices[0].minkowski_dif;
391
392            if triangle.normal.dot(&to_new_point) > 0.0 {
393                for j in 0..3 {
394                    let current_edge = PolytopeEdge {
395                        begin: triangle.vertices[j],
396                        end: triangle.vertices[(j + 1) % 3],
397                    };
398
399                    let mut already_in_list = false;
400                    // Check if current edge is already in list
401                    let last_loose_edge = loose_edge_count;
402                    for k in 0..last_loose_edge {
403                        if loose_edges[k].eq_ccw(&current_edge) {
404                            // If we found that current edge is same as other loose edge
405                            // but in reverse order, then we need to replace the loose
406                            // edge by the last loose edge. Lets see at this drawing and
407                            // follow it step-by-step. This is pyramid with tip below A
408                            // and bottom BCDE divided into 4 triangles by point A. All
409                            // triangles given in CCW order.
410                            //
411                            //       B
412                            //      /|\
413                            //     / | \
414                            //    /  |  \
415                            //   /   |   \
416                            //  /    |    \
417                            // E-----A-----C
418                            //  \    |    /
419                            //   \   |   /
420                            //    \  |  /
421                            //     \ | /
422                            //      \|/
423                            //       D
424                            //
425                            // We found that triangles we want to remove are ACB (1), ADC (2),
426                            // AED (3), and ABE (4). Lets start removing them from triangle ACB.
427                            // Also we have to keep list of loose edges for futher linking with
428                            // new point.
429                            //
430                            // 1. AC, CB, BA - just edges of ACB triangle in CCW order.
431                            //
432                            // 2. AC, CB, BA, AD, DC, (CA) - we see that CA already in list
433                            //                               but in reverse order. Do not add it
434                            //                               but move DC onto AC position.
435                            //    DC, CB, BA, AD - loose edge list for 2nd triangle
436                            //
437                            // 3. DC, CB, BA, AD, AE, ED, (DA) - again we already have DA in list as AD
438                            //                                   move ED to AD position.
439                            //    DC, CB, BA, ED, AE
440                            //
441                            // 4. DC, CB, BA, ED, AE, (AB) - same AB already here as BA, move AE to BA.
442                            //    continue adding rest of edges
443                            //    DC, CB, AE, ED, BE, (EA) - EA already here as AE, move BE to AE
444                            //
445                            //    DC, CB, BE, ED - final list of loose edges which gives us
446                            //
447                            //       B
448                            //      / \
449                            //     /   \
450                            //    /     \
451                            //   /       \
452                            //  /         \
453                            // E           C
454                            //  \         /
455                            //   \       /
456                            //    \     /
457                            //     \   /
458                            //      \ /
459                            //       D
460                            //
461                            // Viola! We now have contour which we have to patch using new point.
462                            loose_edge_count -= 1;
463                            loose_edges.swap(k, loose_edge_count);
464
465                            already_in_list = true;
466                            break;
467                        }
468                    }
469
470                    if !already_in_list {
471                        // Add current edge to list
472                        if loose_edge_count >= EPA_MAX_LOOSE_EDGES {
473                            break;
474                        }
475                        loose_edges[loose_edge_count] = current_edge;
476                        loose_edge_count += 1;
477                    }
478                }
479
480                // Replace current triangle with last in list and discard last, so we will continue
481                // processing last triangle and then next to removed. This will effectively reduce
482                // amount of triangles in polytope.
483                triangle_count -= 1;
484                triangles.swap(i, triangle_count);
485            } else {
486                i += 1;
487            }
488        }
489
490        // Reconstruct polytope with new point added
491        for loose_edge in loose_edges[0..loose_edge_count].iter() {
492            if triangle_count >= EPA_MAX_FACES {
493                break;
494            }
495            let new_triangle = triangles.get_mut(triangle_count).unwrap();
496            new_triangle.vertices = [loose_edge.begin, loose_edge.end, new_point];
497
498            let edge_vector = loose_edge.begin.minkowski_dif - loose_edge.end.minkowski_dif;
499            let begin_to_point = loose_edge.begin.minkowski_dif - new_point.minkowski_dif;
500
501            new_triangle.normal = edge_vector.cross(&begin_to_point).normalized().unwrap_or(Vec3::UP);
502
503            // Check for wrong normal to maintain CCW winding
504            let bias = 2.0 * std::f32::EPSILON;
505            if new_triangle.vertices[0].minkowski_dif.dot(&new_triangle.normal) + bias < 0.0 {
506                // Swap vertices to make CCW winding and flip normal.
507                new_triangle.vertices.swap(0, 1);
508                new_triangle.normal = -new_triangle.normal;
509            }
510            triangle_count += 1;
511        }
512    }
513
514    // Return most recent closest point - this is still valid result but less accurate than
515    // if we would have total convergence.
516    let closest_triangle = triangles[closest_triangle_index];
517    Some(PenetrationInfo {
518        penetration_vector: closest_triangle.normal.scale(closest_triangle.vertices[0].minkowski_dif.dot(&closest_triangle.normal)),
519        contact_point: epa_compute_contact_point(closest_triangle),
520    })
521}