Skip to main content

avian3d/collision/collider/parry/
contact_query.rs

1//! Geometric queries for computing information about contacts between two [`Collider`]s.
2//!
3//! This module contains the following contact queries:
4//!
5//! | Contact query         | Description                                                               |
6//! | --------------------- | ------------------------------------------------------------------------- |
7//! | [`contact`]           | Computes one pair of contact points between two [`Collider`]s.            |
8//! | [`contact_manifolds`] | Computes all [`ContactManifold`]s between two [`Collider`]s.              |
9//! | [`closest_points`]    | Computes the closest points between two [`Collider`]s.                    |
10//! | [`distance`]          | Computes the minimum distance separating two [`Collider`]s.               |
11//! | [`intersection_test`] | Tests whether two [`Collider`]s are intersecting each other.              |
12//! | [`time_of_impact`]    | Computes when two moving [`Collider`]s hit each other for the first time. |
13//!
14//! For geometric queries that query the entire world for intersections, like raycasting, shapecasting
15//! and point projection, see [spatial queries](spatial_query).
16
17use crate::{collision::contact_types::SingleContact, prelude::*};
18use bevy::prelude::*;
19use parry::query::{PersistentQueryDispatcher, ShapeCastOptions, Unsupported};
20
21/// An error indicating that a [contact query](self) is not supported for one of the [`Collider`] shapes.
22pub type UnsupportedShape = Unsupported;
23
24/// Computes one pair of contact points between two [`Collider`]s.
25///
26/// Returns `None` if the colliders are separated by a distance greater than `prediction_distance`
27/// or if the given shapes are invalid.
28///
29/// # Example
30///
31/// ```
32/// # #[cfg(feature = "2d")]
33/// # use avian2d::{collision::collider::contact_query::contact, prelude::*};
34/// # #[cfg(feature = "3d")]
35/// use avian3d::{collision::collider::contact_query::contact, math::RVec3, prelude::*};
36/// use bevy::prelude::*;
37///
38/// # #[cfg(feature = "3d")]
39/// # {
40/// let collider1 = Collider::sphere(0.5);
41/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
42///
43/// // Compute a contact that should have a penetration depth of 0.5
44/// let contact = contact(
45///     // First collider
46///     &collider1,
47///     RVec3::default(),
48///     Quat::default(),
49///     // Second collider
50///     &collider2,
51///     RVec3::X * 0.5,
52///     Quat::default(),
53///     // Prediction distance
54///     0.0,
55/// )
56/// .expect("Unsupported collider shape");
57///
58/// assert_eq!(
59///     contact.is_some_and(|contact| contact.penetration == 0.5),
60///     true
61/// );
62/// # }
63/// ```
64pub fn contact(
65    collider1: &Collider,
66    position1: RVector,
67    rotation1: impl Into<Rot>,
68    collider2: &Collider,
69    position2: RVector,
70    rotation2: impl Into<Rot>,
71    prediction_distance: f32,
72) -> Result<Option<SingleContact>, UnsupportedShape> {
73    let rotation1: Rot = rotation1.into();
74    let rotation2: Rot = rotation2.into();
75    let isometry1 = make_pose(position1, rotation1);
76    let isometry2 = make_pose(position2, rotation2);
77
78    parry::query::contact(
79        &isometry1,
80        collider1.shape_scaled().0.as_ref(),
81        &isometry2,
82        collider2.shape_scaled().0.as_ref(),
83        prediction_distance.real(),
84    )
85    .map(|contact| {
86        if let Some(contact) = contact {
87            // Transform contact data into local space
88            let inv_rotation1 = rotation1.inverse();
89            let inv_rotation2 = rotation2.inverse();
90            let point1: Vector = inv_rotation1 * contact.point1.f32();
91            let point2: Vector = inv_rotation2 * contact.point2.f32();
92            let normal1: Vector = (inv_rotation1 * contact.normal1.f32()).normalize();
93            let normal2: Vector = (inv_rotation2 * contact.normal2.f32()).normalize();
94
95            // Make sure the normals are valid
96            if !normal1.is_normalized() || !normal2.is_normalized() {
97                return None;
98            }
99
100            Some(SingleContact::new(
101                point1,
102                point2,
103                normal1,
104                normal2,
105                -contact.dist.f32(),
106            ))
107        } else {
108            None
109        }
110    })
111}
112
113// TODO: Add a persistent version of this that tries to reuse previous contact manifolds
114// by exploiting spatial and temporal coherence. This is supported by Parry's contact_manifolds,
115// but requires using Parry's `ContactManifold` type.
116/// Computes all [`ContactManifold`]s between two [`Collider`]s.
117///
118/// The contact points are expressed in world space, relative to the origin
119/// of the first and second shape respectively.
120///
121/// Returns an empty vector if the colliders are separated by a distance greater than `prediction_distance`
122/// or if the given shapes are invalid.
123///
124/// # Example
125///
126/// ```
127/// # #[cfg(feature = "2d")]
128/// # use avian2d::{collision::collider::contact_query::contact_manifolds, prelude::*};
129/// # #[cfg(feature = "3d")]
130/// use avian3d::{collision::collider::contact_query::contact_manifolds, math::RVec3, prelude::*};
131/// use bevy::prelude::*;
132///
133/// # #[cfg(feature = "3d")]
134/// # {
135/// let collider1 = Collider::sphere(0.5);
136/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
137///
138/// // Compute contact manifolds a collision that should be penetrating
139/// let mut manifolds = Vec::new();
140/// contact_manifolds(
141///     // First collider
142///     &collider1,
143///     RVec3::default(),
144///     Quat::default(),
145///     // Second collider
146///     &collider2,
147///     RVec3::X * 0.25,
148///     Quat::default(),
149///     // Prediction distance
150///     0.0,
151///     // Output manifolds
152///     &mut manifolds,
153/// );
154///
155/// assert_eq!(manifolds.is_empty(), false);
156/// # }
157/// ```
158pub fn contact_manifolds(
159    collider1: &Collider,
160    position1: RVector,
161    rotation1: impl Into<Rot>,
162    collider2: &Collider,
163    position2: RVector,
164    rotation2: impl Into<Rot>,
165    prediction_distance: f32,
166    manifolds: &mut Vec<ContactManifold>,
167) {
168    let position1: Position = position1.into();
169    let position2: Position = position2.into();
170    let rotation1: Rot = rotation1.into();
171    let rotation2: Rot = rotation2.into();
172    let isometry1 = make_pose(position1, rotation1);
173    let isometry2 = make_pose(position2, rotation2);
174    let isometry12 = isometry1.inv_mul(&isometry2);
175
176    // TODO: Reuse manifolds from previous frame to improve performance
177    let mut new_manifolds =
178        Vec::<parry::query::ContactManifold<(), ()>>::with_capacity(manifolds.len());
179    let result = parry::query::DefaultQueryDispatcher.contact_manifolds(
180        &isometry12,
181        collider1.shape_scaled().0.as_ref(),
182        collider2.shape_scaled().0.as_ref(),
183        prediction_distance.real(),
184        &mut new_manifolds,
185        &mut None,
186    );
187
188    // Clear the old manifolds.
189    manifolds.clear();
190
191    // Fall back to support map contacts for unsupported (custom) shapes.
192    if result.is_err()
193        && let (Some(shape1), Some(shape2)) = (
194            collider1.shape_scaled().as_support_map(),
195            collider2.shape_scaled().as_support_map(),
196        )
197        && let Some(contact) = parry::query::contact::contact_support_map_support_map(
198            &isometry12,
199            shape1,
200            shape2,
201            prediction_distance.real(),
202        )
203    {
204        let normal = rotation1 * contact.normal1.f32();
205
206        // Make sure the normal is valid
207        if !normal.is_normalized() {
208            return;
209        }
210
211        let local_point1: Vector = contact.point1.f32();
212
213        // The contact point is the midpoint of the two points in world space.
214        // The anchors are relative to the positions of the colliders.
215        let point1 = rotation1 * local_point1;
216        let anchor1 = point1 + normal * contact.dist.f32() * 0.5;
217        let anchor2 = anchor1 + (position1.0 - position2.0).f32();
218        let world_point = position1.0 + anchor1.real();
219        let points = [ContactPoint::new(
220            anchor1,
221            anchor2,
222            world_point,
223            -contact.dist.f32(),
224        )];
225
226        manifolds.push(ContactManifold::new(points, normal));
227    }
228
229    manifolds.extend(new_manifolds.iter().filter_map(|manifold| {
230        // Skip empty manifolds.
231        if manifold.contacts().is_empty() {
232            return None;
233        }
234
235        let subpos1 = manifold.subshape_pos1().copied().unwrap_or_default();
236        let local_normal: RVector = (subpos1.rotation * manifold.local_n1).normalize();
237        let normal = rotation1 * local_normal.f32();
238
239        // Make sure the normal is valid
240        if !normal.is_normalized() {
241            return None;
242        }
243
244        let points = manifold.contacts().iter().map(|contact| {
245            // The contact point is the midpoint of the two points in world space.
246            // The anchors are relative to the positions of the colliders.
247            let point1 = rotation1 * subpos1.transform_point(contact.local_p1).f32();
248            let anchor1 = point1 + normal * contact.dist.f32() * 0.5;
249            let anchor2 = anchor1 + (position1.0 - position2.0).f32();
250            let world_point = position1.0 + anchor1.real();
251            ContactPoint::new(anchor1, anchor2, world_point, -contact.dist.f32())
252                .with_feature_ids(contact.fid1.into(), contact.fid2.into())
253        });
254
255        let manifold = ContactManifold::new(points, normal);
256
257        Some(manifold)
258    }));
259}
260
261/// Information about the closest points between two [`Collider`]s.
262///
263/// The closest points can be computed using [`closest_points`].
264#[derive(Reflect, Clone, Copy, Debug, PartialEq)]
265#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
266#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
267#[reflect(Debug, PartialEq)]
268pub enum ClosestPoints {
269    /// The two shapes are intersecting each other.
270    Intersecting,
271    /// The two shapes are not intersecting each other but the distance between the closest points
272    /// is below the user-defined maximum distance.
273    ///
274    /// The points are expressed in world space.
275    WithinMargin(RVector, RVector),
276    /// The two shapes are not intersecting each other and the distance between the closest points
277    /// exceeds the user-defined maximum distance.
278    OutsideMargin,
279}
280
281/// Computes the [`ClosestPoints`] between two [`Collider`]s.
282///
283/// Returns `Err(UnsupportedShape)` if either of the collider shapes is not supported.
284///
285/// # Example
286///
287/// ```
288/// # #[cfg(feature = "2d")]
289/// # use avian2d::{collision::collider::contact_query::*, prelude::*};
290/// # #[cfg(feature = "3d")]
291/// use avian3d::{collision::collider::contact_query::*, math::RVec3, prelude::*};
292/// use bevy::prelude::*;
293///
294/// # #[cfg(feature = "3d")]
295/// # {
296/// let collider1 = Collider::sphere(0.5);
297/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
298///
299/// // The shapes are intersecting
300/// assert_eq!(
301///     closest_points(
302///         &collider1,
303///         RVec3::default(),
304///         Quat::default(),
305///         &collider2,
306///         RVec3::default(),
307///         Quat::default(),
308///         2.0,
309///     )
310///     .expect("Unsupported collider shape"),
311///     ClosestPoints::Intersecting,
312/// );
313///
314/// // The shapes are not intersecting but the distance between the closest points is below 2.0
315/// assert_eq!(
316///     closest_points(
317///         &collider1,
318///         RVec3::default(),
319///         Quat::default(),
320///         &collider2,
321///         RVec3::X * 1.5,
322///         Quat::default(),
323///         2.0,
324///     )
325///     .expect("Unsupported collider shape"),
326///     ClosestPoints::WithinMargin(RVec3::X * 0.5, RVec3::X * 1.0),
327/// );
328///
329/// // The shapes are not intersecting and the distance between the closest points exceeds 2.0
330/// assert_eq!(
331///     closest_points(
332///         &collider1,
333///         RVec3::default(),
334///         Quat::default(),
335///         &collider2,
336///         RVec3::X * 5.0,
337///         Quat::default(),
338///         2.0,
339///     )
340///     .expect("Unsupported collider shape"),
341///     ClosestPoints::OutsideMargin,
342/// );
343/// # }
344/// ```
345pub fn closest_points(
346    collider1: &Collider,
347    position1: RVector,
348    rotation1: impl Into<Rot>,
349    collider2: &Collider,
350    position2: RVector,
351    rotation2: impl Into<Rot>,
352    max_distance: f32,
353) -> Result<ClosestPoints, UnsupportedShape> {
354    let rotation1: Rot = rotation1.into();
355    let rotation2: Rot = rotation2.into();
356    let isometry1 = make_pose(position1, rotation1);
357    let isometry2 = make_pose(position2, rotation2);
358
359    parry::query::closest_points(
360        &isometry1,
361        collider1.shape_scaled().0.as_ref(),
362        &isometry2,
363        collider2.shape_scaled().0.as_ref(),
364        max_distance.real(),
365    )
366    .map(|closest_points| match closest_points {
367        parry::query::ClosestPoints::Intersecting => ClosestPoints::Intersecting,
368        parry::query::ClosestPoints::WithinMargin(point1, point2) => {
369            ClosestPoints::WithinMargin(point1, point2)
370        }
371        parry::query::ClosestPoints::Disjoint => ClosestPoints::OutsideMargin,
372    })
373}
374
375/// Computes the minimum distance separating two [`Collider`]s.
376///
377/// Returns `0.0` if the colliders are touching or penetrating, and `Err(UnsupportedShape)`
378/// if either of the collider shapes is not supported.
379///
380/// # Example
381///
382/// ```
383/// # #[cfg(feature = "2d")]
384/// # use avian2d::{collision::collider::contact_query::distance, prelude::*};
385/// # #[cfg(feature = "3d")]
386/// use avian3d::{collision::collider::contact_query::distance, math::RVec3, prelude::*};
387/// use bevy::prelude::*;
388///
389/// # #[cfg(feature = "3d")]
390/// # {
391/// let collider1 = Collider::sphere(0.5);
392/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
393///
394/// // The distance is 1.0
395/// assert_eq!(
396///     distance(
397///         &collider1,
398///         RVec3::default(),
399///         Quat::default(),
400///         &collider2,
401///         RVec3::X * 2.0,
402///         Quat::default(),
403///     )
404///     .expect("Unsupported collider shape"),
405///     1.0,
406/// );
407///
408/// // The colliders are penetrating, so the distance is 0.0
409/// assert_eq!(
410///     distance(
411///         &collider1,
412///         RVec3::default(),
413///         Quat::default(),
414///         &collider2,
415///         RVec3::default(),
416///         Quat::default(),
417///     )
418///     .expect("Unsupported collider shape"),
419///     0.0,
420/// );
421/// # }
422/// ```
423pub fn distance(
424    collider1: &Collider,
425    position1: RVector,
426    rotation1: impl Into<Rot>,
427    collider2: &Collider,
428    position2: RVector,
429    rotation2: impl Into<Rot>,
430) -> Result<f32, UnsupportedShape> {
431    let rotation1: Rot = rotation1.into();
432    let rotation2: Rot = rotation2.into();
433    let isometry1 = make_pose(position1, rotation1);
434    let isometry2 = make_pose(position2, rotation2);
435
436    parry::query::distance(
437        &isometry1,
438        collider1.shape_scaled().0.as_ref(),
439        &isometry2,
440        collider2.shape_scaled().0.as_ref(),
441    )
442    .map(|distance| distance.distance.f32())
443}
444
445/// Tests whether two [`Collider`]s are intersecting each other.
446///
447/// Returns `Err(UnsupportedShape)` if either of the collider shapes is not supported.
448///
449/// # Example
450///
451/// ```
452/// # #[cfg(feature = "2d")]
453/// # use avian2d::{collision::collider::contact_query::intersection_test, prelude::*};
454/// # #[cfg(feature = "3d")]
455/// use avian3d::{collision::collider::contact_query::intersection_test, math::RVec3, prelude::*};
456/// use bevy::prelude::*;
457///
458/// # #[cfg(feature = "3d")]
459/// # {
460/// let collider1 = Collider::sphere(0.5);
461/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
462///
463/// // These colliders should be intersecting
464/// assert_eq!(
465///     intersection_test(
466///         &collider1,
467///         RVec3::default(),
468///         Quat::default(),
469///         &collider2,
470///         RVec3::default(),
471///         Quat::default(),
472///     )
473///     .expect("Unsupported collider shape"),
474///     true,
475/// );
476///
477/// // These colliders shouldn't be intersecting
478/// assert_eq!(
479///     intersection_test(
480///         &collider1,
481///         RVec3::default(),
482///         Quat::default(),
483///         &collider2,
484///         RVec3::X * 5.0,
485///         Quat::default(),
486///     )
487///     .expect("Unsupported collider shape"),
488///     false,
489/// );
490/// # }
491/// ```
492pub fn intersection_test(
493    collider1: &Collider,
494    position1: RVector,
495    rotation1: impl Into<Rot>,
496    collider2: &Collider,
497    position2: RVector,
498    rotation2: impl Into<Rot>,
499) -> Result<bool, UnsupportedShape> {
500    let rotation1: Rot = rotation1.into();
501    let rotation2: Rot = rotation2.into();
502    let isometry1 = make_pose(position1, rotation1);
503    let isometry2 = make_pose(position2, rotation2);
504
505    parry::query::intersection_test(
506        &isometry1,
507        collider1.shape_scaled().0.as_ref(),
508        &isometry2,
509        collider2.shape_scaled().0.as_ref(),
510    )
511    .map(|intersection| intersection.intersecting)
512}
513
514/// The way the [time of impact](time_of_impact) computation was terminated.
515pub type TimeOfImpactStatus = parry::query::details::ShapeCastStatus;
516
517/// The result of a [time of impact](time_of_impact) computation between two moving [`Collider`]s.
518#[derive(Clone, Copy, Debug, PartialEq)]
519pub struct TimeOfImpact {
520    /// The time at which the colliders come into contact.
521    pub time_of_impact: f32,
522    /// The closest point on the first collider, at the time of impact,
523    /// expressed in local space.
524    pub point1: Vector,
525    /// The closest point on the second collider, at the time of impact,
526    /// expressed in local space.
527    pub point2: Vector,
528    /// The outward normal on the first collider, at the time of impact,
529    /// expressed in local space.
530    pub normal1: Vector,
531    /// The outward normal on the second collider, at the time of impact,
532    /// expressed in local space.
533    pub normal2: Vector,
534    /// The way the time of impact computation was terminated.
535    pub status: TimeOfImpactStatus,
536}
537
538/// Computes when two moving [`Collider`]s hit each other for the first time.
539///
540/// Returns `Ok(None)` if the time of impact is greater than `max_time_of_impact`
541/// and `Err(UnsupportedShape)` if either of the collider shapes is not supported.
542///
543/// # Example
544///
545/// ```
546/// # #[cfg(feature = "2d")]
547/// # use avian2d::{collision::collider::contact_query::time_of_impact, prelude::*};
548/// # #[cfg(feature = "3d")]
549/// use avian3d::{collision::collider::contact_query::time_of_impact, math::RVec3, prelude::*};
550/// use bevy::prelude::*;
551///
552/// # #[cfg(feature = "3d")]
553/// # {
554/// let collider1 = Collider::sphere(0.5);
555/// let collider2 = Collider::cuboid(1.0, 1.0, 1.0);
556///
557/// let result = time_of_impact(
558///     &collider1,         // Collider 1
559///     RVec3::NEG_X * 5.0, // Position 1
560///     Quat::default(),    // Rotation 1
561///     Vec3::X,            // Linear velocity 1
562///     &collider2,         // Collider 2
563///     RVec3::X * 5.0,     // Position 2
564///     Quat::default(),    // Rotation 2
565///     Vec3::NEG_X,        // Linear velocity 2
566///     100.0,              // Maximum time of impact
567/// )
568/// .expect("Unsupported collider shape");
569///
570/// assert_eq!(result.unwrap().time_of_impact, 4.5);
571/// # }
572/// ```
573#[allow(clippy::too_many_arguments, clippy::type_complexity)]
574pub fn time_of_impact(
575    collider1: &Collider,
576    position1: RVector,
577    rotation1: impl Into<Rot>,
578    velocity1: Vector,
579    collider2: &Collider,
580    position2: RVector,
581    rotation2: impl Into<Rot>,
582    velocity2: Vector,
583    max_time_of_impact: f32,
584) -> Result<Option<TimeOfImpact>, UnsupportedShape> {
585    let rotation1: Rot = rotation1.into();
586    let rotation2: Rot = rotation2.into();
587
588    let isometry1 = make_pose(position1, rotation1);
589    let isometry2 = make_pose(position2, rotation2);
590
591    parry::query::cast_shapes(
592        &isometry1,
593        velocity1.real(),
594        collider1.shape_scaled().0.as_ref(),
595        &isometry2,
596        velocity2.real(),
597        collider2.shape_scaled().0.as_ref(),
598        ShapeCastOptions {
599            max_time_of_impact: max_time_of_impact.real(),
600            stop_at_penetration: true,
601            ..default()
602        },
603    )
604    .map(|toi| {
605        toi.map(|toi| TimeOfImpact {
606            time_of_impact: toi.time_of_impact.f32(),
607            point1: toi.witness1.f32(),
608            point2: toi.witness2.f32(),
609            normal1: toi.normal1.f32(),
610            normal2: toi.normal2.f32(),
611            status: toi.status,
612        })
613    })
614}