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}