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}