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}