parry2d/query/epa/
epa2.rs

1//! Two-dimensional penetration depth queries using the Expanding Polytope Algorithm.
2
3use alloc::{collections::BinaryHeap, vec::Vec};
4use core::cmp::Ordering;
5
6use na::{self, Unit};
7use num::Bounded;
8
9use crate::math::{Isometry, Point, Real, Vector};
10use crate::query::gjk::{self, CSOPoint, ConstantOrigin, VoronoiSimplex};
11use crate::shape::SupportMap;
12use crate::utils;
13
14#[derive(Copy, Clone, PartialEq)]
15struct FaceId {
16    id: usize,
17    neg_dist: Real,
18}
19
20impl FaceId {
21    fn new(id: usize, neg_dist: Real) -> Option<Self> {
22        if neg_dist > gjk::eps_tol() {
23            None
24        } else {
25            Some(FaceId { id, neg_dist })
26        }
27    }
28}
29
30impl Eq for FaceId {}
31
32impl PartialOrd for FaceId {
33    #[inline]
34    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
35        Some(self.cmp(other))
36    }
37}
38
39impl Ord for FaceId {
40    #[inline]
41    fn cmp(&self, other: &Self) -> Ordering {
42        if self.neg_dist < other.neg_dist {
43            Ordering::Less
44        } else if self.neg_dist > other.neg_dist {
45            Ordering::Greater
46        } else {
47            Ordering::Equal
48        }
49    }
50}
51
52#[derive(Clone, Debug)]
53struct Face {
54    pts: [usize; 2],
55    normal: Unit<Vector<Real>>,
56    proj: Point<Real>,
57    bcoords: [Real; 2],
58    deleted: bool,
59}
60
61impl Face {
62    pub fn new(vertices: &[CSOPoint], pts: [usize; 2]) -> (Self, bool) {
63        if let Some((proj, bcoords)) =
64            project_origin(&vertices[pts[0]].point, &vertices[pts[1]].point)
65        {
66            (Self::new_with_proj(vertices, proj, bcoords, pts), true)
67        } else {
68            (
69                Self::new_with_proj(vertices, Point::origin(), [0.0; 2], pts),
70                false,
71            )
72        }
73    }
74
75    pub fn new_with_proj(
76        vertices: &[CSOPoint],
77        proj: Point<Real>,
78        bcoords: [Real; 2],
79        pts: [usize; 2],
80    ) -> Self {
81        let normal;
82        let deleted;
83
84        if let Some(n) = utils::ccw_face_normal([&vertices[pts[0]].point, &vertices[pts[1]].point])
85        {
86            normal = n;
87            deleted = false;
88        } else {
89            normal = Unit::new_unchecked(na::zero());
90            deleted = true;
91        }
92
93        Face {
94            pts,
95            normal,
96            proj,
97            bcoords,
98            deleted,
99        }
100    }
101
102    pub fn closest_points(&self, vertices: &[CSOPoint]) -> (Point<Real>, Point<Real>) {
103        (
104            vertices[self.pts[0]].orig1 * self.bcoords[0]
105                + vertices[self.pts[1]].orig1.coords * self.bcoords[1],
106            vertices[self.pts[0]].orig2 * self.bcoords[0]
107                + vertices[self.pts[1]].orig2.coords * self.bcoords[1],
108        )
109    }
110}
111
112/// The Expanding Polytope Algorithm in 2D.
113#[derive(Default)]
114pub struct EPA {
115    vertices: Vec<CSOPoint>,
116    faces: Vec<Face>,
117    heap: BinaryHeap<FaceId>,
118}
119
120impl EPA {
121    /// Creates a new instance of the 2D Expanding Polytope Algorithm.
122    pub fn new() -> Self {
123        EPA::default()
124    }
125
126    fn reset(&mut self) {
127        self.vertices.clear();
128        self.faces.clear();
129        self.heap.clear();
130    }
131
132    /// Projects the origin on boundary the given shape.
133    ///
134    /// The origin is assumed to be inside of the shape. If it is outside, use
135    /// the GJK algorithm instead.
136    ///
137    /// Return `None` if the origin is not inside of the shape or if
138    /// the EPA algorithm failed to compute the projection.
139    ///
140    /// Return the projected point in the local-space of `g`.
141    pub fn project_origin<G: ?Sized + SupportMap>(
142        &mut self,
143        m: &Isometry<Real>,
144        g: &G,
145        simplex: &VoronoiSimplex,
146    ) -> Option<Point<Real>> {
147        self.closest_points(&m.inverse(), g, &ConstantOrigin, simplex)
148            .map(|(p, _, _)| p)
149    }
150
151    /// Projects the origin on a shape using the EPA algorithm.
152    ///
153    /// The origin is assumed to be located inside of the shape.
154    /// Returns `None` if the EPA fails to converge or if `g1` and `g2` are not penetrating.
155    pub fn closest_points<G1, G2>(
156        &mut self,
157        pos12: &Isometry<Real>,
158        g1: &G1,
159        g2: &G2,
160        simplex: &VoronoiSimplex,
161    ) -> Option<(Point<Real>, Point<Real>, Unit<Vector<Real>>)>
162    where
163        G1: ?Sized + SupportMap,
164        G2: ?Sized + SupportMap,
165    {
166        let _eps: Real = crate::math::DEFAULT_EPSILON;
167        let _eps_tol = _eps * 100.0;
168
169        self.reset();
170
171        /*
172         * Initialization.
173         */
174        for i in 0..simplex.dimension() + 1 {
175            self.vertices.push(*simplex.point(i));
176        }
177
178        if simplex.dimension() == 0 {
179            const MAX_ITERS: usize = 100; // If there is no convergence, just use whatever direction was extracted so fare
180
181            // The contact is vertex-vertex.
182            // We need to determine a valid normal that lies
183            // on both vertices' normal cone.
184            let mut n = Vector::y_axis();
185
186            // First, find a vector on the first vertex tangent cone.
187            let orig1 = self.vertices[0].orig1;
188            for _ in 0..MAX_ITERS {
189                let supp1 = g1.local_support_point(&n);
190                if let Some(tangent) = Unit::try_new(supp1 - orig1, _eps_tol) {
191                    if n.dot(&tangent) < _eps_tol {
192                        break;
193                    }
194
195                    n = Unit::new_unchecked(Vector::new(-tangent.y, tangent.x));
196                } else {
197                    break;
198                }
199            }
200
201            // Second, ensure the direction lies on the second vertex's tangent cone.
202            let orig2 = self.vertices[0].orig2;
203            for _ in 0..MAX_ITERS {
204                let supp2 = g2.support_point(pos12, &-n);
205                if let Some(tangent) = Unit::try_new(supp2 - orig2, _eps_tol) {
206                    if (-n).dot(&tangent) < _eps_tol {
207                        break;
208                    }
209
210                    n = Unit::new_unchecked(Vector::new(-tangent.y, tangent.x));
211                } else {
212                    break;
213                }
214            }
215
216            return Some((Point::origin(), Point::origin(), n));
217        } else if simplex.dimension() == 2 {
218            let dp1 = self.vertices[1] - self.vertices[0];
219            let dp2 = self.vertices[2] - self.vertices[0];
220
221            if dp1.perp(&dp2) < 0.0 {
222                self.vertices.swap(1, 2)
223            }
224
225            let pts1 = [0, 1];
226            let pts2 = [1, 2];
227            let pts3 = [2, 0];
228
229            let (face1, proj_inside1) = Face::new(&self.vertices, pts1);
230            let (face2, proj_inside2) = Face::new(&self.vertices, pts2);
231            let (face3, proj_inside3) = Face::new(&self.vertices, pts3);
232
233            self.faces.push(face1);
234            self.faces.push(face2);
235            self.faces.push(face3);
236
237            if proj_inside1 {
238                let dist1 = self.faces[0].normal.dot(&self.vertices[0].point.coords);
239                self.heap.push(FaceId::new(0, -dist1)?);
240            }
241
242            if proj_inside2 {
243                let dist2 = self.faces[1].normal.dot(&self.vertices[1].point.coords);
244                self.heap.push(FaceId::new(1, -dist2)?);
245            }
246
247            if proj_inside3 {
248                let dist3 = self.faces[2].normal.dot(&self.vertices[2].point.coords);
249                self.heap.push(FaceId::new(2, -dist3)?);
250            }
251
252            if !(proj_inside1 || proj_inside2 || proj_inside3) {
253                // Related issues:
254                // https://github.com/dimforge/parry/issues/253
255                // https://github.com/dimforge/parry/issues/246
256                log::debug!("Hit unexpected state in EPA: failed to project the origin on the initial simplex.");
257                return None;
258            }
259        } else {
260            let pts1 = [0, 1];
261            let pts2 = [1, 0];
262
263            self.faces.push(Face::new_with_proj(
264                &self.vertices,
265                Point::origin(),
266                [1.0, 0.0],
267                pts1,
268            ));
269            self.faces.push(Face::new_with_proj(
270                &self.vertices,
271                Point::origin(),
272                [1.0, 0.0],
273                pts2,
274            ));
275
276            let dist1 = self.faces[0].normal.dot(&self.vertices[0].point.coords);
277            let dist2 = self.faces[1].normal.dot(&self.vertices[1].point.coords);
278
279            self.heap.push(FaceId::new(0, dist1)?);
280            self.heap.push(FaceId::new(1, dist2)?);
281        }
282
283        let mut niter = 0;
284        let mut max_dist = Real::max_value();
285        let mut best_face_id = *self.heap.peek().unwrap();
286        let mut old_dist = 0.0;
287
288        /*
289         * Run the expansion.
290         */
291        while let Some(face_id) = self.heap.pop() {
292            // Create new faces.
293            let face = self.faces[face_id.id].clone();
294
295            if face.deleted {
296                continue;
297            }
298
299            let cso_point = CSOPoint::from_shapes(pos12, g1, g2, &face.normal);
300            let support_point_id = self.vertices.len();
301            self.vertices.push(cso_point);
302
303            let candidate_max_dist = cso_point.point.coords.dot(&face.normal);
304
305            if candidate_max_dist < max_dist {
306                best_face_id = face_id;
307                max_dist = candidate_max_dist;
308            }
309
310            let curr_dist = -face_id.neg_dist;
311
312            if max_dist - curr_dist < _eps_tol ||
313                // Accept the intersection as the algorithm is stuck and no new points will be found
314                // This happens because of numerical stability issue
315                ((curr_dist - old_dist).abs() < _eps && candidate_max_dist < max_dist)
316            {
317                let best_face = &self.faces[best_face_id.id];
318                let cpts = best_face.closest_points(&self.vertices);
319                return Some((cpts.0, cpts.1, best_face.normal));
320            }
321
322            old_dist = curr_dist;
323
324            let pts1 = [face.pts[0], support_point_id];
325            let pts2 = [support_point_id, face.pts[1]];
326
327            let new_faces = [
328                Face::new(&self.vertices, pts1),
329                Face::new(&self.vertices, pts2),
330            ];
331
332            for f in new_faces.iter() {
333                if f.1 {
334                    let dist = f.0.normal.dot(&f.0.proj.coords);
335                    if dist < curr_dist {
336                        // TODO: if we reach this point, there were issues due to
337                        // numerical errors.
338                        let cpts = f.0.closest_points(&self.vertices);
339                        return Some((cpts.0, cpts.1, f.0.normal));
340                    }
341
342                    if !f.0.deleted {
343                        self.heap.push(FaceId::new(self.faces.len(), -dist)?);
344                    }
345                }
346
347                self.faces.push(f.0.clone());
348            }
349
350            niter += 1;
351            if niter > 100 {
352                // if we reached this point, our algorithm didn't converge to what precision we wanted.
353                // still return an intersection point, as it's probably close enough.
354                break;
355            }
356        }
357
358        let best_face = &self.faces[best_face_id.id];
359        let cpts = best_face.closest_points(&self.vertices);
360        Some((cpts.0, cpts.1, best_face.normal))
361    }
362}
363
364fn project_origin(a: &Point<Real>, b: &Point<Real>) -> Option<(Point<Real>, [Real; 2])> {
365    let ab = *b - *a;
366    let ap = -a.coords;
367    let ab_ap = ab.dot(&ap);
368    let sqnab = ab.norm_squared();
369
370    if sqnab == 0.0 {
371        return None;
372    }
373
374    let position_on_segment;
375
376    let _eps: Real = gjk::eps_tol();
377
378    if ab_ap < -_eps || ab_ap > sqnab + _eps {
379        // Voronoï region of vertex 'a' or 'b'.
380        None
381    } else {
382        // Voronoï region of the segment interior.
383        position_on_segment = ab_ap / sqnab;
384
385        let res = *a + ab * position_on_segment;
386
387        Some((res, [1.0 - position_on_segment, position_on_segment]))
388    }
389}