parry3d/query/shape_cast/
shape_cast.rs

1use na::Unit;
2
3use crate::math::{Isometry, Point, Real, Vector};
4use crate::query::{DefaultQueryDispatcher, QueryDispatcher, Unsupported};
5use crate::shape::Shape;
6
7#[cfg(feature = "alloc")]
8use crate::partitioning::BvhLeafCost;
9
10/// The status of the time-of-impact computation algorithm.
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub enum ShapeCastStatus {
13    /// The shape-casting algorithm ran out of iterations before achieving convergence.
14    ///
15    /// The content of the `ShapeCastHit` will still be a conservative approximation of the actual result so
16    /// it is often fine to interpret this case as a success.
17    OutOfIterations,
18    /// The shape-casting algorithm converged successfully.
19    Converged,
20    /// Something went wrong during the shape-casting, likely due to numerical instabilities.
21    ///
22    /// The content of the `ShapeCastHit` will still be a conservative approximation of the actual result so
23    /// it is often fine to interpret this case as a success.
24    Failed,
25    /// The two shape already overlap, or are separated by a distance smaller than
26    /// [`ShapeCastOptions::target_distance`] at the time 0.
27    ///
28    /// The witness points and normals provided by the `ShapeCastHit` will have unreliable values unless
29    /// [`ShapeCastOptions::compute_impact_geometry_on_penetration`] was set to `true` when calling
30    /// the time-of-impact function.
31    PenetratingOrWithinTargetDist,
32}
33
34/// The result of a shape casting..
35#[derive(Copy, Clone, Debug)]
36pub struct ShapeCastHit {
37    /// The time at which the objects touch.
38    pub time_of_impact: Real,
39    /// The local-space closest point on the first shape at the time of impact.
40    ///
41    /// This value is unreliable if `status` is [`ShapeCastStatus::PenetratingOrWithinTargetDist`]
42    /// and [`ShapeCastOptions::compute_impact_geometry_on_penetration`] was set to `false`.
43    pub witness1: Point<Real>,
44    /// The local-space closest point on the second shape at the time of impact.
45    ///
46    /// This value is unreliable if `status` is [`ShapeCastStatus::PenetratingOrWithinTargetDist`]
47    /// and both [`ShapeCastOptions::compute_impact_geometry_on_penetration`] was set to `false`
48    /// when calling the time-of-impact function.
49    pub witness2: Point<Real>,
50    /// The local-space outward normal on the first shape at the time of impact.
51    ///
52    /// This value is unreliable if `status` is [`ShapeCastStatus::PenetratingOrWithinTargetDist`]
53    /// and both [`ShapeCastOptions::compute_impact_geometry_on_penetration`] was set to `false`
54    /// when calling the time-of-impact function.
55    pub normal1: Unit<Vector<Real>>,
56    /// The local-space outward normal on the second shape at the time of impact.
57    ///
58    /// This value is unreliable if `status` is [`ShapeCastStatus::PenetratingOrWithinTargetDist`]
59    /// and both [`ShapeCastOptions::compute_impact_geometry_on_penetration`] was set to `false`
60    /// when calling the time-of-impact function.
61    pub normal2: Unit<Vector<Real>>,
62    /// The way the shape-casting algorithm terminated.
63    pub status: ShapeCastStatus,
64}
65
66impl ShapeCastHit {
67    /// Swaps every data of this shape-casting result such that the role of both shapes are swapped.
68    ///
69    /// In practice, this makes it so that `self.witness1` and `self.normal1` are swapped with
70    /// `self.witness2` and `self.normal2`.
71    pub fn swapped(self) -> Self {
72        Self {
73            time_of_impact: self.time_of_impact,
74            witness1: self.witness2,
75            witness2: self.witness1,
76            normal1: self.normal2,
77            normal2: self.normal1,
78            status: self.status,
79        }
80    }
81
82    /// Transform `self.witness1` and `self.normal1` by `pos`.
83    pub fn transform1_by(&self, pos: &Isometry<Real>) -> Self {
84        Self {
85            time_of_impact: self.time_of_impact,
86            witness1: pos * self.witness1,
87            witness2: self.witness2,
88            normal1: pos * self.normal1,
89            normal2: self.normal2,
90            status: self.status,
91        }
92    }
93}
94
95#[cfg(feature = "alloc")]
96impl BvhLeafCost for ShapeCastHit {
97    #[inline]
98    fn cost(&self) -> Real {
99        self.time_of_impact
100    }
101}
102
103/// Configuration for controlling the behavior of time-of-impact (i.e. shape-casting) calculations.
104#[derive(Copy, Clone, Debug, PartialEq)]
105pub struct ShapeCastOptions {
106    /// The maximum time-of-impacts that can be computed.
107    ///
108    /// Any impact occurring after this time will be ignored.
109    pub max_time_of_impact: Real,
110    /// The shapes will be considered as impacting as soon as their distance is smaller or
111    /// equal to this target distance. Must be positive or zero.
112    ///
113    /// If the shapes are separated by a distance smaller than `target_distance` at time 0, the
114    /// calculated witness points and normals are only reliable if
115    /// [`Self::compute_impact_geometry_on_penetration`] is set to `true`.
116    pub target_distance: Real,
117    /// If `false`, the time-of-impact algorithm will automatically discard any impact at time
118    /// 0 where the velocity is separating (i.e., the relative velocity is such that the distance
119    /// between the objects projected on the impact normal is increasing through time).
120    pub stop_at_penetration: bool,
121    /// If `true`, witness points and normals will be calculated even when the time-of-impact is 0.
122    pub compute_impact_geometry_on_penetration: bool,
123}
124
125impl ShapeCastOptions {
126    // Constructor for the most common use-case.
127    /// Crates a [`ShapeCastOptions`] with the default values except for the maximum time of impact.
128    pub fn with_max_time_of_impact(max_time_of_impact: Real) -> Self {
129        Self {
130            max_time_of_impact,
131            ..Default::default()
132        }
133    }
134}
135
136impl Default for ShapeCastOptions {
137    fn default() -> Self {
138        Self {
139            max_time_of_impact: Real::MAX,
140            target_distance: 0.0,
141            stop_at_penetration: true,
142            compute_impact_geometry_on_penetration: true,
143        }
144    }
145}
146
147/// Computes the smallest time when two shapes under translational movement are separated by a
148/// distance smaller or equal to `distance`.
149///
150/// Returns `0.0` if the objects are touching or closer than `options.target_distance`,
151/// or penetrating.
152pub fn cast_shapes(
153    pos1: &Isometry<Real>,
154    vel1: &Vector<Real>,
155    g1: &dyn Shape,
156    pos2: &Isometry<Real>,
157    vel2: &Vector<Real>,
158    g2: &dyn Shape,
159    options: ShapeCastOptions,
160) -> Result<Option<ShapeCastHit>, Unsupported> {
161    let pos12 = pos1.inv_mul(pos2);
162    let vel12 = pos1.inverse_transform_vector(&(vel2 - vel1));
163    DefaultQueryDispatcher.cast_shapes(&pos12, &vel12, g1, g2, options)
164}