1use super::{make_shape_proxy, Shape, ShapeGeometry};
10use crate::compound::{
11 get_compound_child, make_compound_child_sweep, query_compound, ChildGeometry,
12};
13use crate::constants::{linear_slop, speculative_distance};
14use crate::core::NULL_INDEX;
15use crate::distance::{
16 get_sweep_transform, make_proxy, time_of_impact, ShapeProxy, Sweep, ToiInput, ToiOutput,
17};
18use crate::geometry::{compute_swept_capsule_aabb, compute_swept_sphere_aabb, ShapeType};
19use crate::height_field::query_height_field;
20use crate::hull::{compute_swept_hull_aabb, get_hull_points};
21use crate::math_functions::{
22 aabb_transform, cross, dot, inv_transform_point, invert_transform, max_float, mul_transforms,
23 normalize, rotate_vector, sub, transform_point, Aabb, Transform, Vec3, VEC3_ZERO,
24};
25use crate::mesh::{query_mesh, Mesh};
26
27pub fn compute_swept_shape_aabb(shape: &Shape, sweep: &Sweep, time: f32) -> Aabb {
30 debug_assert!((0.0..=1.0).contains(&time));
31 let xf1 = Transform {
32 p: sub(sweep.c1, rotate_vector(sweep.q1, sweep.local_center)),
33 q: sweep.q1,
34 };
35 let xf2 = get_sweep_transform(sweep, time);
36
37 match &shape.geometry {
38 ShapeGeometry::Capsule(capsule) => compute_swept_capsule_aabb(capsule, xf1, xf2),
39 ShapeGeometry::Hull(hull) => compute_swept_hull_aabb(hull, xf1, xf2),
40 ShapeGeometry::Sphere(sphere) => compute_swept_sphere_aabb(sphere, xf1, xf2),
41 _ => {
42 debug_assert!(false, "compute_swept_shape_aabb is for convex shapes only");
43 Aabb {
44 lower_bound: xf1.p,
45 upper_bound: xf1.p,
46 }
47 }
48 }
49}
50
51struct MeshImpactContext {
53 toi_input: ToiInput,
54 toi_output: ToiOutput,
55 local_centroid_b: Vec3,
57 mesh_local_centroid_b1: Vec3,
59 mesh_local_centroid_b2: Vec3,
61 fallback_radius: f32,
62 is_sensor: bool,
63}
64
65fn mesh_time_of_impact_fcn(a: Vec3, b: Vec3, c: Vec3, context: &mut MeshImpactContext) -> bool {
67 let c1 = context.mesh_local_centroid_b1;
69 let c2 = context.mesh_local_centroid_b2;
70
71 let n = normalize(cross(sub(b, a), sub(c, a)));
72 let offset1 = dot(n, sub(c1, a));
73 let offset2 = dot(n, sub(c2, a));
74
75 if offset1 < 0.0 {
76 return true;
78 }
79
80 if !context.is_sensor
81 && offset1 - offset2 < context.fallback_radius
82 && offset2 > context.fallback_radius
83 {
84 return true;
86 }
87
88 let triangle = [a, b, c];
89 context.toi_input.proxy_a = make_proxy(&triangle, 0.0);
90
91 let mut output = time_of_impact(&context.toi_input);
92
93 if 0.0 < output.fraction && output.fraction < context.toi_input.max_fraction {
96 context.toi_output = output;
97 context.toi_input.max_fraction = output.fraction;
98 } else if output.fraction == 0.0 {
99 let mut fallback_input = context.toi_input;
101 fallback_input.proxy_b = make_proxy(
102 &[context.local_centroid_b],
103 context.fallback_radius + linear_slop(),
104 );
105 output = time_of_impact(&fallback_input);
106
107 if 0.0 < output.fraction && output.fraction < context.toi_input.max_fraction {
108 context.toi_output = output;
109 context.toi_input.max_fraction = output.fraction;
110 context.toi_output.used_fallback = true;
111 }
112 }
113
114 true
116}
117
118struct CompoundImpactContext {
120 toi_input: ToiInput,
121 toi_output: ToiOutput,
122 compound_transform: Transform,
123 local_sweep_bounds_b: Aabb,
125 local_centroid_b: Vec3,
127 fallback_radius: f32,
128}
129
130fn compound_time_of_impact_fcn(
132 compound: &crate::compound::CompoundData,
133 child_index: i32,
134 context: &mut CompoundImpactContext,
135) -> bool {
136 let child = get_compound_child(compound, child_index);
137
138 context.toi_input.sweep_a =
139 make_compound_child_sweep(context.compound_transform, child.transform);
140
141 let output = match child.geometry {
142 ChildGeometry::Capsule(capsule) => {
143 context.toi_input.proxy_a =
144 make_proxy(&[capsule.center1, capsule.center2], capsule.radius);
145 time_of_impact(&context.toi_input)
146 }
147 ChildGeometry::Hull(hull) => {
148 context.toi_input.proxy_a = make_proxy(get_hull_points(hull), 0.0);
149 time_of_impact(&context.toi_input)
150 }
151 ChildGeometry::Mesh(mesh) => {
152 let mut mesh_context = MeshImpactContext {
153 toi_input: context.toi_input,
154 toi_output: ToiOutput::default(),
155 local_centroid_b: context.local_centroid_b,
156 mesh_local_centroid_b1: VEC3_ZERO,
157 mesh_local_centroid_b2: VEC3_ZERO,
158 fallback_radius: context.fallback_radius,
159 is_sensor: false,
160 };
161
162 let mesh_world_transform = mul_transforms(context.compound_transform, child.transform);
163
164 let sweep_b = &context.toi_input.sweep_b;
165 let xf_b1 = Transform {
166 p: sub(sweep_b.c1, rotate_vector(sweep_b.q1, sweep_b.local_center)),
167 q: sweep_b.q1,
168 };
169 let xf_b2 = Transform {
170 p: sub(sweep_b.c2, rotate_vector(sweep_b.q2, sweep_b.local_center)),
171 q: sweep_b.q2,
172 };
173
174 mesh_context.mesh_local_centroid_b1 = inv_transform_point(
175 mesh_world_transform,
176 transform_point(xf_b1, mesh_context.local_centroid_b),
177 );
178 mesh_context.mesh_local_centroid_b2 = inv_transform_point(
179 mesh_world_transform,
180 transform_point(xf_b2, mesh_context.local_centroid_b),
181 );
182
183 let local_bounds = aabb_transform(
185 invert_transform(child.transform),
186 context.local_sweep_bounds_b,
187 );
188
189 query_mesh(&mesh, local_bounds, |a, b, c, _triangle_index| {
190 mesh_time_of_impact_fcn(a, b, c, &mut mesh_context)
191 });
192
193 mesh_context.toi_output
194 }
195 ChildGeometry::Sphere(sphere) => {
196 context.toi_input.proxy_a = make_proxy(&[sphere.center], sphere.radius);
197 time_of_impact(&context.toi_input)
198 }
199 };
200
201 if 0.0 < output.fraction && output.fraction < context.toi_input.max_fraction {
202 context.toi_output = output;
203 context.toi_input.max_fraction = output.fraction;
204 }
205
206 context.toi_input.proxy_a = ShapeProxy::default();
208
209 true
211}
212
213pub fn shape_time_of_impact(
217 shape_a: &Shape,
218 shape_b: &Shape,
219 sweep_a: &Sweep,
220 sweep_b: &Sweep,
221 max_fraction: f32,
222) -> ToiOutput {
223 use super::{compute_shape_extent, get_shape_centroid};
224
225 let is_sensor = shape_a.sensor_index != NULL_INDEX;
226 let type_a = shape_a.shape_type();
227
228 if type_a == ShapeType::Compound {
229 let ShapeGeometry::Compound(compound) = &shape_a.geometry else {
230 unreachable!();
231 };
232
233 let local_centroid_b = get_shape_centroid(shape_b);
234 let extents = compute_shape_extent(shape_b, local_centroid_b);
235 let fallback_radius = max_float(0.75 * extents.min_extent, speculative_distance());
236
237 let compound_transform = Transform {
238 p: sweep_a.c1,
239 q: sweep_a.q1,
240 };
241
242 let bounds = compute_swept_shape_aabb(shape_b, sweep_b, max_fraction);
244 let local_bounds = aabb_transform(invert_transform(compound_transform), bounds);
245
246 let mut context = CompoundImpactContext {
247 toi_input: ToiInput {
248 proxy_a: ShapeProxy::default(),
249 proxy_b: make_shape_proxy(shape_b),
250 sweep_a: Sweep::default(),
251 sweep_b: *sweep_b,
252 max_fraction,
253 },
254 toi_output: ToiOutput::default(),
255 compound_transform,
256 local_sweep_bounds_b: local_bounds,
257 local_centroid_b,
258 fallback_radius,
259 };
260
261 query_compound(compound, local_bounds, |compound, child_index| {
262 compound_time_of_impact_fcn(compound, child_index, &mut context)
263 });
264
265 return context.toi_output;
266 }
267
268 if type_a == ShapeType::Height || type_a == ShapeType::Mesh {
269 let local_centroid_b = get_shape_centroid(shape_b);
271
272 let xf_a = Transform {
274 p: sub(sweep_a.c1, rotate_vector(sweep_a.q1, sweep_a.local_center)),
275 q: sweep_a.q1,
276 };
277 let xf_b1 = Transform {
278 p: sub(sweep_b.c1, rotate_vector(sweep_b.q1, sweep_b.local_center)),
279 q: sweep_b.q1,
280 };
281 let xf_b2 = Transform {
282 p: sub(sweep_b.c2, rotate_vector(sweep_b.q2, sweep_b.local_center)),
283 q: sweep_b.q2,
284 };
285
286 let extents = compute_shape_extent(shape_b, local_centroid_b);
287 let fallback_radius = max_float(0.5 * extents.min_extent, linear_slop());
288
289 let bounds = compute_swept_shape_aabb(shape_b, sweep_b, max_fraction);
291 let local_bounds = aabb_transform(invert_transform(xf_a), bounds);
292
293 let mut context = MeshImpactContext {
294 toi_input: ToiInput {
295 proxy_a: ShapeProxy {
296 count: 3,
297 ..ShapeProxy::default()
298 },
299 proxy_b: make_shape_proxy(shape_b),
300 sweep_a: *sweep_a,
301 sweep_b: *sweep_b,
302 max_fraction,
303 },
304 toi_output: ToiOutput::default(),
305 local_centroid_b,
306 mesh_local_centroid_b1: inv_transform_point(
307 xf_a,
308 transform_point(xf_b1, local_centroid_b),
309 ),
310 mesh_local_centroid_b2: inv_transform_point(
311 xf_a,
312 transform_point(xf_b2, local_centroid_b),
313 ),
314 fallback_radius,
315 is_sensor,
316 };
317
318 match &shape_a.geometry {
319 ShapeGeometry::Mesh { data, scale } => {
320 let mesh = Mesh {
321 data,
322 scale: *scale,
323 };
324 query_mesh(&mesh, local_bounds, |a, b, c, _triangle_index| {
325 mesh_time_of_impact_fcn(a, b, c, &mut context)
326 });
327 }
328 ShapeGeometry::HeightField(hf) => {
329 query_height_field(hf, local_bounds, |a, b, c, _triangle_index| {
330 mesh_time_of_impact_fcn(a, b, c, &mut context)
331 });
332 }
333 _ => unreachable!(),
334 }
335
336 return context.toi_output;
337 }
338
339 debug_assert!(
340 shape_b.shape_type() != ShapeType::Compound
341 && shape_b.shape_type() != ShapeType::Mesh
342 && shape_b.shape_type() != ShapeType::Height
343 );
344
345 let input = ToiInput {
346 proxy_a: make_shape_proxy(shape_a),
347 proxy_b: make_shape_proxy(shape_b),
348 sweep_a: *sweep_a,
349 sweep_b: *sweep_b,
350 max_fraction,
351 };
352 time_of_impact(&input)
353}