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