parry3d_f64/query/
default_query_dispatcher.rs

1use crate::math::{Isometry, Point, Real, Vector};
2use crate::query::details::ShapeCastOptions;
3use crate::query::{
4    self, details::NonlinearShapeCastMode, ClosestPoints, Contact, NonlinearRigidMotion,
5    QueryDispatcher, ShapeCastHit, Unsupported,
6};
7#[cfg(feature = "alloc")]
8use crate::query::{
9    contact_manifolds::{ContactManifoldsWorkspace, NormalConstraints},
10    query_dispatcher::PersistentQueryDispatcher,
11    ContactManifold,
12};
13use crate::shape::{HalfSpace, Segment, Shape, ShapeType};
14#[cfg(feature = "alloc")]
15use alloc::vec::Vec;
16
17/// A dispatcher that exposes built-in queries
18#[derive(Debug, Clone)]
19pub struct DefaultQueryDispatcher;
20
21impl QueryDispatcher for DefaultQueryDispatcher {
22    fn intersection_test(
23        &self,
24        pos12: &Isometry<Real>,
25        shape1: &dyn Shape,
26        shape2: &dyn Shape,
27    ) -> Result<bool, Unsupported> {
28        if let (Some(b1), Some(b2)) = (shape1.as_ball(), shape2.as_ball()) {
29            let p12 = Point::from(pos12.translation.vector);
30            Ok(query::details::intersection_test_ball_ball(&p12, b1, b2))
31        } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
32            Ok(query::details::intersection_test_cuboid_cuboid(
33                pos12, c1, c2,
34            ))
35        } else if let (Some(t1), Some(c2)) = (shape1.as_triangle(), shape2.as_cuboid()) {
36            Ok(query::details::intersection_test_triangle_cuboid(
37                pos12, t1, c2,
38            ))
39        } else if let (Some(c1), Some(t2)) = (shape1.as_cuboid(), shape2.as_triangle()) {
40            Ok(query::details::intersection_test_cuboid_triangle(
41                pos12, c1, t2,
42            ))
43        } else if let Some(b1) = shape1.as_ball() {
44            Ok(query::details::intersection_test_ball_point_query(
45                pos12, b1, shape2,
46            ))
47        } else if let Some(b2) = shape2.as_ball() {
48            Ok(query::details::intersection_test_point_query_ball(
49                pos12, shape1, b2,
50            ))
51        } else if let (Some(p1), Some(s2)) =
52            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
53        {
54            Ok(query::details::intersection_test_halfspace_support_map(
55                pos12, p1, s2,
56            ))
57        } else if let (Some(s1), Some(p2)) =
58            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
59        {
60            Ok(query::details::intersection_test_support_map_halfspace(
61                pos12, s1, p2,
62            ))
63        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
64            Ok(query::details::intersection_test_support_map_support_map(
65                pos12, s1, s2,
66            ))
67        } else {
68            #[cfg(feature = "alloc")]
69            if let Some(c1) = shape1.as_composite_shape() {
70                return Ok(query::details::intersection_test_composite_shape_shape(
71                    self, pos12, c1, shape2,
72                ));
73            } else if let Some(c2) = shape2.as_composite_shape() {
74                return Ok(query::details::intersection_test_shape_composite_shape(
75                    self, pos12, shape1, c2,
76                ));
77            }
78
79            Err(Unsupported)
80        }
81    }
82
83    /// Computes the minimum distance separating two shapes.
84    ///
85    /// Returns `0.0` if the objects are touching or penetrating.
86    fn distance(
87        &self,
88        pos12: &Isometry<Real>,
89        shape1: &dyn Shape,
90        shape2: &dyn Shape,
91    ) -> Result<Real, Unsupported> {
92        let ball1 = shape1.as_ball();
93        let ball2 = shape2.as_ball();
94
95        if let (Some(b1), Some(b2)) = (ball1, ball2) {
96            let p2 = Point::from(pos12.translation.vector);
97            Ok(query::details::distance_ball_ball(b1, &p2, b2))
98        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
99            Ok(query::details::distance_ball_convex_polyhedron(
100                pos12, b1, shape2,
101            ))
102        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
103            Ok(query::details::distance_convex_polyhedron_ball(
104                pos12, shape1, b2,
105            ))
106        } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
107            Ok(query::details::distance_cuboid_cuboid(pos12, c1, c2))
108        } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) {
109            Ok(query::details::distance_segment_segment(pos12, s1, s2))
110        } else if let (Some(p1), Some(s2)) =
111            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
112        {
113            Ok(query::details::distance_halfspace_support_map(
114                pos12, p1, s2,
115            ))
116        } else if let (Some(s1), Some(p2)) =
117            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
118        {
119            Ok(query::details::distance_support_map_halfspace(
120                pos12, s1, p2,
121            ))
122        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
123            Ok(query::details::distance_support_map_support_map(
124                pos12, s1, s2,
125            ))
126        } else {
127            #[cfg(feature = "alloc")]
128            if let Some(c1) = shape1.as_composite_shape() {
129                return Ok(query::details::distance_composite_shape_shape(
130                    self, pos12, c1, shape2,
131                ));
132            } else if let Some(c2) = shape2.as_composite_shape() {
133                return Ok(query::details::distance_shape_composite_shape(
134                    self, pos12, shape1, c2,
135                ));
136            }
137
138            Err(Unsupported)
139        }
140    }
141
142    fn contact(
143        &self,
144        pos12: &Isometry<Real>,
145        shape1: &dyn Shape,
146        shape2: &dyn Shape,
147        prediction: Real,
148    ) -> Result<Option<Contact>, Unsupported> {
149        let ball1 = shape1.as_ball();
150        let ball2 = shape2.as_ball();
151
152        if let (Some(b1), Some(b2)) = (ball1, ball2) {
153            Ok(query::details::contact_ball_ball(pos12, b1, b2, prediction))
154        // } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
155        //     Ok(query::details::contact_cuboid_cuboid(
156        //         pos12, c1, c2, prediction,
157        //     ))
158        } else if let (Some(p1), Some(s2)) =
159            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
160        {
161            Ok(query::details::contact_halfspace_support_map(
162                pos12, p1, s2, prediction,
163            ))
164        } else if let (Some(s1), Some(p2)) =
165            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
166        {
167            Ok(query::details::contact_support_map_halfspace(
168                pos12, s1, p2, prediction,
169            ))
170        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
171            Ok(query::details::contact_ball_convex_polyhedron(
172                pos12, b1, shape2, prediction,
173            ))
174        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
175            Ok(query::details::contact_convex_polyhedron_ball(
176                pos12, shape1, b2, prediction,
177            ))
178        } else {
179            #[cfg(feature = "alloc")]
180            if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
181                return Ok(query::details::contact_support_map_support_map(
182                    pos12, s1, s2, prediction,
183                ));
184            } else if let Some(c1) = shape1.as_composite_shape() {
185                return Ok(query::details::contact_composite_shape_shape(
186                    self, pos12, c1, shape2, prediction,
187                ));
188            } else if let Some(c2) = shape2.as_composite_shape() {
189                return Ok(query::details::contact_shape_composite_shape(
190                    self, pos12, shape1, c2, prediction,
191                ));
192            }
193
194            Err(Unsupported)
195        }
196    }
197
198    fn closest_points(
199        &self,
200        pos12: &Isometry<Real>,
201        shape1: &dyn Shape,
202        shape2: &dyn Shape,
203        max_dist: Real,
204    ) -> Result<ClosestPoints, Unsupported> {
205        let ball1 = shape1.as_ball();
206        let ball2 = shape2.as_ball();
207
208        if let (Some(b1), Some(b2)) = (ball1, ball2) {
209            Ok(query::details::closest_points_ball_ball(
210                pos12, b1, b2, max_dist,
211            ))
212        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
213            Ok(query::details::closest_points_ball_convex_polyhedron(
214                pos12, b1, shape2, max_dist,
215            ))
216        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
217            Ok(query::details::closest_points_convex_polyhedron_ball(
218                pos12, shape1, b2, max_dist,
219            ))
220        } else if let (Some(s1), Some(s2)) =
221            (shape1.as_shape::<Segment>(), shape2.as_shape::<Segment>())
222        {
223            Ok(query::details::closest_points_segment_segment(
224                pos12, s1, s2, max_dist,
225            ))
226        // } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
227        //     Ok(query::details::closest_points_cuboid_cuboid(
228        //         pos12, c1, c2, max_dist,
229        //     ))
230        } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) {
231            Ok(query::details::closest_points_segment_segment(
232                pos12, s1, s2, max_dist,
233            ))
234        // } else if let (Some(c1), Some(t2)) = (shape1.as_cuboid(), shape2.as_triangle()) {
235        //     Ok(query::details::closest_points_cuboid_triangle(
236        //         pos12, c1, t2, max_dist,
237        //     ))
238        } else if let (Some(t1), Some(c2)) = (shape1.as_triangle(), shape2.as_cuboid()) {
239            Ok(query::details::closest_points_triangle_cuboid(
240                pos12, t1, c2, max_dist,
241            ))
242        } else if let (Some(p1), Some(s2)) =
243            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
244        {
245            Ok(query::details::closest_points_halfspace_support_map(
246                pos12, p1, s2, max_dist,
247            ))
248        } else if let (Some(s1), Some(p2)) =
249            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
250        {
251            Ok(query::details::closest_points_support_map_halfspace(
252                pos12, s1, p2, max_dist,
253            ))
254        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
255            Ok(query::details::closest_points_support_map_support_map(
256                pos12, s1, s2, max_dist,
257            ))
258        } else {
259            #[cfg(feature = "alloc")]
260            if let Some(c1) = shape1.as_composite_shape() {
261                return Ok(query::details::closest_points_composite_shape_shape(
262                    self, pos12, c1, shape2, max_dist,
263                ));
264            } else if let Some(c2) = shape2.as_composite_shape() {
265                return Ok(query::details::closest_points_shape_composite_shape(
266                    self, pos12, shape1, c2, max_dist,
267                ));
268            }
269
270            Err(Unsupported)
271        }
272    }
273
274    fn cast_shapes(
275        &self,
276        pos12: &Isometry<Real>,
277        local_vel12: &Vector<Real>,
278        shape1: &dyn Shape,
279        shape2: &dyn Shape,
280        options: ShapeCastOptions,
281    ) -> Result<Option<ShapeCastHit>, Unsupported> {
282        if let (Some(b1), Some(b2)) = (shape1.as_ball(), shape2.as_ball()) {
283            Ok(query::details::cast_shapes_ball_ball(
284                pos12,
285                local_vel12,
286                b1,
287                b2,
288                options,
289            ))
290        } else if let (Some(p1), Some(s2)) =
291            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
292        {
293            Ok(query::details::cast_shapes_halfspace_support_map(
294                pos12,
295                local_vel12,
296                p1,
297                s2,
298                options,
299            ))
300        } else if let (Some(s1), Some(p2)) =
301            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
302        {
303            Ok(query::details::cast_shapes_support_map_halfspace(
304                pos12,
305                local_vel12,
306                s1,
307                p2,
308                options,
309            ))
310        } else {
311            #[cfg(feature = "alloc")]
312            if let Some(heightfield1) = shape1.as_heightfield() {
313                return query::details::cast_shapes_heightfield_shape(
314                    self,
315                    pos12,
316                    local_vel12,
317                    heightfield1,
318                    shape2,
319                    options,
320                );
321            } else if let Some(heightfield2) = shape2.as_heightfield() {
322                return query::details::cast_shapes_shape_heightfield(
323                    self,
324                    pos12,
325                    local_vel12,
326                    shape1,
327                    heightfield2,
328                    options,
329                );
330            } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map())
331            {
332                return Ok(query::details::cast_shapes_support_map_support_map(
333                    pos12,
334                    local_vel12,
335                    s1,
336                    s2,
337                    options,
338                ));
339            } else if let Some(c1) = shape1.as_composite_shape() {
340                return Ok(query::details::cast_shapes_composite_shape_shape(
341                    self,
342                    pos12,
343                    local_vel12,
344                    c1,
345                    shape2,
346                    options,
347                ));
348            } else if let Some(c2) = shape2.as_composite_shape() {
349                return Ok(query::details::cast_shapes_shape_composite_shape(
350                    self,
351                    pos12,
352                    local_vel12,
353                    shape1,
354                    c2,
355                    options,
356                ));
357            } else if let Some(v1) = shape1.as_voxels() {
358                return Ok(query::details::cast_shapes_voxels_shape(
359                    self,
360                    pos12,
361                    local_vel12,
362                    v1,
363                    shape2,
364                    options,
365                ));
366            } else if let Some(v2) = shape2.as_voxels() {
367                return Ok(query::details::cast_shapes_shape_voxels(
368                    self,
369                    pos12,
370                    local_vel12,
371                    shape1,
372                    v2,
373                    options,
374                ));
375            }
376
377            Err(Unsupported)
378        }
379    }
380
381    fn cast_shapes_nonlinear(
382        &self,
383        motion1: &NonlinearRigidMotion,
384        shape1: &dyn Shape,
385        motion2: &NonlinearRigidMotion,
386        shape2: &dyn Shape,
387        start_time: Real,
388        end_time: Real,
389        stop_at_penetration: bool,
390    ) -> Result<Option<ShapeCastHit>, Unsupported> {
391        if let (Some(sm1), Some(sm2)) = (shape1.as_support_map(), shape2.as_support_map()) {
392            let mode = if stop_at_penetration {
393                NonlinearShapeCastMode::StopAtPenetration
394            } else {
395                NonlinearShapeCastMode::directional_toi(shape1, shape2)
396            };
397
398            Ok(
399                query::details::cast_shapes_nonlinear_support_map_support_map(
400                    self, motion1, sm1, shape1, motion2, sm2, shape2, start_time, end_time, mode,
401                ),
402            )
403        } else {
404            #[cfg(feature = "alloc")]
405            if let Some(c1) = shape1.as_composite_shape() {
406                return Ok(query::details::cast_shapes_nonlinear_composite_shape_shape(
407                    self,
408                    motion1,
409                    c1,
410                    motion2,
411                    shape2,
412                    start_time,
413                    end_time,
414                    stop_at_penetration,
415                ));
416            } else if let Some(c2) = shape2.as_composite_shape() {
417                return Ok(query::details::cast_shapes_nonlinear_shape_composite_shape(
418                    self,
419                    motion1,
420                    shape1,
421                    motion2,
422                    c2,
423                    start_time,
424                    end_time,
425                    stop_at_penetration,
426                ));
427            } else if let Some(c1) = shape1.as_voxels() {
428                return Ok(query::details::cast_shapes_nonlinear_voxels_shape(
429                    self,
430                    motion1,
431                    c1,
432                    motion2,
433                    shape2,
434                    start_time,
435                    end_time,
436                    stop_at_penetration,
437                ));
438            } else if let Some(c2) = shape2.as_voxels() {
439                return Ok(query::details::cast_shapes_nonlinear_shape_voxels(
440                    self,
441                    motion1,
442                    shape1,
443                    motion2,
444                    c2,
445                    start_time,
446                    end_time,
447                    stop_at_penetration,
448                ));
449            }
450            /* } else if let (Some(p1), Some(s2)) = (shape1.as_shape::<HalfSpace>(), shape2.as_support_map()) {
451            //        query::details::cast_shapes_nonlinear_halfspace_support_map(m1, vel1, p1, m2, vel2, s2)
452                    unimplemented!()
453                } else if let (Some(s1), Some(p2)) = (shape1.as_support_map(), shape2.as_shape::<HalfSpace>()) {
454            //        query::details::cast_shapes_nonlinear_support_map_halfspace(m1, vel1, s1, m2, vel2, p2)
455                    unimplemented!() */
456
457            Err(Unsupported)
458        }
459    }
460}
461
462#[cfg(feature = "alloc")]
463impl<ManifoldData, ContactData> PersistentQueryDispatcher<ManifoldData, ContactData>
464    for DefaultQueryDispatcher
465where
466    ManifoldData: Default + Clone,
467    ContactData: Default + Copy,
468{
469    fn contact_manifolds(
470        &self,
471        pos12: &Isometry<Real>,
472        shape1: &dyn Shape,
473        shape2: &dyn Shape,
474        prediction: Real,
475        manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
476        workspace: &mut Option<ContactManifoldsWorkspace>,
477    ) -> Result<(), Unsupported> {
478        use crate::query::contact_manifolds::*;
479
480        let composite1 = shape1.as_composite_shape();
481        let composite2 = shape2.as_composite_shape();
482
483        if let (Some(composite1), Some(composite2)) = (composite1, composite2) {
484            contact_manifolds_composite_shape_composite_shape(
485                self, pos12, composite1, composite2, prediction, manifolds, workspace,
486            );
487
488            return Ok(());
489        }
490
491        match (shape1.shape_type(), shape2.shape_type()) {
492            (ShapeType::TriMesh, _) | (_, ShapeType::TriMesh) => {
493                contact_manifolds_trimesh_shape_shapes(
494                    self, pos12, shape1, shape2, prediction, manifolds, workspace,
495                );
496            }
497            (ShapeType::HeightField, _) => {
498                if let Some(composite2) = composite2 {
499                    contact_manifolds_heightfield_composite_shape(
500                        self,
501                        pos12,
502                        &pos12.inverse(),
503                        shape1.as_heightfield().unwrap(),
504                        composite2,
505                        prediction,
506                        manifolds,
507                        workspace,
508                        false,
509                    )
510                } else {
511                    contact_manifolds_heightfield_shape_shapes(
512                        self, pos12, shape1, shape2, prediction, manifolds, workspace,
513                    );
514                }
515            }
516            (_, ShapeType::HeightField) => {
517                if let Some(composite1) = composite1 {
518                    contact_manifolds_heightfield_composite_shape(
519                        self,
520                        &pos12.inverse(),
521                        pos12,
522                        shape2.as_heightfield().unwrap(),
523                        composite1,
524                        prediction,
525                        manifolds,
526                        workspace,
527                        true,
528                    )
529                } else {
530                    contact_manifolds_heightfield_shape_shapes(
531                        self, pos12, shape1, shape2, prediction, manifolds, workspace,
532                    );
533                }
534            }
535            (ShapeType::Voxels, ShapeType::Ball) | (ShapeType::Ball, ShapeType::Voxels) => {
536                contact_manifolds_voxels_ball_shapes(pos12, shape1, shape2, prediction, manifolds)
537            }
538            (ShapeType::Voxels, _) | (_, ShapeType::Voxels) => {
539                contact_manifolds_voxels_shape_shapes(
540                    self, pos12, shape1, shape2, prediction, manifolds, workspace,
541                )
542            }
543            _ => {
544                if let Some(composite1) = composite1 {
545                    contact_manifolds_composite_shape_shape(
546                        self, pos12, composite1, shape2, prediction, manifolds, workspace, false,
547                    );
548                } else if let Some(composite2) = composite2 {
549                    contact_manifolds_composite_shape_shape(
550                        self,
551                        &pos12.inverse(),
552                        composite2,
553                        shape1,
554                        prediction,
555                        manifolds,
556                        workspace,
557                        true,
558                    );
559                } else {
560                    if manifolds.is_empty() {
561                        manifolds.push(ContactManifold::new());
562                    }
563
564                    return self.contact_manifold_convex_convex(
565                        pos12,
566                        shape1,
567                        shape2,
568                        None,
569                        None,
570                        prediction,
571                        &mut manifolds[0],
572                    );
573                }
574            }
575        }
576
577        Ok(())
578    }
579
580    fn contact_manifold_convex_convex(
581        &self,
582        pos12: &Isometry<Real>,
583        shape1: &dyn Shape,
584        shape2: &dyn Shape,
585        normal_constraints1: Option<&dyn NormalConstraints>,
586        normal_constraints2: Option<&dyn NormalConstraints>,
587        prediction: Real,
588        manifold: &mut ContactManifold<ManifoldData, ContactData>,
589    ) -> Result<(), Unsupported> {
590        use crate::query::contact_manifolds::*;
591
592        match (shape1.shape_type(), shape2.shape_type()) {
593            (ShapeType::Ball, ShapeType::Ball) => {
594                contact_manifold_ball_ball_shapes(pos12, shape1, shape2, prediction, manifold)
595            }
596            (ShapeType::Cuboid, ShapeType::Cuboid) =>
597                contact_manifold_cuboid_cuboid_shapes(pos12, shape1, shape2, prediction, manifold)
598            ,
599            // (ShapeType::Polygon, ShapeType::Polygon) => (
600            //     PrimitiveContactGenerator {
601            //         generate_contacts: super::generate_contacts_polygon_polygon,
602            //         ..PrimitiveContactGenerator::default()
603            //     },
604            //     None,
605            // ),
606            (ShapeType::Capsule, ShapeType::Capsule) => {
607                contact_manifold_capsule_capsule_shapes(pos12, shape1, shape2, prediction, manifold)
608            }
609            (_, ShapeType::Ball) | (ShapeType::Ball, _) => {
610                contact_manifold_convex_ball_shapes(pos12, shape1, shape2, normal_constraints1, normal_constraints2, prediction, manifold)
611            }
612            // (ShapeType::Capsule, ShapeType::Cuboid) | (ShapeType::Cuboid, ShapeType::Capsule) =>
613            //     contact_manifold_cuboid_capsule_shapes(pos12, shape1, shape2, prediction, manifold),
614            (ShapeType::Triangle, ShapeType::Cuboid) | (ShapeType::Cuboid, ShapeType::Triangle) => {
615                contact_manifold_cuboid_triangle_shapes(pos12, shape1, shape2, normal_constraints1, normal_constraints2,  prediction, manifold)
616            }
617            (ShapeType::HalfSpace, _) => {
618                if let Some((pfm2, border_radius2)) = shape2.as_polygonal_feature_map() {
619                    contact_manifold_halfspace_pfm(
620                        pos12,
621                        shape1.as_halfspace().unwrap(),
622                        pfm2,
623                        border_radius2,
624                        prediction,
625                        manifold,
626                        false
627                    )
628                } else {
629                    return Err(Unsupported)
630                }
631            }
632            (_, ShapeType::HalfSpace) => {
633                if let Some((pfm1, border_radius1)) = shape1.as_polygonal_feature_map() {
634                    contact_manifold_halfspace_pfm(
635                        &pos12.inverse(),
636                        shape2.as_halfspace().unwrap(),
637                        pfm1,
638                        border_radius1,
639                        prediction,
640                        manifold,
641                        true
642                    )
643                } else {
644                    return Err(Unsupported)
645                }
646            }
647            _ => {
648                if let (Some(pfm1), Some(pfm2)) = (
649                    shape1.as_polygonal_feature_map(),
650                    shape2.as_polygonal_feature_map(),
651                ) {
652                    contact_manifold_pfm_pfm(
653                        pos12, pfm1.0, pfm1.1, normal_constraints1, pfm2.0, pfm2.1, normal_constraints2, prediction, manifold,
654                    )
655                } else {
656                    return Err(Unsupported);
657                }
658            }
659        }
660
661        Ok(())
662    }
663}