Skip to main content

rapier2d/geometry/
contact_pair.rs

1use super::CollisionEvent;
2use crate::alloc_prelude::*;
3use crate::dynamics::{RigidBodyHandle, RigidBodySet};
4use crate::geometry::soft_contacts::{SoftPairContacts, SoftRigidContacts};
5use crate::geometry::{ColliderHandle, ColliderSet, Contact, ContactManifold};
6use crate::math::{Pose, Real, TangentImpulse, Vector};
7use crate::pipeline::EventHandler;
8use crate::prelude::CollisionEventFlags;
9use crate::utils::ScalarType;
10use crate::utils::SolverBlock;
11use parry::math::{SIMD_WIDTH, SimdReal};
12use parry::query::ContactManifoldsWorkspace;
13// Only `relative_pose_drift`’s 2D branch needs the no-std float methods.
14#[cfg(all(not(feature = "std"), feature = "dim2"))]
15use simba::scalar::ComplexField as _;
16
17bitflags::bitflags! {
18    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
19    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
20    /// Flags affecting the behavior of the constraints solver for a given contact manifold.
21    pub struct SolverFlags: u32 {
22        /// The constraint solver will take this contact manifold into
23        /// account for force computation between two rigid colliders.
24        const COMPUTE_RIGID_IMPULSES = 0b001;
25        // TODO: add a COMPUTE_SOFT_IMPULSES flag too?
26    }
27}
28
29impl Default for SolverFlags {
30    fn default() -> Self {
31        SolverFlags::COMPUTE_RIGID_IMPULSES
32    }
33}
34
35bitflags::bitflags! {
36    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
37    #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
38    /// Event bookkeeping bits of a contact pair.
39    ///
40    /// Serialized as a single byte with the same values as the `start_event_emitted`
41    /// bool it replaces, so snapshots keep their exact byte layout.
42    pub(crate) struct PairEventStatus: u8 {
43        /// A `CollisionEvent::Started` was emitted for this pair.
44        const START_EVENT_EMITTED = 0b01;
45        /// The pair's total contact force exceeded its force-event threshold at the
46        /// previous step. [`ContactForceEvent::started`] is derived from it, and it
47        /// resets when the force drops back below the threshold or the pair stops
48        /// touching.
49        const INITIAL_FORCE_THRESHOLD_EVENT_EMITTED = 0b10;
50    }
51}
52
53#[derive(Copy, Clone, Debug)]
54#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
55/// A single contact between two collider.
56pub struct ContactData {
57    /// The impulse, along the contact normal, applied by this contact to the first collider's rigid-body.
58    ///
59    /// The impulse applied to the second collider's rigid-body is given by `-impulse`.
60    pub impulse: Real,
61    /// The friction impulse along the vector orthonormal to the contact normal, applied to the first
62    /// collider's rigid-body.
63    pub tangent_impulse: TangentImpulse<Real>,
64    /// The impulse retained for warmstarting the next simulation step.
65    pub warmstart_impulse: Real,
66    /// The friction impulse retained for warmstarting the next simulation step.
67    pub warmstart_tangent_impulse: TangentImpulse<Real>,
68    /// The twist impulse retained for warmstarting the next simulation step.
69    #[cfg(feature = "dim3")]
70    pub warmstart_twist_impulse: Real,
71    /// The friction warm-start impulse as a **world-space** vector — the canonical
72    /// value 3D friction warm-starts from,
73    /// projected onto the constraint's current tangent basis at constraint generation.
74    /// Warm-starting from the raw [`Self::warmstart_tangent_impulse`] components would
75    /// silently rotate the friction force whenever the basis changes, kicking resting stacks.
76    #[cfg(feature = "dim3")]
77    #[cfg_attr(feature = "serde-serialize", serde(default))]
78    pub warmstart_tangent_world: Vector,
79    /// The solver's lever arm for the first body: contact point relative to the body's CoM,
80    /// in **world space**, frozen at the pair's last full narrow-phase update (anchor
81    /// freezing) and used verbatim while recycled. Load-bearing for tall-stack stability:
82    /// re-linearizing the arms every step under heavy warm-started impulses is a state-
83    /// proportional energy pump (lean mode). Separations still track the bodies' rigid motion.
84    #[cfg_attr(feature = "serde-serialize", serde(default))]
85    pub solver_dp1: Vector,
86    /// The solver's lever arm for the second body (see [`Self::solver_dp1`]).
87    #[cfg_attr(feature = "serde-serialize", serde(default))]
88    pub solver_dp2: Vector,
89}
90
91impl Default for ContactData {
92    fn default() -> Self {
93        Self {
94            impulse: 0.0,
95            tangent_impulse: na::zero(),
96            warmstart_impulse: 0.0,
97            warmstart_tangent_impulse: na::zero(),
98            #[cfg(feature = "dim3")]
99            warmstart_twist_impulse: 0.0,
100            #[cfg(feature = "dim3")]
101            warmstart_tangent_world: Vector::ZERO,
102            solver_dp1: Vector::ZERO,
103            solver_dp2: Vector::ZERO,
104        }
105    }
106}
107
108#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
109#[derive(Copy, Clone, Debug)]
110/// The description of all the contacts between a pair of colliders.
111pub struct IntersectionPair {
112    /// Are the colliders intersecting?
113    pub intersecting: bool,
114    /// Was a `CollisionEvent::Started` emitted for this collider?
115    pub(crate) start_event_emitted: bool,
116}
117
118impl IntersectionPair {
119    pub(crate) fn new() -> Self {
120        Self {
121            intersecting: false,
122            start_event_emitted: false,
123        }
124    }
125
126    pub(crate) fn emit_start_event(
127        &mut self,
128        bodies: &RigidBodySet,
129        colliders: &ColliderSet,
130        collider1: ColliderHandle,
131        collider2: ColliderHandle,
132        events: &dyn EventHandler,
133    ) {
134        self.start_event_emitted = true;
135        events.handle_collision_event(
136            bodies,
137            colliders,
138            CollisionEvent::Started(collider1, collider2, CollisionEventFlags::SENSOR),
139            None,
140        );
141    }
142
143    pub(crate) fn emit_stop_event(
144        &mut self,
145        bodies: &RigidBodySet,
146        colliders: &ColliderSet,
147        collider1: ColliderHandle,
148        collider2: ColliderHandle,
149        events: &dyn EventHandler,
150    ) {
151        self.start_event_emitted = false;
152        events.handle_collision_event(
153            bodies,
154            colliders,
155            CollisionEvent::Stopped(collider1, collider2, CollisionEventFlags::SENSOR),
156            None,
157        );
158    }
159}
160
161/// Sentinel color for pairs currently holding no solver graph color.
162pub(crate) const SOLVER_COLOR_UNCOLORED: u8 = u8::MAX;
163/// Color assigned when the parallel color space is exhausted (or for extra manifolds
164/// of multi-manifold pairs); such constraints are solved sequentially.
165pub(crate) const SOLVER_COLOR_OVERFLOW: u8 = 128;
166/// Number of low colors dynamic-vs-dynamic contacts may use; `..128` is reserved for
167/// dynamic-vs-fixed so those always iterate last, giving
168/// fixed geometry the final say each sweep and reducing push-through of piled bodies.
169pub(crate) const SOLVER_DYNAMIC_COLOR_COUNT: u32 = 120;
170
171#[cfg(feature = "serde-serialize")]
172fn default_solver_color() -> u8 {
173    SOLVER_COLOR_UNCOLORED
174}
175#[cfg(feature = "serde-serialize")]
176fn default_solver_color_bodies() -> [u32; 2] {
177    [u32::MAX; 2]
178}
179
180#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
181#[derive(Clone)]
182/// All contact information between two colliding colliders.
183///
184/// When two colliders are touching, a ContactPair stores all the contact points, normals,
185/// and forces between them. You can access this through the narrow phase or in event handlers.
186///
187/// ## Contact manifolds
188///
189/// The contacts are organized into "manifolds" - groups of contact points that share similar
190/// properties (like being on the same face). Most collider pairs have 1 manifold, but complex
191/// shapes may have multiple.
192///
193/// ## Use cases
194///
195/// - Reading contact normals for custom physics
196/// - Checking penetration depth
197/// - Analyzing impact forces
198/// - Implementing custom contact responses
199///
200/// # Example
201/// ```
202/// # use rapier3d::prelude::*;
203/// # use rapier3d::geometry::ContactPair;
204/// # let contact_pair = ContactPair::default();
205/// if let Some((manifold, contact)) = contact_pair.find_deepest_contact() {
206///     println!("Deepest penetration: {}", -contact.dist);
207///     println!("Contact normal: {:?}", manifold.data.normal);
208/// }
209/// ```
210pub struct ContactPair {
211    /// The first collider involved in the contact pair.
212    pub collider1: ColliderHandle,
213    /// The second collider involved in the contact pair.
214    pub collider2: ColliderHandle,
215    /// Event bookkeeping: `CollisionEvent::Started` emission and force-event
216    /// threshold status.
217    pub(crate) event_status: PairEventStatus,
218    /// The pair's contacts, in the form the two colliders call for: contact manifolds, or
219    /// the contact candidates of two soft surfaces.
220    pub contacts: PairContacts,
221}
222
223/// The contacts of a [`ContactPair`], with a fixed layout: the solver addresses a pair's manifolds
224/// through raw pointers (see `ManifoldStore`), so the offset of the rigid contacts must not depend
225/// on the value.
226#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
227#[derive(Clone)]
228#[repr(C, u8)]
229#[allow(clippy::large_enum_variant)] // Boxing would move the manifolds off the fixed layout.
230pub enum PairContacts {
231    /// Contact manifolds: every pair but the pairs of two soft surfaces.
232    Rigid(RigidPairContacts),
233    /// The contact candidates of two soft surfaces (see [`SoftPairContacts`]), rebuilt by
234    /// every update of the pair and turned into constraints by the soft-body solver.
235    Soft {
236        /// Whether some feature of one surface is within contact reach of the other, or the
237        /// two surfaces cross: the pair's touching state.
238        touching: bool,
239        /// The candidates (not serialized: the next update rebuilds them).
240        #[cfg_attr(feature = "serde-serialize", serde(skip))]
241        candidates: Box<SoftPairContacts>,
242    },
243}
244
245/// The contact manifolds of a [`ContactPair`], with the solver's bookkeeping about them.
246#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
247#[derive(Clone)]
248pub struct RigidPairContacts {
249    /// The set of contact manifolds between the two colliders.
250    ///
251    /// All contact manifold contain themselves contact points between the colliders.
252    /// Note that contact points in the contact manifold do not take into account the
253    /// [`Collider::contact_skin`] which only affects the constraint solver and the
254    /// [`SolverContact`].
255    ///
256    /// [`Collider::contact_skin`]: crate::geometry::Collider::contact_skin
257    pub manifolds: Vec<ContactManifold>,
258    /// Cluster manifolds handed to the constraint solver instead of `manifolds` when
259    /// contact clustering applies (see [`IntegrationParameters::contact_clustering`]);
260    /// empty otherwise. They merge the points of manifolds sharing (nearly) the same
261    /// contact normal and hold the contact impulses actually applied by the solver.
262    ///
263    /// [`IntegrationParameters::contact_clustering`]: crate::dynamics::IntegrationParameters::contact_clustering
264    pub solver_clusters: Vec<ContactManifold>,
265    /// The clusters solved at the previous step, kept as the warm-start source (and
266    /// reused as workspace buffers) when rebuilding `solver_clusters` each frame.
267    #[cfg_attr(feature = "serde-serialize", serde(skip))]
268    pub(crate) solver_clusters_prev: Vec<ContactManifold>,
269    /// The persistent solver graph color of this pair: same-color active pairs never share
270    /// a rigid-body, so one color solves concurrently. Maintained incrementally on contact
271    /// start/stop; `SOLVER_COLOR_UNCOLORED` inactive, `SOLVER_COLOR_OVERFLOW` no free color.
272    #[cfg_attr(feature = "serde-serialize", serde(default = "default_solver_color"))]
273    pub(crate) solver_color: u8,
274    /// The body mask slots on which this pair's color bit is set (u32::MAX = none).
275    #[cfg_attr(
276        feature = "serde-serialize",
277        serde(default = "default_solver_color_bodies")
278    )]
279    pub(crate) solver_color_bodies: [u32; 2],
280    pub(crate) workspace: Option<ContactManifoldsWorkspace>,
281    /// State cached at the last full narrow-phase update, allowing the update to be
282    /// skipped ("recycled") while the colliders' relative pose stays within
283    /// `IntegrationParameters::contact_recycling`'s drift threshold.
284    ///
285    /// Part of the snapshot: a restored pair must resume recycling from the same
286    /// reference pose, or its first update recomputes manifolds (and re-derives the
287    /// world-frozen solver anchors) where the uninterrupted run would have recycled.
288    pub(crate) recycle_state: Option<ContactRecycleState>,
289    /// The soft contacts of a soft-rigid pair beside its manifolds (the predictive vertex
290    /// contacts and the volume patch, see `SoftRigidContacts`), rebuilt by every update; `None`
291    /// for every other pair.
292    #[cfg_attr(feature = "serde-serialize", serde(skip))]
293    pub(crate) soft: Option<Box<SoftRigidContacts>>,
294}
295
296impl RigidPairContacts {
297    fn new() -> Self {
298        Self {
299            manifolds: Vec::new(),
300            solver_clusters: Vec::new(),
301            solver_clusters_prev: Vec::new(),
302            solver_color: SOLVER_COLOR_UNCOLORED,
303            solver_color_bodies: [u32::MAX; 2],
304            workspace: None,
305            recycle_state: None,
306            soft: None,
307        }
308    }
309
310    /// The manifolds actually seen by the constraint solver: the contact clusters if
311    /// clustering applied to this pair, the plain manifolds otherwise.
312    pub fn solver_manifolds(&self) -> &[ContactManifold] {
313        if self.solver_clusters.is_empty() {
314            &self.manifolds
315        } else {
316            &self.solver_clusters
317        }
318    }
319
320    pub(crate) fn solver_manifolds_mut(&mut self) -> &mut [ContactManifold] {
321        if self.solver_clusters.is_empty() {
322            &mut self.manifolds
323        } else {
324            &mut self.solver_clusters
325        }
326    }
327
328    /// Clears the manifolds and the update's caches; the solver colour is left to the pair
329    /// transitions.
330    fn clear(&mut self) {
331        self.manifolds.clear();
332        self.solver_clusters.clear();
333        self.solver_clusters_prev.clear();
334        self.workspace = None;
335        self.recycle_state = None;
336        self.soft = None;
337    }
338
339    /// Is there any active contact among this side's solver manifolds?
340    pub fn has_any_active_contact(&self) -> bool {
341        self.solver_manifolds()
342            .iter()
343            .any(|m| !m.data.solver_contacts.is_empty())
344    }
345}
346
347/// The relative configuration of a contact pair at its last full narrow-phase
348/// update, used by contact recycling to bound how much the pair moved since.
349#[derive(Copy, Clone, Debug)]
350#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
351pub(crate) struct ContactRecycleState {
352    /// Pose of the second collider relative to the first at the last full update.
353    pub pos12: Pose,
354    /// World rotation of the first collider at the last full update. The frozen world-space anchors
355    /// ([`ContactData::solver_dp1`]) mean recycling must also bound each body's *absolute* rotation:
356    /// rotating rigidly together keeps the relative pose but invalidates world-frozen arms (bound: `cos Δθ > 0.98`, ~11.5°).
357    pub rot1: crate::math::Rotation,
358    /// World rotation of the second collider at the last full update.
359    pub rot2: crate::math::Rotation,
360    /// Conservative bound on the distance of any point of either shape from its
361    /// collider origin, used to convert a relative rotation into a point-drift bound.
362    pub max_extent: Real,
363    /// The maximum relative-pose drift below which this pair can be recycled,
364    /// precomputed at the last full update (it depends on whether the pair had
365    /// active contacts, which recycling doesn't change).
366    pub max_drift: Real,
367}
368
369/// `cos Δθ` between two world rotations (in 3D, computed from the quaternion dot
370/// `cos(Δθ/2)` as `2·dot² − 1`), for the per-body rotation bound of contact
371/// recycling.
372#[inline]
373pub(crate) fn relative_rot_cos(base: &crate::math::Rotation, cur: &crate::math::Rotation) -> Real {
374    #[cfg(feature = "dim2")]
375    {
376        base.dot(*cur)
377    }
378    #[cfg(feature = "dim3")]
379    {
380        let c = base.dot(*cur);
381        2.0 * c * c - 1.0
382    }
383}
384
385/// Straight-line bound on how far any point within `max_extent` of the origin moved
386/// between poses `base` and `cur`: translation delta + rotation *chord* `2·max_extent·sin(Δθ/2)`.
387/// Tighter than the arc length `max_extent·Δθ`, and no `atan2`/`acos`.
388#[inline]
389pub(crate) fn relative_pose_drift(base: &Pose, cur: &Pose, max_extent: Real) -> Real {
390    let trans = (cur.translation - base.translation).length();
391    let delta_rot = cur.rotation * base.rotation.inverse();
392    #[cfg(feature = "dim2")]
393    let rot_chord = {
394        // To avoid explicit trigonometric functions, use the identity:
395        // `sin(Δθ/2) = |sin Δθ| / sqrt(2(1 + cos Δθ))`
396        let (sin, cos) = (delta_rot.sin(), delta_rot.cos());
397        let denom = 2.0 * (1.0 + cos);
398        let half_sin = if denom > 1.0e-6 {
399            sin.abs() / denom.sqrt()
400        } else {
401            1.0
402        };
403
404        2.0 * half_sin * max_extent
405    };
406    #[cfg(feature = "dim3")]
407    let rot_chord = {
408        // A unit quaternion's vector part is already `sin(Δθ/2)` about its axis.
409        2.0 * Vector::new(delta_rot.x, delta_rot.y, delta_rot.z).length() * max_extent
410    };
411    trans + rot_chord
412}
413
414impl Default for ContactPair {
415    fn default() -> Self {
416        Self::new(ColliderHandle::invalid(), ColliderHandle::invalid())
417    }
418}
419
420impl ContactPair {
421    pub(crate) fn new(collider1: ColliderHandle, collider2: ColliderHandle) -> Self {
422        Self {
423            collider1,
424            collider2,
425            event_status: PairEventStatus::empty(),
426            contacts: PairContacts::Rigid(RigidPairContacts::new()),
427        }
428    }
429
430    /// Resets a retired pair to the exact state [`Self::new`] would produce,
431    /// keeping the (outer) buffer capacities so pooled reuse skips their
432    /// reallocation on pair-churn-heavy scenes.
433    pub(crate) fn reset_for_reuse(&mut self, collider1: ColliderHandle, collider2: ColliderHandle) {
434        self.collider1 = collider1;
435        self.collider2 = collider2;
436        self.event_status = PairEventStatus::empty();
437        match &mut self.contacts {
438            PairContacts::Rigid(rigid) => {
439                rigid.clear();
440                rigid.solver_color = SOLVER_COLOR_UNCOLORED;
441                rigid.solver_color_bodies = [u32::MAX; 2];
442            }
443            PairContacts::Soft { .. } => {
444                self.contacts = PairContacts::Rigid(RigidPairContacts::new());
445            }
446        }
447    }
448
449    /// Turns the pair into a pair of two soft surfaces (see [`PairContacts::Soft`]), keeping
450    /// it if it already is one.
451    pub(crate) fn make_soft(&mut self) {
452        if !matches!(self.contacts, PairContacts::Soft { .. }) {
453            self.contacts = PairContacts::Soft {
454                touching: false,
455                candidates: Box::default(),
456            };
457        }
458    }
459
460    /// The pair's contact manifolds (`None` for a pair of two soft surfaces).
461    pub fn rigid(&self) -> Option<&RigidPairContacts> {
462        match &self.contacts {
463            PairContacts::Rigid(rigid) => Some(rigid),
464            PairContacts::Soft { .. } => None,
465        }
466    }
467
468    pub(crate) fn rigid_mut(&mut self) -> Option<&mut RigidPairContacts> {
469        match &mut self.contacts {
470            PairContacts::Rigid(rigid) => Some(rigid),
471            PairContacts::Soft { .. } => None,
472        }
473    }
474
475    /// The pair's contact candidates (`None` unless the pair is two soft surfaces).
476    pub fn soft(&self) -> Option<&SoftPairContacts> {
477        match &self.contacts {
478            PairContacts::Soft { candidates, .. } => Some(candidates),
479            PairContacts::Rigid(_) => None,
480        }
481    }
482
483    pub(crate) fn soft_mut(&mut self) -> Option<&mut SoftPairContacts> {
484        match &mut self.contacts {
485            PairContacts::Soft { candidates, .. } => Some(candidates),
486            PairContacts::Rigid(_) => None,
487        }
488    }
489
490    /// The pair's contact manifolds (empty for a pair of two soft surfaces, whose contacts
491    /// are its candidates: see [`Self::soft`]).
492    pub fn manifolds(&self) -> &[ContactManifold] {
493        self.rigid().map_or(&[], |r| &r.manifolds)
494    }
495
496    /// The manifolds actually seen by the constraint solver: the contact clusters if
497    /// clustering applied to this pair, the plain manifolds otherwise (empty for a pair of
498    /// two soft surfaces).
499    pub fn solver_manifolds(&self) -> &[ContactManifold] {
500        self.rigid().map_or(&[], |r| r.solver_manifolds())
501    }
502
503    /// Mutable twin of [`Self::solver_manifolds`]: the manifolds the constraint
504    /// solver actually sees (the solver clusters if any, else the plain manifolds).
505    #[cfg_attr(feature = "parallel", allow(dead_code))] // Single-threaded solver path.
506    pub(crate) fn solver_manifolds_mut(&mut self) -> &mut [ContactManifold] {
507        self.rigid_mut()
508            .map_or(&mut [], |r| r.solver_manifolds_mut())
509    }
510
511    /// The pair's solver graph colour (`SOLVER_COLOR_UNCOLORED` for a pair of two soft
512    /// surfaces, which the rigid solver never sees).
513    pub(crate) fn solver_color(&self) -> u8 {
514        self.rigid()
515            .map_or(SOLVER_COLOR_UNCOLORED, |r| r.solver_color)
516    }
517
518    /// Is there any active contact in this contact pair?
519    pub fn has_any_active_contact(&self) -> bool {
520        match &self.contacts {
521            PairContacts::Rigid(rigid) => rigid.has_any_active_contact(),
522            PairContacts::Soft { touching, .. } => *touching,
523        }
524    }
525
526    /// Clears all the contacts of this contact pair.
527    pub fn clear(&mut self) {
528        match &mut self.contacts {
529            PairContacts::Rigid(rigid) => rigid.clear(),
530            PairContacts::Soft {
531                touching,
532                candidates,
533            } => {
534                *touching = false;
535                candidates.clear();
536            }
537        }
538    }
539
540    // NOTE: while recycled, a pair's world-space solver data (normal, frozen lever arms — see
541    // `ContactData::solver_dp1`) keeps its last-full-update values (anchor freezing): the solver
542    // rebuilds world points/separations from body-local anchors + current poses, so no per-step update; user data stays stale within the recycle drift bound.
543
544    /// The total impulse (force × time) applied by all contacts.
545    ///
546    /// This is the accumulated force that pushed the colliders apart.
547    /// Useful for determining impact strength.
548    pub fn total_impulse(&self) -> Vector {
549        match &self.contacts {
550            PairContacts::Rigid(rigid) => rigid
551                .solver_manifolds()
552                .iter()
553                .map(|m| m.total_impulse() * m.data.normal)
554                .sum(),
555            PairContacts::Soft { candidates, .. } => candidates
556                .impulses(self.collider1)
557                .map(|i| i.normal * i.impulse)
558                .sum(),
559        }
560    }
561
562    /// The total magnitude of all contact impulses (sum of lengths, not length of sum).
563    ///
564    /// This is what's compared against `contact_force_event_threshold`.
565    pub fn total_impulse_magnitude(&self) -> Real {
566        match &self.contacts {
567            PairContacts::Rigid(rigid) => rigid
568                .solver_manifolds()
569                .iter()
570                .fold(0.0, |a, m| a + m.total_impulse()),
571            PairContacts::Soft { candidates, .. } => candidates
572                .impulses(self.collider1)
573                .fold(0.0, |a, i| a + i.impulse),
574        }
575    }
576
577    /// Finds the strongest contact impulse and its direction.
578    ///
579    /// Returns `(magnitude, normal_direction)` of the strongest individual contact.
580    pub fn max_impulse(&self) -> (Real, Vector) {
581        let mut result = (0.0, Vector::ZERO);
582
583        match &self.contacts {
584            PairContacts::Rigid(rigid) => {
585                for m in rigid.solver_manifolds() {
586                    let impulse = m.total_impulse();
587
588                    if impulse > result.0 {
589                        result = (impulse, m.data.normal);
590                    }
591                }
592            }
593            PairContacts::Soft { candidates, .. } => {
594                for i in candidates.impulses(self.collider1) {
595                    if i.impulse > result.0 {
596                        result = (i.impulse, i.normal);
597                    }
598                }
599            }
600        }
601
602        result
603    }
604
605    /// Finds the contact point with the deepest penetration.
606    ///
607    /// When objects overlap, this returns the contact point that's penetrating the most.
608    /// Useful for:
609    /// - Finding the "worst" overlap
610    /// - Determining primary contact direction
611    /// - Custom penetration resolution
612    ///
613    /// Returns both the contact point and its parent manifold (`None` for a pair of two soft
614    /// surfaces, whose contacts are candidates: see [`Self::soft`]).
615    ///
616    /// # Example
617    /// ```
618    /// # use rapier3d::prelude::*;
619    /// # use rapier3d::geometry::ContactPair;
620    /// # let pair = ContactPair::default();
621    /// if let Some((manifold, contact)) = pair.find_deepest_contact() {
622    ///     let penetration_depth = -contact.dist;  // Negative dist = penetration
623    ///     println!("Deepest penetration: {} units", penetration_depth);
624    /// }
625    /// ```
626    #[profiling::function]
627    pub fn find_deepest_contact(&self) -> Option<(&ContactManifold, &Contact)> {
628        let mut deepest = None;
629
630        for m2 in self.manifolds() {
631            let deepest_candidate = m2.find_deepest_contact();
632
633            deepest = match (deepest, deepest_candidate) {
634                (_, None) => deepest,
635                (None, Some(c2)) => Some((m2, c2)),
636                (Some((m1, c1)), Some(c2)) => {
637                    if c1.dist <= c2.dist {
638                        Some((m1, c1))
639                    } else {
640                        Some((m2, c2))
641                    }
642                }
643            }
644        }
645
646        deepest
647    }
648
649    pub(crate) fn emit_start_event(
650        &mut self,
651        bodies: &RigidBodySet,
652        colliders: &ColliderSet,
653        events: &dyn EventHandler,
654    ) {
655        self.event_status
656            .insert(PairEventStatus::START_EVENT_EMITTED);
657
658        events.handle_collision_event(
659            bodies,
660            colliders,
661            CollisionEvent::Started(self.collider1, self.collider2, CollisionEventFlags::empty()),
662            Some(self),
663        );
664    }
665
666    pub(crate) fn emit_stop_event(
667        &mut self,
668        bodies: &RigidBodySet,
669        colliders: &ColliderSet,
670        events: &dyn EventHandler,
671    ) {
672        // Not touching anymore: the force-event threshold status resets with it.
673        self.event_status = PairEventStatus::empty();
674
675        events.handle_collision_event(
676            bodies,
677            colliders,
678            CollisionEvent::Stopped(self.collider1, self.collider2, CollisionEventFlags::empty()),
679            Some(self),
680        );
681    }
682}
683
684#[derive(Clone, Debug)]
685#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
686/// A contact manifold between two colliders.
687///
688/// A contact manifold describes a set of contacts between two colliders. All the contact
689/// part of the same contact manifold share the same contact normal and contact kinematics.
690pub struct ContactManifoldData {
691    // The following are set by the narrow-phase.
692    /// The first rigid-body involved in this contact manifold.
693    pub rigid_body1: Option<RigidBodyHandle>,
694    /// The second rigid-body involved in this contact manifold.
695    pub rigid_body2: Option<RigidBodyHandle>,
696    // We put the following fields here to avoids reading the colliders inside of the
697    // contact preparation method.
698    /// Flags used to control some aspects of the constraints solver for this contact manifold.
699    pub solver_flags: SolverFlags,
700    /// The solver graph color of this manifold (copied from its contact pair during
701    /// constraint selection; extra manifolds of a same pair are sent to the overflow
702    /// color since they share their bodies).
703    #[cfg_attr(feature = "serde-serialize", serde(default = "default_solver_color"))]
704    pub(crate) solver_color: u8,
705    /// The solver-body index (`active_set_id`) of each rigid-body, or `u32::MAX` for a
706    /// world-attached side (fixed, sleeping, no body). Stamped by constraint selection so
707    /// the assembly never re-reads the rigid-body set.
708    pub(crate) solver_body_ids: [u32; 2],
709    /// This manifold's persistent position (bucket + index) in the narrow-phase's
710    /// `SolverContactGraph`, maintained incrementally so the solver reads a ready color-grouped
711    /// contact list without re-selecting/re-sorting. `GraphPos::NONE` when not solver-active.
712    #[cfg_attr(feature = "parallel", allow(dead_code))] // Single-threaded solver path.
713    pub(crate) graph_pos: crate::dynamics::solver::solver_contact_graph::GraphPos,
714    /// The world-space contact normal shared by all the contact in this contact manifold.
715    // NOTE: read the comment of `solver_contacts` regarding serialization. It applies
716    // to this field as well.
717    pub normal: Vector,
718    /// The contacts that will be seen by the constraints solver for computing forces.
719    // NOTE: unfortunately, we can't ignore this field when serialize
720    // the contact manifold data. The reason is that the solver contacts
721    // won't be updated for sleeping bodies. So it means that for one
722    // frame, we won't have any solver contacts when waking up an island
723    // after a deserialization. Not only does this break post-snapshot
724    // determinism, but it will also skip constraint resolution for these
725    // contacts during one frame.
726    //
727    // An alternative would be to skip the serialization of `solver_contacts` and
728    // find a way to recompute them right after the deserialization process completes.
729    // However, this would be an expensive operation. And doing this efficiently as part
730    // of the narrow-phase update or the contact manifold collect will likely lead to tricky
731    // bugs too.
732    //
733    // So right now it is best to just serialize this field and keep it that way until it
734    // is proven to be actually problematic in real applications (in terms of snapshot size for example).
735    pub solver_contacts: SolverContacts,
736    /// The relative dominance of the bodies involved in this contact manifold.
737    pub relative_dominance: i16,
738    /// A user-defined piece of data.
739    pub user_data: u32,
740    /// The effective friction coefficient of this manifold's contacts (combined from
741    /// both colliders' materials; identical for every contact of the manifold).
742    #[cfg_attr(feature = "serde-serialize", serde(default))]
743    pub friction: Real,
744    /// The effective restitution coefficient of this manifold's contacts.
745    #[cfg_attr(feature = "serde-serialize", serde(default))]
746    pub restitution: Real,
747}
748
749/// A single solver contact.
750pub type SolverContact = SolverContactGeneric<Real, 1>;
751
752/// The container of a manifold's solver contacts. In 2D a manifold has at most 2 active
753/// contacts, so they are stored inline: the solver's contact gathers read one contiguous
754/// manifold instead of chasing a heap allocation per manifold (a dependent cache miss
755/// on every SIMD lane of every constraint, every step).
756#[cfg(feature = "dim2")]
757pub type SolverContacts = arrayvec::ArrayVec<SolverContact, 2>;
758/// The container of a manifold's solver contacts. In 3D, composite-shape manifolds can
759/// exceed the solver's per-constraint point cap, so they stay heap-allocated.
760#[cfg(feature = "dim3")]
761pub type SolverContacts = Vec<SolverContact>;
762/// A group of `SIMD_WIDTH` solver contacts stored in SoA fashion for SIMD optimizations.
763pub type SimdSolverContact = SolverContactGeneric<SimdReal, SIMD_WIDTH>;
764
765/// A contact seen by the constraints solver for computing forces.
766#[derive(Copy, Clone, Debug)]
767#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
768#[cfg_attr(
769    feature = "serde-serialize",
770    serde(bound(
771        serialize = "N: serde::Serialize, N::Vector: serde::Serialize, [ContactId; LANES]: serde::Serialize"
772    ))
773)]
774#[cfg_attr(
775    feature = "serde-serialize",
776    serde(bound(
777        deserialize = "N: serde::Deserialize<'de>, N::Vector: serde::Deserialize<'de>, [ContactId; LANES]: serde::Deserialize<'de>"
778    ))
779)]
780#[repr(C)]
781#[repr(align(16))]
782pub struct SolverContactGeneric<N: ScalarType, const LANES: usize> {
783    // IMPORTANT: don't change the fields unless `SimdSolverContactRepr` is also changed.
784    // TOTAL: 8/8 lanes in 2D (two 16B SIMD rows), 11/12 in 3D. Friction/restitution live
785    // on `ContactManifoldData`, is-new in bit 31 of `contact_id`, warm-starts on the manifold points.
786    /// The contact point on the first body's surface (contact skin baked in), in that
787    /// body's CoM-centered local frame so it rides rigidly with the body — what lets
788    /// contact recycling skip the per-frame world update. World-space instead for a side
789    /// without a solver body (none, or world-attached by dominance — fixed bodies included).
790    /// Inside [`PhysicsHooks::modify_solver_contacts`] this always holds the fresh
791    /// **world-space** point (hooks run before localization).
792    ///
793    /// [`PhysicsHooks::modify_solver_contacts`]: crate::pipeline::PhysicsHooks::modify_solver_contacts
794    pub anchor1: N::Vector, // 2/3
795    /// The contact point on the second body's surface, expressed like
796    /// [`Self::anchor1`] (world-space when the second side is world-attached, i.e.
797    /// `relative_dominance < 0`, or inside the contact-modification hook).
798    pub anchor2: N::Vector, // 2/3
799    /// Distance between the contact points along the normal at the last full contact
800    /// update (negative = penetration), minus the contact skins. Writable from
801    /// [`PhysicsHooks::modify_solver_contacts`] (the delta is baked into the anchors after
802    /// the hook); afterwards the solver re-derives the live separation and never reads this.
803    ///
804    /// [`PhysicsHooks::modify_solver_contacts`]: crate::pipeline::PhysicsHooks::modify_solver_contacts
805    pub dist: N, // 1/1
806    /// The desired tangent relative velocity at the contact point.
807    ///
808    /// This is set to zero by default. Set to a non-zero value to
809    /// simulate, e.g., conveyor belts.
810    pub tangent_velocity: N::Vector, // 2/3
811    /// The index of the manifold contact used to generate this solver contact, in the
812    /// low 31 bits; bit 31 ([`NEW_CONTACT_BIT`]) is set if this contact did not exist
813    /// during the last *full* contact update (recycled steps leave it untouched; the
814    /// solver derives contact newness from the warm-start state instead).
815    pub contact_id: [ContactId; LANES], // 1/1
816    #[cfg(feature = "dim3")]
817    pub(crate) padding: [N; 1],
818}
819
820/// The storage type of [`SolverContactGeneric::contact_id`]: one `Real`-sized slot
821/// per lane, so that a lane of the AoSoA struct keeps the same layout as a scalar
822/// contact. At `f32` a slot is exactly the `u32` id; at `f64` the high 32 bits are
823/// unused padding.
824#[cfg(feature = "f32")]
825pub type ContactId = u32;
826/// See [`ContactId`].
827#[cfg(feature = "f64")]
828pub type ContactId = u64;
829
830/// Bit set in [`SolverContactGeneric::contact_id`] when the contact did not exist
831/// during the previous timestep.
832pub const NEW_CONTACT_BIT: ContactId = 1 << 31;
833
834// One scalar `SolverContact` reinterpreted as fixed 128-bit blocks for the
835// AoS↔SoA gather. The blocks are always 4-wide (`SolverBlock`), independent of
836// `SIMD_WIDTH`, so this holds at both 4 and 8 lanes.
837#[repr(C)]
838#[repr(align(16))]
839pub struct SimdSolverContactRepr {
840    data0: SolverBlock,
841    data1: SolverBlock,
842    #[cfg(feature = "dim3")]
843    data2: SolverBlock,
844}
845
846// NOTE: if these assertion fail with a weird "0 - 1 would overflow" error, it means the equality doesn’t hold.
847static_assertions::const_assert_eq!(
848    align_of::<SimdSolverContactRepr>(),
849    align_of::<SolverContact>()
850);
851static_assertions::assert_eq_size!(SimdSolverContactRepr, SolverContact);
852// The SoA gather result is at least as aligned as the AoS lane array (equal at 4
853// lanes; at 8 lanes `SimdReal` is 32-byte-aligned while the scalar array is 16).
854static_assertions::const_assert_eq!(
855    align_of::<SimdSolverContact>() % align_of::<[SolverContact; SIMD_WIDTH]>(),
856    0
857);
858static_assertions::assert_eq_size!(SimdSolverContact, [SolverContact; SIMD_WIDTH]);
859
860impl SimdSolverContact {
861    /// Gathers one solver contact per lane, at a per-lane index (the lanes of a
862    /// constraint chunk may have different active-contact counts, so callers
863    /// clamp each lane's index to its own count).
864    ///
865    /// # Safety
866    ///
867    /// Every `ks[k]` must be a valid index into `contacts[k]` — the gather reads each
868    /// lane's slice unchecked.
869    pub unsafe fn gather_unchecked(
870        contacts: &[&[SolverContact]; SIMD_WIDTH],
871        ks: [usize; SIMD_WIDTH],
872    ) -> Self {
873        // TODO PERF: double-check that the compiler is using simd loads and
874        //       isn’t generating useless copies.
875
876        let data_repr: &[&[SimdSolverContactRepr]; SIMD_WIDTH] =
877            unsafe { core::mem::transmute(contacts) };
878        use crate::utils::transpose_wide;
879
880        // One 128-bit block per lane, gathered at each lane's own `ks` index.
881        let aos0: [_; SIMD_WIDTH] =
882            core::array::from_fn(|k| unsafe { data_repr[k].get_unchecked(ks[k]).data0.0 });
883        let aos1: [_; SIMD_WIDTH] =
884            core::array::from_fn(|k| unsafe { data_repr[k].get_unchecked(ks[k]).data1.0 });
885        let soa0 = transpose_wide(aos0);
886        let soa1 = transpose_wide(aos1);
887
888        #[cfg(feature = "dim2")]
889        unsafe {
890            core::mem::transmute::<[[SimdReal; 4]; 2], SimdSolverContact>([soa0, soa1])
891        }
892
893        #[cfg(feature = "dim3")]
894        {
895            let aos2: [_; SIMD_WIDTH] =
896                core::array::from_fn(|k| unsafe { data_repr[k].get_unchecked(ks[k]).data2.0 });
897            let soa2 = transpose_wide(aos2);
898
899            unsafe {
900                core::mem::transmute::<[[SimdReal; 4]; 3], SimdSolverContact>([soa0, soa1, soa2])
901            }
902        }
903    }
904}
905
906impl<N: ScalarType, const LANES: usize> SolverContactGeneric<N, LANES> {
907    /// The manifold contact indices, with the is-new bit masked off.
908    ///
909    /// These indices are only valid within the timestep that produced this solver
910    /// contact: manifold points may be reordered or replaced by the next narrow-phase
911    /// update.
912    #[inline]
913    pub fn contact_indices(&self) -> [ContactId; LANES] {
914        self.contact_id.map(|id| id & !NEW_CONTACT_BIT)
915    }
916}
917
918/// Should a contact be treated as bouncy? (SIMD lanes; `1.0` = bouncy.) Restitution is
919/// per-manifold ([`ContactManifoldData::restitution`]); `is_new` is decoded from bit 31
920/// ([`NEW_CONTACT_BIT`]) of [`SolverContactGeneric::contact_id`].
921pub fn is_bouncy_simd(restitution: SimdReal, is_new: SimdReal) -> SimdReal {
922    use na::{SimdPartialOrd, SimdValue};
923
924    let one = SimdReal::splat(1.0);
925    let zero = SimdReal::splat(0.0);
926
927    // Treat new collisions as bouncing at first, unless we have zero restitution.
928    let if_new = one.select(restitution.simd_gt(zero), zero);
929
930    // If the contact is still here one step later, it is now a resting contact.
931    // The exception is very high restitutions, which can never rest
932    let if_not_new = one.select(restitution.simd_ge(one), zero);
933
934    if_new.select(is_new.simd_ne(zero), if_not_new)
935}
936
937/// Scalar variant of [`is_bouncy_simd`].
938pub fn is_bouncy(restitution: Real, is_new: bool) -> Real {
939    if is_new {
940        (restitution > 0.0) as u32 as Real
941    } else {
942        (restitution >= 1.0) as u32 as Real
943    }
944}
945
946impl Default for ContactManifoldData {
947    fn default() -> Self {
948        Self::new(None, None, SolverFlags::empty())
949    }
950}
951
952impl ContactManifoldData {
953    pub(crate) fn new(
954        rigid_body1: Option<RigidBodyHandle>,
955        rigid_body2: Option<RigidBodyHandle>,
956        solver_flags: SolverFlags,
957    ) -> ContactManifoldData {
958        Self {
959            rigid_body1,
960            rigid_body2,
961            solver_flags,
962            solver_color: SOLVER_COLOR_UNCOLORED,
963            solver_body_ids: [u32::MAX; 2],
964            graph_pos: crate::dynamics::solver::solver_contact_graph::GraphPos::NONE,
965            normal: Vector::ZERO,
966            solver_contacts: SolverContacts::new(),
967            relative_dominance: 0,
968            user_data: 0,
969            friction: 0.0,
970            restitution: 0.0,
971        }
972    }
973
974    /// Resolves the world-space contact points (one per body surface) of one solver
975    /// contact: body-local anchors ([`SolverContactGeneric::anchor1`]) are resolved through
976    /// the bodies' current poses (a world-attached side's anchor already is a world point).
977    /// The points differ by roughly the separation along the normal; their midpoint is the
978    /// effective solver contact point.
979    pub fn solver_contact_world_points(
980        &self,
981        contact: &SolverContact,
982        bodies: &crate::dynamics::RigidBodySet,
983    ) -> (Vector, Vector) {
984        let resolve =
985            |anchor: Vector, handle: Option<RigidBodyHandle>, world_attached: bool| match handle
986                .filter(|_| !world_attached)
987                .and_then(|h| bodies.get(h))
988            {
989                Some(rb) => rb.pos.position * (rb.mprops.local_mprops.local_com + anchor),
990                None => anchor,
991            };
992        (
993            resolve(
994                contact.anchor1,
995                self.rigid_body1,
996                self.relative_dominance > 0,
997            ),
998            resolve(
999                contact.anchor2,
1000                self.rigid_body2,
1001                self.relative_dominance < 0,
1002            ),
1003        )
1004    }
1005
1006    /// Number of actives contacts, i.e., contacts that will be seen by
1007    /// the constraints solver.
1008    #[inline]
1009    pub fn num_active_contacts(&self) -> usize {
1010        self.solver_contacts.len()
1011    }
1012}
1013
1014/// Additional methods for the contact manifold.
1015pub trait ContactManifoldExt {
1016    /// Computes the sum of all the impulses applied by contacts from this contact manifold.
1017    fn total_impulse(&self) -> Real;
1018}
1019
1020impl ContactManifoldExt for ContactManifold {
1021    fn total_impulse(&self) -> Real {
1022        self.points.iter().map(|pt| pt.data.impulse).sum()
1023    }
1024}
1025
1026#[cfg(test)]
1027mod tests {
1028    use super::relative_pose_drift;
1029    use crate::math::{Pose, Real, Vector};
1030
1031    /// A pose compared against *itself* must report no drift beyond rounding noise (the
1032    /// `cur ∘ base⁻¹` product is not exactly the identity), whatever the orientation and
1033    /// however far the shape reaches: it must never eat into the sleep gate's allowance.
1034    #[test]
1035    fn same_pose_never_drifts() {
1036        // The sleep gate's per-step allowance at the default threshold and 60 Hz.
1037        let allowance = 0.05 * (1.0 / 60.0) * 2.0;
1038        let max_extent = 4.0;
1039
1040        let mut worst: Real = 0.0;
1041        let mut n = 0;
1042        for i in 0..40 {
1043            for j in 0..10 {
1044                let angle = i as Real * 0.157;
1045                #[cfg(feature = "dim2")]
1046                let pose = Pose::new(Vector::new(j as Real * 3.7, 1.0), angle);
1047                #[cfg(feature = "dim3")]
1048                let pose = Pose::new(
1049                    Vector::new(j as Real * 3.7, 1.0, -2.0),
1050                    Vector::new(0.3, -0.7, 0.15).normalize() * angle,
1051                );
1052                worst = worst.max(relative_pose_drift(&pose, &pose, max_extent));
1053                n += 1;
1054            }
1055        }
1056        let rounding_noise = 8.0 * Real::EPSILON * max_extent;
1057        assert!(
1058            worst <= rounding_noise,
1059            "an unmoved pose drifted by up to {worst} over {n} orientations \
1060             (rounding noise is {rounding_noise}, the sleep gate allows {allowance} per step)"
1061        );
1062    }
1063}