avian3d/collision/contact_types/
mod.rs

1//! Contact types and data structures used in the collision pipeline.
2
3mod contact_graph;
4mod feature_id;
5mod system_param;
6
7pub use contact_graph::ContactGraph;
8pub use feature_id::PackedFeatureId;
9pub use system_param::Collisions;
10
11use crate::prelude::*;
12use bevy::prelude::*;
13
14/// A contact pair between two colliders.
15///
16/// Each contact pair has one or more [contact manifolds](ContactManifold),
17/// which represent contact surfaces between the two colliders.
18/// Each of these manifolds contains one or more [contact points](ContactPoint).
19///
20/// Contact pairs exist in the [`ContactGraph`] between colliders whose [`ColliderAabb`]s
21/// are overlapping, even if the colliders themselves are not touching.
22#[derive(Clone, Debug, PartialEq)]
23#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
24pub struct ContactPair {
25    /// The first collider entity in the contact.
26    pub collider1: Entity,
27    /// The second collider entity in the contact.
28    pub collider2: Entity,
29    /// The entity of the first body involved in the contact.
30    pub body1: Option<Entity>,
31    /// The entity of the second body involved in the contact.
32    pub body2: Option<Entity>,
33    /// A list of contact manifolds between two colliders.
34    /// Each manifold contains one or more contact points, but each contact
35    /// in a given manifold shares the same contact normal.
36    pub manifolds: Vec<ContactManifold>,
37    /// Flag indicating the status and type of the contact pair.
38    pub flags: ContactPairFlags,
39}
40
41/// Flags indicating the status and type of a [contact pair](ContactPair).
42#[repr(transparent)]
43#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
44#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
45#[reflect(opaque, Hash, PartialEq, Debug)]
46pub struct ContactPairFlags(u8);
47
48bitflags::bitflags! {
49    impl ContactPairFlags: u8 {
50        /// Set if the colliders are touching, including sensors.
51        const TOUCHING = 0b0000_0001;
52        /// Set if the AABBs of the colliders are no longer overlapping.
53        const DISJOINT_AABB = 0b0000_0010;
54        /// Set if the colliders are touching and were not touching previously.
55        const STARTED_TOUCHING = 0b0000_0100;
56        /// Set if the colliders are not touching and were touching previously.
57        const STOPPED_TOUCHING = 0b0000_1000;
58        /// Set if at least one of the colliders is a sensor.
59        const SENSOR = 0b0001_0000;
60        /// Set if the contact pair should emit contact events or sensor events.
61        const CONTACT_EVENTS = 0b0010_0000;
62        /// Set if the contact pair should have a custom contact modification hook applied.
63        const MODIFY_CONTACTS = 0b0100_0000;
64    }
65}
66
67impl ContactPair {
68    /// Creates a new [`ContactPair`] with the given entities.
69    #[inline]
70    pub fn new(collider1: Entity, collider2: Entity) -> Self {
71        Self {
72            collider1,
73            collider2,
74            body1: None,
75            body2: None,
76            manifolds: Vec::new(),
77            flags: ContactPairFlags::empty(),
78        }
79    }
80
81    /// Computes the sum of all impulses applied along contact normals between the contact pair.
82    ///
83    /// To get the corresponding force, divide the impulse by `Time::<Substeps>::delta_secs()`.
84    pub fn total_normal_impulse(&self) -> Vector {
85        self.manifolds.iter().fold(Vector::ZERO, |acc, manifold| {
86            acc + manifold.normal * manifold.total_normal_impulse()
87        })
88    }
89
90    /// Computes the sum of the magnitudes of all impulses applied along contact normals between the contact pair.
91    ///
92    /// This is the sum of impulse magnitudes, *not* the magnitude of the [`total_normal_impulse`](Self::total_normal_impulse).
93    ///
94    /// To get the corresponding force, divide the impulse by `Time::<Substeps>::delta_secs()`.
95    pub fn total_normal_impulse_magnitude(&self) -> Scalar {
96        self.manifolds
97            .iter()
98            .fold(0.0, |acc, manifold| acc + manifold.total_normal_impulse())
99    }
100
101    // TODO: We could also return a reference to the whole manifold. Would that be useful?
102    /// Finds the largest impulse between the contact pair, and the associated world-space contact normal,
103    /// pointing from the first shape to the second.
104    ///
105    /// To get the corresponding force, divide the impulse by `Time::<Substeps>::delta_secs()`.
106    pub fn max_normal_impulse(&self) -> (Scalar, Vector) {
107        let mut magnitude: Scalar = 0.0;
108        let mut normal = Vector::ZERO;
109
110        for manifold in &self.manifolds {
111            let impulse = manifold.max_normal_impulse();
112            if impulse.abs() > magnitude.abs() {
113                magnitude = impulse;
114                normal = manifold.normal;
115            }
116        }
117
118        (magnitude, normal)
119    }
120
121    /// The force corresponding to the total normal impulse applied over `delta_time`.
122    ///
123    /// Because contacts are solved over several substeps, `delta_time` should
124    /// typically use `Time<Substeps>::delta_secs()`.
125    #[deprecated(
126        note = "Use `total_normal_impulse` instead, and divide it by `Time<Substeps>::delta_secs()`",
127        since = "0.3.0"
128    )]
129    pub fn total_normal_force(&self, delta_time: Scalar) -> Scalar {
130        self.total_normal_impulse_magnitude() / delta_time
131    }
132
133    /// Returns `true` if at least one of the colliders is a [`Sensor`].
134    pub fn is_sensor(&self) -> bool {
135        self.flags.contains(ContactPairFlags::SENSOR)
136    }
137
138    /// Returns `true` if the colliders are touching, including sensors.
139    pub fn is_touching(&self) -> bool {
140        self.flags.contains(ContactPairFlags::TOUCHING)
141    }
142
143    /// Returns `true` if the AABBs of the colliders are no longer overlapping.
144    pub fn aabbs_disjoint(&self) -> bool {
145        self.flags.contains(ContactPairFlags::DISJOINT_AABB)
146    }
147
148    /// Returns `true` if a collision started during the current frame.
149    pub fn collision_started(&self) -> bool {
150        self.flags.contains(ContactPairFlags::STARTED_TOUCHING)
151    }
152
153    /// Returns `true` if a collision ended during the current frame.
154    pub fn collision_ended(&self) -> bool {
155        self.flags.contains(ContactPairFlags::STOPPED_TOUCHING)
156    }
157
158    /// Returns `true` if collision events are enabled for the contact pair.
159    pub fn events_enabled(&self) -> bool {
160        self.flags.contains(ContactPairFlags::CONTACT_EVENTS)
161    }
162
163    /// Returns the contact with the largest penetration depth.
164    ///
165    /// If the objects are separated but there is still a speculative contact,
166    /// the penetration depth will be negative.
167    ///
168    /// If there are no contacts, `None` is returned.
169    pub fn find_deepest_contact(&self) -> Option<&ContactPoint> {
170        self.manifolds
171            .iter()
172            .filter_map(|manifold| manifold.find_deepest_contact())
173            .max_by(|a, b| {
174                a.penetration
175                    .partial_cmp(&b.penetration)
176                    .unwrap_or(core::cmp::Ordering::Equal)
177            })
178    }
179}
180
181/// A contact manifold describing a contact surface between two colliders,
182/// represented by a set of [contact points](ContactPoint) and surface properties.
183///
184/// A manifold can typically be a single point, a line segment, or a polygon formed by its contact points.
185/// Each contact point in a manifold shares the same contact normal.
186#[cfg_attr(
187    feature = "2d",
188    doc = "
189In 2D, contact manifolds are limited to 2 points."
190)]
191#[derive(Clone, Debug, PartialEq)]
192#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
193pub struct ContactManifold {
194    /// The contact points in this manifold. Limited to 2 points in 2D.
195    ///
196    /// Each point in a manifold shares the same `normal`.
197    #[cfg(feature = "2d")]
198    pub points: arrayvec::ArrayVec<ContactPoint, 2>,
199    /// The contact points in this manifold.
200    ///
201    /// Each point in a manifold shares the same `normal`.
202    #[cfg(feature = "3d")]
203    pub points: Vec<ContactPoint>,
204    /// The unit contact normal in world space, pointing from the first shape to the second.
205    ///
206    /// The same normal is shared by all `points` in a manifold.
207    pub normal: Vector,
208    /// The effective coefficient of dynamic [friction](Friction) used for the contact surface.
209    pub friction: Scalar,
210    /// The effective coefficient of [restitution](Restitution) used for the contact surface.
211    pub restitution: Scalar,
212    /// The desired relative linear speed of the bodies along the surface,
213    /// expressed in world space as `tangent_speed2 - tangent_speed1`.
214    ///
215    /// Defaults to zero. If set to a non-zero value, this can be used to simulate effects
216    /// such as conveyor belts.
217    #[cfg(feature = "2d")]
218    pub tangent_speed: Scalar,
219    // TODO: Jolt also supports a relative angular surface velocity, which can be used for making
220    //       objects rotate on platforms. Would that be useful enough to warrant the extra memory usage?
221    /// The desired relative linear velocity of the bodies along the surface,
222    /// expressed in world space as `tangent_velocity2 - tangent_velocity1`.
223    ///
224    /// Defaults to zero. If set to a non-zero value, this can be used to simulate effects
225    /// such as conveyor belts.
226    #[cfg(feature = "3d")]
227    pub tangent_velocity: Vector,
228    /// The index of the manifold in the collision.
229    pub index: usize,
230}
231
232impl ContactManifold {
233    /// Creates a new [`ContactManifold`] with the given contact points and surface normals,
234    /// expressed in local space.
235    ///
236    /// `index` represents the index of the manifold in the collision.
237    pub fn new(
238        points: impl IntoIterator<Item = ContactPoint>,
239        normal: Vector,
240        index: usize,
241    ) -> Self {
242        Self {
243            #[cfg(feature = "2d")]
244            points: arrayvec::ArrayVec::from_iter(points),
245            #[cfg(feature = "3d")]
246            points: points.into_iter().collect(),
247            normal,
248            friction: 0.0,
249            restitution: 0.0,
250            #[cfg(feature = "2d")]
251            tangent_speed: 0.0,
252            #[cfg(feature = "3d")]
253            tangent_velocity: Vector::ZERO,
254            index,
255        }
256    }
257
258    /// The sum of the impulses applied at the contact points in the manifold along the contact normal.
259    fn total_normal_impulse(&self) -> Scalar {
260        self.points
261            .iter()
262            .fold(0.0, |acc, contact| acc + contact.normal_impulse)
263    }
264
265    /// The magnitude of the largest impulse applied at a contact point in the manifold along the contact normal.
266    fn max_normal_impulse(&self) -> Scalar {
267        self.points
268            .iter()
269            .map(|contact| contact.normal_impulse)
270            .max_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
271            .unwrap_or(0.0)
272    }
273
274    /// Copies impulses from previous contacts to matching contacts in `self`.
275    ///
276    /// Contacts are first matched based on their [feature IDs](PackedFeatureId), and if they are unknown,
277    /// matching is done based on contact positions using the given `distance_threshold`
278    /// for determining if points are too far away from each other to be considered matching.
279    pub fn match_contacts(
280        &mut self,
281        previous_contacts: &[ContactPoint],
282        distance_threshold: Scalar,
283    ) {
284        // The squared maximum distance for two contact points to be considered matching.
285        let distance_threshold_squared = distance_threshold.powi(2);
286
287        for contact in self.points.iter_mut() {
288            for previous_contact in previous_contacts.iter() {
289                // If the feature IDs match, copy the contact impulses over for warm starting.
290                if (contact.feature_id1 == previous_contact.feature_id1
291                    && contact.feature_id2 == previous_contact.feature_id2) ||
292                    // we have to check both directions because the entities are sorted in order
293                    // of aabb.min.x, which could have changed even the two objects in contact are the same
294                    (contact.feature_id2 == previous_contact.feature_id1
295                    && contact.feature_id1 == previous_contact.feature_id2)
296                {
297                    contact.normal_impulse = previous_contact.normal_impulse;
298                    contact.tangent_impulse = previous_contact.tangent_impulse;
299                    break;
300                }
301
302                let unknown_features = contact.feature_id1 == PackedFeatureId::UNKNOWN
303                    || contact.feature_id2 == PackedFeatureId::UNKNOWN;
304
305                // If the feature IDs are unknown and the contact positions match closely enough,
306                // copy the contact impulses over for warm starting.
307                if unknown_features
308                    && (contact
309                        .local_point1
310                        .distance_squared(previous_contact.local_point1)
311                        < distance_threshold_squared
312                        && contact
313                            .local_point2
314                            .distance_squared(previous_contact.local_point2)
315                            < distance_threshold_squared)
316                    || (contact
317                        .local_point1
318                        .distance_squared(previous_contact.local_point2)
319                        < distance_threshold_squared
320                        && contact
321                            .local_point2
322                            .distance_squared(previous_contact.local_point1)
323                            < distance_threshold_squared)
324                {
325                    contact.normal_impulse = previous_contact.normal_impulse;
326                    contact.tangent_impulse = previous_contact.tangent_impulse;
327                    break;
328                }
329            }
330        }
331    }
332
333    /// Returns the contact point with the largest penetration depth.
334    ///
335    /// If the objects are separated but there is still a speculative contact,
336    /// the penetration depth will be negative.
337    ///
338    /// If there are no contacts, `None` is returned.
339    pub fn find_deepest_contact(&self) -> Option<&ContactPoint> {
340        self.points.iter().max_by(|a, b| {
341            a.penetration
342                .partial_cmp(&b.penetration)
343                .unwrap_or(core::cmp::Ordering::Equal)
344        })
345    }
346}
347
348/// Data associated with a contact point in a [`ContactManifold`].
349#[derive(Clone, Copy, Debug, PartialEq)]
350#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
351pub struct ContactPoint {
352    /// The contact point on the first shape in local space.
353    pub local_point1: Vector,
354    /// The contact point on the second shape in local space.
355    pub local_point2: Vector,
356    /// The penetration depth.
357    ///
358    /// Can be negative if the objects are separated and [speculative collision] is enabled.
359    ///
360    /// [speculative collision]: crate::dynamics::ccd#speculative-collision
361    pub penetration: Scalar,
362    /// The impulse applied to the first body along the contact normal.
363    ///
364    /// To get the corresponding force, divide the impulse by `Time<Substeps>::delta_secs()`.
365    pub normal_impulse: Scalar,
366    /// The impulse applied to the first body along the contact tangent. This corresponds to the impulse caused by friction.
367    ///
368    /// To get the corresponding force, divide the impulse by `Time<Substeps>::delta_secs()`.
369    #[cfg(feature = "2d")]
370    #[doc(alias = "friction_impulse")]
371    pub tangent_impulse: Scalar,
372    /// The impulse applied to the first body along the contact tangent. This corresponds to the impulse caused by friction.
373    ///
374    /// To get the corresponding force, divide the impulse by `Time<Substeps>::delta_secs()`.
375    #[cfg(feature = "3d")]
376    #[doc(alias = "friction_impulse")]
377    pub tangent_impulse: Vector2,
378    /// The contact feature ID on the first shape. This indicates the ID of
379    /// the vertex, edge, or face of the contact, if one can be determined.
380    pub feature_id1: PackedFeatureId,
381    /// The contact feature ID on the second shape. This indicates the ID of
382    /// the vertex, edge, or face of the contact, if one can be determined.
383    pub feature_id2: PackedFeatureId,
384}
385
386impl ContactPoint {
387    /// Creates a new [`ContactPoint`] with the given points expressed in the local space
388    /// of the first and second shape respectively.
389    ///
390    /// [Feature IDs](PackedFeatureId) can be specified for the contact points using [`with_feature_ids`](Self::with_feature_ids).
391    #[allow(clippy::too_many_arguments)]
392    pub fn new(local_point1: Vector, local_point2: Vector, penetration: Scalar) -> Self {
393        Self {
394            local_point1,
395            local_point2,
396            penetration,
397            normal_impulse: 0.0,
398            tangent_impulse: default(),
399            feature_id1: PackedFeatureId::UNKNOWN,
400            feature_id2: PackedFeatureId::UNKNOWN,
401        }
402    }
403
404    /// Sets the [feature IDs](PackedFeatureId) of the contact points.
405    pub fn with_feature_ids(mut self, id1: PackedFeatureId, id2: PackedFeatureId) -> Self {
406        self.feature_id1 = id1;
407        self.feature_id2 = id2;
408        self
409    }
410
411    /// The force corresponding to the normal impulse applied over `delta_time`.
412    ///
413    /// Because contacts are solved over several substeps, `delta_time` should
414    /// typically use `Time<Substeps>::delta_secs()`.
415    pub fn normal_force(&self, delta_time: Scalar) -> Scalar {
416        self.normal_impulse / delta_time
417    }
418
419    /// The force corresponding to the tangent impulse applied over `delta_time`.
420    ///
421    /// Because contacts are solved over several substeps, `delta_time` should
422    /// typically use `Time<Substeps>::delta_secs()`.
423    #[cfg(feature = "2d")]
424    #[doc(alias = "friction_force")]
425    pub fn tangent_force(&self, delta_time: Scalar) -> Scalar {
426        self.tangent_impulse / delta_time
427    }
428
429    /// The force corresponding to the tangent impulse applied over `delta_time`.
430    ///
431    /// Because contacts are solved over several substeps, `delta_time` should
432    /// typically use `Time<Substeps>::delta_secs()`.
433    #[cfg(feature = "3d")]
434    #[doc(alias = "friction_force")]
435    pub fn tangent_force(&self, delta_time: Scalar) -> Vector2 {
436        self.tangent_impulse / delta_time
437    }
438
439    /// Returns the global contact point on the first shape,
440    /// transforming the local point by the given position and rotation.
441    pub fn global_point1(&self, position: &Position, rotation: &Rotation) -> Vector {
442        position.0 + rotation * self.local_point1
443    }
444
445    /// Returns the global contact point on the second shape,
446    /// transforming the local point by the given position and rotation.
447    pub fn global_point2(&self, position: &Position, rotation: &Rotation) -> Vector {
448        position.0 + rotation * self.local_point2
449    }
450
451    /// Flips the contact data, swapping the points and feature IDs,
452    /// and negating the impulses.
453    pub fn flip(&mut self) {
454        core::mem::swap(&mut self.local_point1, &mut self.local_point2);
455        core::mem::swap(&mut self.feature_id1, &mut self.feature_id2);
456        self.normal_impulse = -self.normal_impulse;
457        self.tangent_impulse = -self.tangent_impulse;
458    }
459
460    /// Returns a flipped copy of the contact data, swapping the points and feature IDs,
461    /// and negating the impulses.
462    pub fn flipped(&self) -> Self {
463        Self {
464            local_point1: self.local_point2,
465            local_point2: self.local_point1,
466            penetration: self.penetration,
467            normal_impulse: -self.normal_impulse,
468            tangent_impulse: -self.tangent_impulse,
469            feature_id1: self.feature_id2,
470            feature_id2: self.feature_id1,
471        }
472    }
473}
474
475/// Data related to a single contact between two bodies.
476///
477/// If you want a contact that belongs to a [contact manifold](ContactManifold) and has more data,
478/// see [`ContactPoint`].
479#[derive(Clone, Copy, Debug, PartialEq)]
480#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
481pub struct SingleContact {
482    /// The contact point on the first shape in local space.
483    pub local_point1: Vector,
484    /// The contact point on the second shape in local space.
485    pub local_point2: Vector,
486    /// The contact normal expressed in the local space of the first shape.
487    pub local_normal1: Vector,
488    /// The contact normal expressed in the local space of the second shape.
489    pub local_normal2: Vector,
490    /// Penetration depth.
491    pub penetration: Scalar,
492}
493
494impl SingleContact {
495    /// Creates a new [`SingleContact`]. The contact points and normals should be given in local space.
496    pub fn new(
497        local_point1: Vector,
498        local_point2: Vector,
499        local_normal1: Vector,
500        local_normal2: Vector,
501        penetration: Scalar,
502    ) -> Self {
503        Self {
504            local_point1,
505            local_point2,
506            local_normal1,
507            local_normal2,
508            penetration,
509        }
510    }
511
512    /// Returns the global contact point on the first shape,
513    /// transforming the local point by the given position and rotation.
514    pub fn global_point1(&self, position: &Position, rotation: &Rotation) -> Vector {
515        position.0 + rotation * self.local_point1
516    }
517
518    /// Returns the global contact point on the second shape,
519    /// transforming the local point by the given position and rotation.
520    pub fn global_point2(&self, position: &Position, rotation: &Rotation) -> Vector {
521        position.0 + rotation * self.local_point2
522    }
523
524    /// Returns the world-space contact normal pointing from the first shape to the second.
525    pub fn global_normal1(&self, rotation: &Rotation) -> Vector {
526        rotation * self.local_normal1
527    }
528
529    /// Returns the world-space contact normal pointing from the second shape to the first.
530    pub fn global_normal2(&self, rotation: &Rotation) -> Vector {
531        rotation * self.local_normal2
532    }
533
534    /// Flips the contact data, swapping the points and normals.
535    pub fn flip(&mut self) {
536        core::mem::swap(&mut self.local_point1, &mut self.local_point2);
537        core::mem::swap(&mut self.local_normal1, &mut self.local_normal2);
538    }
539
540    /// Returns a flipped copy of the contact data, swapping the points and normals.
541    pub fn flipped(&self) -> Self {
542        Self {
543            local_point1: self.local_point2,
544            local_point2: self.local_point1,
545            local_normal1: self.local_normal2,
546            local_normal2: self.local_normal1,
547            penetration: self.penetration,
548        }
549    }
550}