Skip to main content

bevy_rapier2d/plugin/
narrow_phase.rs

1use crate::math::{Real, Vect};
2use crate::plugin::context::{RapierContextColliders, RapierContextSimulation, RapierRigidBodySet};
3use crate::utils::iso_to_transform;
4use bevy::prelude::*;
5use rapier::geometry::{
6    ColliderHandle, Contact, ContactManifold, ContactManifoldData, ContactPair, InteractionGraph,
7    IntersectionPair, PairContacts, SoftPairContacts, SolverContact, SolverFlags, NEW_CONTACT_BIT,
8};
9
10impl RapierContextSimulation {
11    /// All the contact pairs involving the non-sensor collider attached to the given entity.
12    ///
13    /// The returned contact pairs identify pairs of colliders with intersecting bounding-volumes.
14    /// To check if any geometric contact happened between the collider shapes, check
15    /// [`ContactPairView::has_any_active_contact`].
16    pub fn contact_pairs_with<'a, 'b: 'a>(
17        &'a self,
18        context_colliders: &'b RapierContextColliders,
19        rigidbody_set: &'b RapierRigidBodySet,
20        collider: Entity,
21    ) -> impl Iterator<Item = ContactPairView<'a>> {
22        context_colliders
23            .entity2collider
24            .get(&collider)
25            .into_iter()
26            .flat_map(|h| {
27                self.narrow_phase
28                    .contact_pairs_with(*h)
29                    .map(|raw| ContactPairView {
30                        context_colliders,
31                        rigidbody_set,
32                        raw,
33                    })
34            })
35    }
36
37    /// All the intersection pairs involving the collider attached to the given entity, where at least one collider
38    /// involved in the intersection is a sensor.
39    ///
40    /// The returned contact pairs identify pairs of colliders (where at least one is a sensor) with
41    /// intersecting bounding-volumes. To check if any geometric overlap happened between the collider shapes, check
42    /// the returned boolean.
43    pub fn intersection_pairs_with<'a, 'b: 'a>(
44        &'a self,
45        rapier_colliders: &'b RapierContextColliders,
46        collider: Entity,
47    ) -> impl Iterator<Item = (Entity, Entity, bool)> + 'a {
48        rapier_colliders
49            .entity2collider
50            .get(&collider)
51            .into_iter()
52            .flat_map(|h| {
53                self.narrow_phase
54                    .intersection_pairs_with(*h)
55                    .filter_map(|(h1, h2, inter)| {
56                        let e1 = rapier_colliders.collider_entity(h1);
57                        let e2 = rapier_colliders.collider_entity(h2);
58                        match (e1, e2) {
59                            (Some(e1), Some(e2)) => Some((e1, e2, inter)),
60                            _ => None,
61                        }
62                    })
63            })
64    }
65
66    /// The contact pair involving two specific colliders.
67    ///
68    /// If this returns `None`, there is no contact between the two colliders.
69    /// If this returns `Some`, then there may be a contact between the two colliders. Check the
70    /// result [`ContactPairView::has_any_active_contact`] method to see if there is an actual contact.
71    pub fn contact_pair<'a, 'b: 'a>(
72        &'a self,
73        context_colliders: &'b RapierContextColliders,
74        rigidbody_set: &'b RapierRigidBodySet,
75        collider1: Entity,
76        collider2: Entity,
77    ) -> Option<ContactPairView<'a>> {
78        let h1 = context_colliders.entity2collider.get(&collider1)?;
79        let h2 = context_colliders.entity2collider.get(&collider2)?;
80        self.narrow_phase
81            .contact_pair(*h1, *h2)
82            .map(|raw| ContactPairView {
83                context_colliders,
84                rigidbody_set,
85                raw,
86            })
87    }
88
89    /// The intersection pair involving two specific colliders (at least one being a sensor).
90    ///
91    /// If this returns `None` or `Some(false)`, then there is no intersection between the two colliders.
92    /// If this returns `Some(true)`, then there may be an intersection between the two colliders.
93    pub fn intersection_pair(
94        &self,
95        rapier_colliders: &RapierContextColliders,
96        collider1: Entity,
97        collider2: Entity,
98    ) -> Option<bool> {
99        let h1 = rapier_colliders.entity2collider.get(&collider1)?;
100        let h2 = rapier_colliders.entity2collider.get(&collider2)?;
101        self.narrow_phase.intersection_pair(*h1, *h2)
102    }
103
104    /// All the contact pairs detected during the last timestep.
105    pub fn contact_pairs<'a, 'b: 'a>(
106        &'a self,
107        context_colliders: &'b RapierContextColliders,
108        rigidbody_set: &'b RapierRigidBodySet,
109    ) -> impl Iterator<Item = ContactPairView<'a>> {
110        self.narrow_phase
111            .contact_pairs()
112            .map(|raw| ContactPairView {
113                context_colliders,
114                rigidbody_set,
115                raw,
116            })
117    }
118
119    /// The raw contact graph of the narrow-phase.
120    ///
121    /// Its nodes are the Rapier handles of the colliders, and its edges the raw contact pairs
122    /// between non-sensor colliders with intersecting bounding volumes. Prefer
123    /// [`Self::contact_pairs`] and [`Self::contact_pairs_with`] which yield Bevy entities.
124    pub fn contact_graph(&self) -> &InteractionGraph<ColliderHandle, ContactPair> {
125        self.narrow_phase.contact_graph()
126    }
127
128    /// The raw intersection graph of the narrow-phase.
129    ///
130    /// Its nodes are the Rapier handles of the colliders, and its edges the raw intersection
131    /// pairs involving at least one sensor. Prefer [`Self::intersection_pairs`] and
132    /// [`Self::intersection_pairs_with`] which yield Bevy entities.
133    pub fn intersection_graph(&self) -> &InteractionGraph<ColliderHandle, IntersectionPair> {
134        self.narrow_phase.intersection_graph()
135    }
136
137    /// All the intersection pairs detected during the last timestep.
138    pub fn intersection_pairs<'a, 'b: 'a>(
139        &'a self,
140        rapier_colliders: &'b RapierContextColliders,
141    ) -> impl Iterator<Item = (Entity, Entity, bool)> + 'a {
142        self.narrow_phase
143            .intersection_pairs()
144            .filter_map(|(h1, h2, inter)| {
145                let e1 = rapier_colliders.collider_entity(h1);
146                let e2 = rapier_colliders.collider_entity(h2);
147                match (e1, e2) {
148                    (Some(e1), Some(e2)) => Some((e1, e2, inter)),
149                    _ => None,
150                }
151            })
152    }
153}
154
155/// Read-only access to the properties of a contact manifold.
156pub struct ContactManifoldView<'a> {
157    rigidbody_set: &'a RapierRigidBodySet,
158    /// The raw contact manifold from Rapier.
159    pub raw: &'a ContactManifold,
160}
161
162impl ContactManifoldView<'_> {
163    /// The number of points on this contact manifold.
164    pub fn num_points(&self) -> usize {
165        self.raw.points.len()
166    }
167
168    /// Retrieves the i-th point of this contact manifold.
169    pub fn point(&self, i: usize) -> Option<ContactView<'_>> {
170        self.raw.points.get(i).map(|raw| ContactView { raw })
171    }
172
173    /// The contacts points.
174    pub fn points(&self) -> impl ExactSizeIterator<Item = ContactView<'_>> {
175        self.raw.points.iter().map(|raw| ContactView { raw })
176    }
177
178    /// The contact normal of all the contacts of this manifold, expressed in the local space of the first shape.
179    pub fn local_n1(&self) -> Vect {
180        self.raw.local_n1
181    }
182
183    /// The contact normal of all the contacts of this manifold, expressed in the local space of the second shape.
184    pub fn local_n2(&self) -> Vect {
185        self.raw.local_n2
186    }
187
188    /// The first subshape involved in this contact manifold.
189    ///
190    /// This is zero if the first shape is not a composite shape.
191    pub fn subshape1(&self) -> u32 {
192        self.raw.subshape1
193    }
194
195    /// The second subshape involved in this contact manifold.
196    ///
197    /// This is zero if the second shape is not a composite shape.
198    pub fn subshape2(&self) -> u32 {
199        self.raw.subshape2
200    }
201
202    /// The first rigid-body involved in this contact manifold.
203    pub fn rigid_body1(&self) -> Option<Entity> {
204        self.raw
205            .data
206            .rigid_body1
207            .and_then(|h| self.rigidbody_set.rigid_body_entity(h))
208    }
209
210    /// The second rigid-body involved in this contact manifold.
211    pub fn rigid_body2(&self) -> Option<Entity> {
212        self.raw
213            .data
214            .rigid_body2
215            .and_then(|h| self.rigidbody_set.rigid_body_entity(h))
216    }
217
218    /// Flags used to control some aspects of the constraints solver for this contact manifold.
219    pub fn solver_flags(&self) -> SolverFlags {
220        self.raw.data.solver_flags
221    }
222
223    /// The world-space contact normal shared by all the contact in this contact manifold.
224    pub fn normal(&self) -> Vect {
225        self.raw.data.normal
226    }
227
228    /// The contacts that will be seen by the constraints solver for computing forces.
229    pub fn num_solver_contacts(&self) -> usize {
230        self.raw.data.solver_contacts.len()
231    }
232
233    /// Gets the i-th solver contact.
234    pub fn solver_contact(&self, i: usize) -> Option<SolverContactView<'_>> {
235        self.raw
236            .data
237            .solver_contacts
238            .get(i)
239            .map(|raw| SolverContactView {
240                rigidbody_set: self.rigidbody_set,
241                manifold_data: &self.raw.data,
242                raw,
243            })
244    }
245
246    /// The contacts that will be seen by the constraints solver for computing forces.
247    pub fn solver_contacts(&self) -> impl ExactSizeIterator<Item = SolverContactView<'_>> {
248        self.raw
249            .data
250            .solver_contacts
251            .iter()
252            .map(|raw| SolverContactView {
253                rigidbody_set: self.rigidbody_set,
254                manifold_data: &self.raw.data,
255                raw,
256            })
257    }
258
259    /// The relative dominance of the bodies involved in this contact manifold.
260    pub fn relative_dominance(&self) -> i16 {
261        self.raw.data.relative_dominance
262    }
263
264    /// A user-defined piece of data.
265    pub fn user_data(&self) -> u32 {
266        self.raw.data.user_data
267    }
268
269    /// The effective friction coefficient of this manifold's contacts, combined from both
270    /// colliders' materials (or as set by a contact modification hook).
271    pub fn friction(&self) -> Real {
272        self.raw.data.friction
273    }
274
275    /// The effective restitution coefficient of this manifold's contacts, combined from both
276    /// colliders' materials (or as set by a contact modification hook).
277    pub fn restitution(&self) -> Real {
278        self.raw.data.restitution
279    }
280
281    /// The pose of the first shape's subshape involved in this manifold, relative to the first
282    /// collider, if the first shape is a composite shape.
283    pub fn subshape_pose1(&self) -> Option<Transform> {
284        self.raw.subshape_pos1().map(iso_to_transform)
285    }
286
287    /// The pose of the second shape's subshape involved in this manifold, relative to the second
288    /// collider, if the second shape is a composite shape.
289    pub fn subshape_pose2(&self) -> Option<Transform> {
290        self.raw.subshape_pos2().map(iso_to_transform)
291    }
292}
293
294impl ContactManifoldView<'_> {
295    /// Returns the contact with the smallest distance (i.e. the largest penetration depth).
296    pub fn find_deepest_contact(&self) -> Option<ContactView<'_>> {
297        self.raw
298            .find_deepest_contact()
299            .map(|raw| ContactView { raw })
300    }
301}
302
303/// Read-only access to the properties of a single contact.
304pub struct ContactView<'a> {
305    /// The raw contact from Rapier.
306    pub raw: &'a Contact,
307}
308
309impl ContactView<'_> {
310    /// The contact point in the local-space of the first shape.
311    pub fn local_p1(&self) -> Vect {
312        self.raw.local_p1
313    }
314
315    /// The contact point in the local-space of the second shape.
316    pub fn local_p2(&self) -> Vect {
317        self.raw.local_p2
318    }
319
320    /// The distance between the two contact points.
321    pub fn dist(&self) -> Real {
322        self.raw.dist
323    }
324
325    /// The feature ID of the first shape involved in the contact.
326    pub fn fid1(&self) -> u32 {
327        self.raw.fid1.0
328    }
329
330    /// The feature ID of the second shape involved in the contact.
331    pub fn fid2(&self) -> u32 {
332        self.raw.fid2.0
333    }
334
335    /// The impulse, along the contact normal, applied by this contact to the first collider's rigid-body.
336    ///
337    /// The impulse applied to the second collider's rigid-body is given by `-impulse`.
338    pub fn impulse(&self) -> Real {
339        self.raw.data.impulse
340    }
341
342    /// The friction impulse along the vector orthonormal to the contact normal, applied to the first
343    /// collider's rigid-body.
344    #[cfg(feature = "dim2")]
345    pub fn tangent_impulse(&self) -> Real {
346        self.raw.data.tangent_impulse.x
347    }
348
349    /// The friction impulse along the vector orthonormal to the contact normal, applied to the first
350    /// collider's rigid-body.
351    #[cfg(feature = "dim3")]
352    pub fn tangent_impulse(&self) -> [Real; 2] {
353        self.raw.data.tangent_impulse.into()
354    }
355
356    /// The normal impulse retained for warmstarting the next simulation step.
357    pub fn warmstart_impulse(&self) -> Real {
358        self.raw.data.warmstart_impulse
359    }
360
361    /// The friction impulse retained for warmstarting the next simulation step.
362    #[cfg(feature = "dim2")]
363    pub fn warmstart_tangent_impulse(&self) -> Real {
364        self.raw.data.warmstart_tangent_impulse.x
365    }
366
367    /// The friction impulse retained for warmstarting the next simulation step.
368    #[cfg(feature = "dim3")]
369    pub fn warmstart_tangent_impulse(&self) -> [Real; 2] {
370        self.raw.data.warmstart_tangent_impulse.into()
371    }
372
373    /// The twist (rotational friction) impulse retained for warmstarting the next simulation step.
374    #[cfg(feature = "dim3")]
375    pub fn warmstart_twist_impulse(&self) -> Real {
376        self.raw.data.warmstart_twist_impulse
377    }
378}
379
380/// Read-only access to the properties of a single solver contact.
381pub struct SolverContactView<'a> {
382    rigidbody_set: &'a RapierRigidBodySet,
383    /// The data of the contact manifold this solver contact is part of.
384    pub manifold_data: &'a ContactManifoldData,
385    /// The raw solver contact from Rapier.
386    pub raw: &'a SolverContact,
387}
388
389impl SolverContactView<'_> {
390    /// The world-space contact point.
391    pub fn point(&self) -> Vect {
392        let (p1, p2) = self
393            .manifold_data
394            .solver_contact_world_points(self.raw, &self.rigidbody_set.bodies);
395        (p1 + p2) / 2.0
396    }
397    /// The distance between the two original contacts points along the contact normal.
398    /// If negative, this is measures the penetration depth.
399    pub fn dist(&self) -> Real {
400        self.raw.dist
401    }
402    /// The effective friction coefficient at this contact point.
403    pub fn friction(&self) -> Real {
404        self.manifold_data.friction
405    }
406    /// The effective restitution coefficient at this contact point.
407    pub fn restitution(&self) -> Real {
408        self.manifold_data.restitution
409    }
410    /// The desired tangent relative velocity at the contact point.
411    ///
412    /// This is set to zero by default. Set to a non-zero value to
413    /// simulate, e.g., conveyor belts.
414    pub fn tangent_velocity(&self) -> Vect {
415        self.raw.tangent_velocity
416    }
417    /// Whether or not this contact existed during the last timestep.
418    pub fn is_new(&self) -> bool {
419        self.raw.contact_id[0] & NEW_CONTACT_BIT != 0
420    }
421    /// The index, in its manifold's contact points, of the contact this solver contact was
422    /// generated from.
423    ///
424    /// This index is only valid for the timestep that produced this solver contact.
425    pub fn contact_id(&self) -> u32 {
426        self.raw.contact_indices()[0]
427    }
428    /// The raw contact point on the first body's surface, as stored for the solver.
429    ///
430    /// This is expressed in the center-of-mass-centered local frame of the first rigid-body, or in
431    /// world-space if that side is attached to the world (no rigid-body, or a fixed one). Use
432    /// [`Self::world_point1`] for a world-space point.
433    pub fn anchor1(&self) -> Vect {
434        self.raw.anchor1
435    }
436    /// The raw contact point on the second body's surface, as stored for the solver.
437    ///
438    /// See [`Self::anchor1`] for the frame it is expressed in.
439    pub fn anchor2(&self) -> Vect {
440        self.raw.anchor2
441    }
442    /// The world-space contact point on the first body's surface.
443    pub fn world_point1(&self) -> Vect {
444        self.manifold_data
445            .solver_contact_world_points(self.raw, &self.rigidbody_set.bodies)
446            .0
447    }
448    /// The world-space contact point on the second body's surface.
449    pub fn world_point2(&self) -> Vect {
450        self.manifold_data
451            .solver_contact_world_points(self.raw, &self.rigidbody_set.bodies)
452            .1
453    }
454}
455
456/// Read-only access to the properties of a contact pair.
457pub struct ContactPairView<'a> {
458    context_colliders: &'a RapierContextColliders,
459    rigidbody_set: &'a RapierRigidBodySet,
460    /// The raw contact pair from Rapier.
461    pub raw: &'a ContactPair,
462}
463
464impl ContactPairView<'_> {
465    /// The first collider involved in this contact pair.
466    pub fn collider1(&self) -> Option<Entity> {
467        self.context_colliders.collider_entity(self.raw.collider1)
468    }
469
470    /// The second collider involved in this contact pair.
471    pub fn collider2(&self) -> Option<Entity> {
472        self.context_colliders.collider_entity(self.raw.collider2)
473    }
474
475    /// The number of contact manifolds detected for this contact pair.
476    pub fn manifolds_len(&self) -> usize {
477        self.raw.manifolds().len()
478    }
479
480    /// Gets the i-th contact manifold.
481    pub fn manifold(&self, i: usize) -> Option<ContactManifoldView<'_>> {
482        self.raw.manifolds().get(i).map(|raw| ContactManifoldView {
483            rigidbody_set: self.rigidbody_set,
484            raw,
485        })
486    }
487
488    /// Iterate through all the contact manifolds of this contact pair.
489    pub fn manifolds(&self) -> impl ExactSizeIterator<Item = ContactManifoldView<'_>> {
490        self.raw.manifolds().iter().map(|raw| ContactManifoldView {
491            rigidbody_set: self.rigidbody_set,
492            raw,
493        })
494    }
495
496    /// Is there any active contact in this contact pair?
497    pub fn has_any_active_contact(&self) -> bool {
498        self.raw.has_any_active_contact()
499    }
500
501    /// Does this pair hold contact manifolds (every pair except two soft surfaces)?
502    pub fn is_rigid(&self) -> bool {
503        self.raw.rigid().is_some()
504    }
505
506    /// Is this a pair of two soft surfaces, whose contacts are candidates instead of manifolds?
507    ///
508    /// Such pairs have no manifolds; their candidates can be read through
509    /// [`Self::soft_contacts`].
510    pub fn is_soft(&self) -> bool {
511        self.raw.soft().is_some()
512    }
513
514    /// The contacts of this pair: manifolds, or the contact candidates of two soft surfaces.
515    pub fn contacts(&self) -> &PairContacts {
516        &self.raw.contacts
517    }
518
519    /// The contact candidates of a pair of two soft surfaces (`None` for other pairs).
520    pub fn soft_contacts(&self) -> Option<&SoftPairContacts> {
521        self.raw.soft()
522    }
523
524    /// Whether the two soft surfaces of this pair touch: some feature of one surface is within
525    /// contact reach of the other, or the two surfaces cross (`None` for other pairs).
526    pub fn soft_touching(&self) -> Option<bool> {
527        match &self.raw.contacts {
528            PairContacts::Soft { touching, .. } => Some(*touching),
529            PairContacts::Rigid(_) => None,
530        }
531    }
532
533    /// The number of manifolds seen by the constraints solver, see [`Self::solver_manifolds`].
534    pub fn solver_manifolds_len(&self) -> usize {
535        self.raw.solver_manifolds().len()
536    }
537
538    /// Iterate through the contact manifolds actually seen by the constraints solver.
539    ///
540    /// These are the contact clusters if contact clustering applied to this pair (merging the
541    /// manifolds sharing nearly the same normal), and the plain [`Self::manifolds`] otherwise.
542    /// The contact impulses applied by the solver are stored in these manifolds.
543    pub fn solver_manifolds(&self) -> impl ExactSizeIterator<Item = ContactManifoldView<'_>> {
544        self.raw
545            .solver_manifolds()
546            .iter()
547            .map(|raw| ContactManifoldView {
548                rigidbody_set: self.rigidbody_set,
549                raw,
550            })
551    }
552
553    /// The sum of all the contact impulses applied by the solver to the first collider during
554    /// the last timestep.
555    ///
556    /// Divide by the timestep length to obtain a force.
557    pub fn total_impulse(&self) -> Vect {
558        self.raw.total_impulse()
559    }
560
561    /// The sum of the magnitudes of all the contact impulses applied during the last timestep.
562    ///
563    /// This is not the magnitude of [`Self::total_impulse`]; it is the quantity compared against
564    /// the contact force event threshold (after conversion to a force).
565    pub fn total_impulse_magnitude(&self) -> Real {
566        self.raw.total_impulse_magnitude()
567    }
568
569    /// The magnitude and world-space direction of the strongest contact impulse applied during
570    /// the last timestep.
571    pub fn max_impulse(&self) -> (Real, Vect) {
572        self.raw.max_impulse()
573    }
574
575    /// Finds the contact with the smallest signed distance.
576    ///
577    /// If the colliders involved in this contact pair are penetrating, then
578    /// this returns the contact with the largest penetration depth.
579    ///
580    /// Returns a reference to the contact, as well as the contact manifold
581    /// it is part of.
582    pub fn find_deepest_contact(&self) -> Option<(ContactManifoldView<'_>, ContactView<'_>)> {
583        self.raw.find_deepest_contact().map(|(manifold, contact)| {
584            (
585                ContactManifoldView {
586                    rigidbody_set: self.rigidbody_set,
587                    raw: manifold,
588                },
589                ContactView { raw: contact },
590            )
591        })
592    }
593}
594
595#[cfg(test)]
596mod test {
597    use crate::math::Real;
598    use crate::prelude::*;
599    use bevy::{
600        prelude::*,
601        time::{TimePlugin, TimeUpdateStrategy},
602    };
603
604    #[cfg(feature = "dim3")]
605    fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
606        Collider::cuboid(hx, hy, hz)
607    }
608    #[cfg(feature = "dim2")]
609    fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
610        Collider::cuboid(hx, hy)
611    }
612
613    #[test]
614    fn resting_box_contact_impulses() {
615        let mut app = App::new();
616        app.add_plugins((
617            TransformPlugin,
618            TimePlugin,
619            RapierPhysicsPlugin::<NoUserData>::default(),
620        ))
621        .insert_resource(TimeUpdateStrategy::ManualDuration(
622            std::time::Duration::from_secs_f32(1f32 / 60f32),
623        ));
624        let ground = app
625            .world_mut()
626            .spawn((Transform::from_xyz(0.0, -1.0, 0.0), cuboid(4.0, 1.0, 4.0)))
627            .id();
628        let body = app
629            .world_mut()
630            .spawn((
631                Transform::from_xyz(0.0, 0.6, 0.0),
632                RigidBody::Dynamic,
633                cuboid(0.5, 0.5, 0.5),
634                // Keep the box awake so the impulses are refreshed at every step.
635                Sleeping::disabled(),
636            ))
637            .id();
638        app.finish();
639        for _ in 0..60 {
640            app.update();
641        }
642
643        let mut query = app.world_mut().query::<(
644            &RapierContextSimulation,
645            &RapierContextColliders,
646            &RapierRigidBodySet,
647        )>();
648        let (simulation, colliders, bodies) = query.single(app.world()).unwrap();
649        let pair = simulation
650            .contact_pair(colliders, bodies, ground, body)
651            .unwrap();
652        assert!(pair.is_rigid());
653        assert!(!pair.is_soft());
654        assert!(pair.has_any_active_contact());
655        assert!(pair.solver_manifolds_len() > 0);
656        assert!(pair.total_impulse_magnitude() > 0.0);
657        assert!(pair.total_impulse().length() > 0.0);
658        let (max_impulse, max_dir) = pair.max_impulse();
659        assert!(max_impulse > 0.0);
660        assert!(max_dir.y.abs() > 0.9);
661
662        let manifold = pair.solver_manifolds().next().unwrap();
663        assert!(manifold.friction() > 0.0);
664        assert!(manifold.subshape_pose1().is_none());
665        assert!(manifold.points().any(|c| c.warmstart_impulse() > 0.0));
666        for solver_contact in manifold.solver_contacts() {
667            assert!((solver_contact.contact_id() as usize) < manifold.num_points());
668            let midpoint = (solver_contact.world_point1() + solver_contact.world_point2()) / 2.0;
669            assert!((midpoint - solver_contact.point()).length() < 1.0e-5);
670        }
671
672        assert!(simulation.contact_graph().interactions().count() > 0);
673        assert_eq!(simulation.intersection_graph().interactions().count(), 0);
674    }
675}