1use super::TOIEntry;
2use crate::dynamics::{IslandManager, RigidBodyHandle, RigidBodySet};
3use crate::geometry::{ColliderParent, ColliderSet, CollisionEvent, NarrowPhase};
4use crate::math::Real;
5use crate::parry::utils::SortedPair;
6use crate::pipeline::{EventHandler, QueryFilter, QueryPipeline};
7use crate::prelude::{ActiveEvents, CollisionEventFlags};
8use parry::partitioning::{Bvh, BvhBuildStrategy};
9use parry::utils::hashmap::HashMap;
10use std::collections::BinaryHeap;
11
12pub enum PredictedImpacts {
13 Impacts(HashMap<RigidBodyHandle, Real>),
14 ImpactsAfterEndTime(Real),
15 NoImpacts,
16}
17
18#[derive(Clone)]
20#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
21pub struct CCDSolver {
22 #[cfg_attr(feature = "serde-serialize", serde(skip))]
26 bvh: Bvh,
27}
28
29impl Default for CCDSolver {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl CCDSolver {
36 pub fn new() -> Self {
38 Self { bvh: Bvh::new() }
39 }
40
41 pub fn clamp_motions(&self, dt: Real, bodies: &mut RigidBodySet, impacts: &PredictedImpacts) {
45 if let PredictedImpacts::Impacts(tois) = impacts {
46 for (handle, toi) in tois {
47 let rb = bodies.index_mut_internal(*handle);
48 let local_com = &rb.mprops.local_mprops.local_com;
49
50 let min_toi = (rb.ccd.ccd_thickness
51 * 0.15
52 * crate::utils::inv(rb.ccd.max_point_velocity(&rb.integrated_vels)))
53 .min(dt);
54 let new_pos =
62 rb.integrated_vels
63 .integrate(toi.max(min_toi), &rb.pos.position, local_com);
64 rb.pos.next_position = new_pos;
65 }
66 }
67 }
68
69 pub fn update_ccd_active_flags(
73 &self,
74 islands: &IslandManager,
75 bodies: &mut RigidBodySet,
76 dt: Real,
77 include_forces: bool,
78 ) -> bool {
79 let mut ccd_active = false;
80
81 for handle in islands.active_dynamic_bodies() {
83 let rb = bodies.index_mut_internal(*handle);
84
85 if rb.ccd.ccd_enabled {
86 let forces = if include_forces {
87 Some(&rb.forces)
88 } else {
89 None
90 };
91 let moving_fast = rb.ccd.is_moving_fast(dt, &rb.integrated_vels, forces);
92 rb.ccd.ccd_active = moving_fast;
93 ccd_active = ccd_active || moving_fast;
94 }
95 }
96
97 ccd_active
98 }
99
100 #[profiling::function]
102 pub fn find_first_impact(
103 &mut self,
104 dt: Real,
105 islands: &IslandManager,
106 bodies: &RigidBodySet,
107 colliders: &ColliderSet,
108 narrow_phase: &NarrowPhase,
109 ) -> Option<Real> {
110 self.bvh = Bvh::from_iter(
112 BvhBuildStrategy::Binned,
113 colliders.iter_enabled().map(|(co_handle, co)| {
114 let id = co_handle.into_raw_parts().0;
115 if let Some(co_parent) = co.parent {
116 let rb = &bodies[co_parent.handle];
117 let predicted_pos = rb
118 .pos
119 .integrate_forces_and_velocities(dt, &rb.forces, &rb.vels, &rb.mprops);
120
121 let next_position = predicted_pos * co_parent.pos_wrt_parent;
122 (
123 id as usize,
124 co.shape.compute_swept_aabb(&co.pos, &next_position),
125 )
126 } else {
127 (id as usize, co.shape.compute_aabb(&co.pos))
128 }
129 }),
130 );
131
132 let query_pipeline = QueryPipeline {
133 dispatcher: narrow_phase.query_dispatcher(),
135 bvh: &self.bvh,
136 bodies,
137 colliders,
138 filter: QueryFilter::default(),
139 };
140
141 let mut pairs_seen = HashMap::default();
142 let mut min_toi = dt;
143
144 for handle in islands.active_dynamic_bodies() {
145 let rb1 = &bodies[*handle];
146
147 if rb1.ccd.ccd_active {
148 let predicted_body_pos1 = rb1.pos.integrate_forces_and_velocities(
149 dt,
150 &rb1.forces,
151 &rb1.integrated_vels,
152 &rb1.mprops,
153 );
154
155 for ch1 in &rb1.colliders.0 {
156 let co1 = &colliders[*ch1];
157 let co1_parent = co1
158 .parent
159 .as_ref()
160 .expect("Could not find the ColliderParent component.");
161
162 if co1.is_sensor() {
163 continue; }
165
166 let predicted_collider_pos1 = predicted_body_pos1 * co1_parent.pos_wrt_parent;
167 let aabb1 = co1
168 .shape
169 .compute_swept_aabb(&co1.pos, &predicted_collider_pos1);
170
171 for (ch2, _) in query_pipeline.intersect_aabb_conservative(aabb1) {
172 if *ch1 == ch2 {
173 continue;
175 }
176
177 if pairs_seen
178 .insert(
179 SortedPair::new(ch1.into_raw_parts().0, ch2.into_raw_parts().0),
180 (),
181 )
182 .is_none()
183 {
184 let co1 = &colliders[*ch1];
185 let co2 = &colliders[ch2];
186
187 let bh1 = co1.parent.map(|p| p.handle);
188 let bh2 = co2.parent.map(|p| p.handle);
189
190 if bh1 == bh2 || (co1.is_sensor() || co2.is_sensor()) || !co1.flags.collision_groups.test(co2.flags.collision_groups) || !co1.flags.solver_groups.test(co2.flags.solver_groups)
195 {
197 continue;
198 }
199
200 let smallest_dist = narrow_phase
201 .contact_pair(*ch1, ch2)
202 .and_then(|p| p.find_deepest_contact())
203 .map(|c| c.1.dist)
204 .unwrap_or(0.0);
205
206 let rb2 = bh2.and_then(|h| bodies.get(h));
207
208 if let Some(toi) = TOIEntry::try_from_colliders(
209 narrow_phase.query_dispatcher(),
210 *ch1,
211 ch2,
212 co1,
213 co2,
214 Some(rb1),
215 rb2,
216 None,
217 None,
218 0.0,
219 min_toi,
220 smallest_dist,
221 ) {
222 min_toi = min_toi.min(toi.toi);
223 }
224 }
225 }
226 }
227 }
228 }
229
230 if min_toi < dt { Some(min_toi) } else { None }
231 }
232
233 #[profiling::function]
235 pub fn predict_impacts_at_next_positions(
236 &mut self,
237 dt: Real,
238 islands: &IslandManager,
239 bodies: &RigidBodySet,
240 colliders: &ColliderSet,
241 narrow_phase: &NarrowPhase,
242 events: &dyn EventHandler,
243 ) -> PredictedImpacts {
244 let mut frozen = HashMap::<_, Real>::default();
245 let mut all_toi = BinaryHeap::new();
246 let mut pairs_seen = HashMap::default();
247 let mut min_overstep = dt;
248
249 self.bvh = Bvh::from_iter(
251 BvhBuildStrategy::Binned,
252 colliders.iter_enabled().map(|(co_handle, co)| {
253 let id = co_handle.into_raw_parts().0;
254 if let Some(co_parent) = co.parent {
255 let rb_next_pos = &bodies[co_parent.handle].pos.next_position;
256 let next_position = rb_next_pos * co_parent.pos_wrt_parent;
257 (
258 id as usize,
259 co.shape.compute_swept_aabb(&co.pos, &next_position),
260 )
261 } else {
262 (id as usize, co.shape.compute_aabb(&co.pos))
263 }
264 }),
265 );
266
267 let query_pipeline = QueryPipeline {
268 dispatcher: narrow_phase.query_dispatcher(),
269 bvh: &self.bvh,
270 bodies,
271 colliders,
272 filter: QueryFilter::default(),
273 };
274
275 for handle in islands.active_dynamic_bodies() {
282 let rb1 = &bodies[*handle];
283
284 if rb1.ccd.ccd_active {
285 let predicted_body_pos1 = rb1.pos.integrate_forces_and_velocities(
286 dt,
287 &rb1.forces,
288 &rb1.integrated_vels,
289 &rb1.mprops,
290 );
291
292 for ch1 in &rb1.colliders.0 {
293 let co1 = &colliders[*ch1];
294 let co_parent1 = co1
295 .parent
296 .as_ref()
297 .expect("Could not find the ColliderParent component.");
298
299 let predicted_collider_pos1 = predicted_body_pos1 * co_parent1.pos_wrt_parent;
300 let aabb1 = co1
301 .shape
302 .compute_swept_aabb(&co1.pos, &predicted_collider_pos1);
303
304 for (ch2, _) in query_pipeline.intersect_aabb_conservative(aabb1) {
305 if *ch1 == ch2 {
306 continue;
308 }
309
310 if pairs_seen
311 .insert(
312 SortedPair::new(ch1.into_raw_parts().0, ch2.into_raw_parts().0),
313 (),
314 )
315 .is_none()
316 {
317 let co1 = &colliders[*ch1];
318 let co2 = &colliders[ch2];
319
320 let bh1 = co1.parent.map(|p| p.handle);
321 let bh2 = co2.parent.map(|p| p.handle);
322
323 if bh1 == bh2
325 || !co1.flags.collision_groups.test(co2.flags.collision_groups)
326 {
327 continue;
328 }
329
330 let smallest_dist = narrow_phase
331 .contact_pair(*ch1, ch2)
332 .and_then(|p| p.find_deepest_contact())
333 .map(|c| c.1.dist)
334 .unwrap_or(0.0);
335
336 let rb1 = bh1.map(|h| &bodies[h]);
337 let rb2 = bh2.map(|h| &bodies[h]);
338
339 if let Some(toi) = TOIEntry::try_from_colliders(
340 query_pipeline.dispatcher,
341 *ch1,
342 ch2,
343 co1,
344 co2,
345 rb1,
346 rb2,
347 None,
348 None,
349 0.0,
350 min_overstep,
353 smallest_dist,
354 ) {
355 if toi.toi > dt {
356 min_overstep = min_overstep.min(toi.toi);
357 } else {
358 min_overstep = dt;
359 all_toi.push(toi);
360 }
361 }
362 }
363 }
364 }
365 }
366 }
367
368 if min_overstep == dt && all_toi.is_empty() {
374 return PredictedImpacts::NoImpacts;
375 } else if min_overstep > dt {
376 return PredictedImpacts::ImpactsAfterEndTime(min_overstep);
377 }
378
379 let mut pseudo_intersections_to_check = vec![];
382
383 while let Some(toi) = all_toi.pop() {
384 assert!(toi.toi <= dt);
385
386 let rb1 = toi.b1.and_then(|b| bodies.get(b));
387 let rb2 = toi.b2.and_then(|b| bodies.get(b));
388
389 let mut colliders_to_check = Vec::new();
390 let should_freeze1 = rb1.is_some()
391 && rb1.unwrap().ccd.ccd_active
392 && !frozen.contains_key(&toi.b1.unwrap());
393 let should_freeze2 = rb2.is_some()
394 && rb2.unwrap().ccd.ccd_active
395 && !frozen.contains_key(&toi.b2.unwrap());
396
397 if !should_freeze1 && !should_freeze2 {
398 continue;
399 }
400
401 if toi.is_pseudo_intersection_test {
402 if should_freeze1 || should_freeze2 {
406 pseudo_intersections_to_check.push(toi);
410 }
411 continue;
412 }
413
414 if should_freeze1 {
415 let _ = frozen.insert(toi.b1.unwrap(), toi.toi);
416 colliders_to_check.extend_from_slice(&rb1.unwrap().colliders.0);
417 }
418
419 if should_freeze2 {
420 let _ = frozen.insert(toi.b2.unwrap(), toi.toi);
421 colliders_to_check.extend_from_slice(&rb2.unwrap().colliders.0);
422 }
423
424 let start_time = toi.toi;
425
426 for ch1 in &colliders_to_check {
429 let co1 = &colliders[*ch1];
430 let co1_parent = co1.parent.as_ref().unwrap();
431 let rb1 = &bodies[co1_parent.handle];
432
433 let co_next_pos1 = rb1.pos.next_position * co1_parent.pos_wrt_parent;
434 let aabb = co1.shape.compute_swept_aabb(&co1.pos, &co_next_pos1);
435
436 for (ch2, _) in query_pipeline.intersect_aabb_conservative(aabb) {
437 let co2 = &colliders[ch2];
438
439 let bh1 = co1.parent.map(|p| p.handle);
440 let bh2 = co2.parent.map(|p| p.handle);
441
442 if bh1 == bh2 || !co1.flags.collision_groups.test(co2.flags.collision_groups) {
444 continue;
445 }
446
447 let frozen1 = bh1.and_then(|h| frozen.get(&h));
448 let frozen2 = bh2.and_then(|h| frozen.get(&h));
449
450 let rb1 = bh1.and_then(|h| bodies.get(h));
451 let rb2 = bh2.and_then(|h| bodies.get(h));
452
453 if (frozen1.is_some() || !rb1.map(|b| b.ccd.ccd_active).unwrap_or(false))
454 && (frozen2.is_some() || !rb2.map(|b| b.ccd.ccd_active).unwrap_or(false))
455 {
456 continue;
458 }
459
460 let smallest_dist = narrow_phase
461 .contact_pair(*ch1, ch2)
462 .and_then(|p| p.find_deepest_contact())
463 .map(|c| c.1.dist)
464 .unwrap_or(0.0);
465
466 if let Some(toi) = TOIEntry::try_from_colliders(
467 query_pipeline.dispatcher,
468 *ch1,
469 ch2,
470 co1,
471 co2,
472 rb1,
473 rb2,
474 frozen1.copied(),
475 frozen2.copied(),
476 start_time,
477 dt,
478 smallest_dist,
479 ) {
480 all_toi.push(toi);
481 }
482 }
483 }
484 }
485
486 for toi in pseudo_intersections_to_check {
487 let co1 = &colliders[toi.c1];
495 let co2 = &colliders[toi.c2];
496
497 if !co1.is_sensor() && !co2.is_sensor() {
498 continue;
506 }
507
508 let co_next_pos1 = if let Some(b1) = toi.b1 {
509 let co_parent1: &ColliderParent = co1.parent.as_ref().unwrap();
510 let rb1 = &bodies[b1];
511 let local_com1 = &rb1.mprops.local_mprops.local_com;
512 let frozen1 = frozen.get(&b1);
513 let pos1 = frozen1
514 .map(|t| {
515 rb1.integrated_vels
516 .integrate(*t, &rb1.pos.position, local_com1)
517 })
518 .unwrap_or(rb1.pos.next_position);
519 pos1 * co_parent1.pos_wrt_parent
520 } else {
521 co1.pos.0
522 };
523
524 let co_next_pos2 = if let Some(b2) = toi.b2 {
525 let co_parent2: &ColliderParent = co2.parent.as_ref().unwrap();
526 let rb2 = &bodies[b2];
527 let local_com2 = &rb2.mprops.local_mprops.local_com;
528 let frozen2 = frozen.get(&b2);
529 let pos2 = frozen2
530 .map(|t| {
531 rb2.integrated_vels
532 .integrate(*t, &rb2.pos.position, local_com2)
533 })
534 .unwrap_or(rb2.pos.next_position);
535 pos2 * co_parent2.pos_wrt_parent
536 } else {
537 co2.pos.0
538 };
539
540 let prev_coll_pos12 = co1.pos.inv_mul(&co2.pos);
541 let next_coll_pos12 = co_next_pos1.inv_mul(&co_next_pos2);
542
543 let intersect_before = query_pipeline
544 .dispatcher
545 .intersection_test(&prev_coll_pos12, co1.shape.as_ref(), co2.shape.as_ref())
546 .unwrap_or(false);
547
548 let intersect_after = query_pipeline
549 .dispatcher
550 .intersection_test(&next_coll_pos12, co1.shape.as_ref(), co2.shape.as_ref())
551 .unwrap_or(false);
552
553 if !intersect_before
554 && !intersect_after
555 && (co1.flags.active_events | co2.flags.active_events)
556 .contains(ActiveEvents::COLLISION_EVENTS)
557 {
558 events.handle_collision_event(
560 bodies,
561 colliders,
562 CollisionEvent::Started(toi.c1, toi.c2, CollisionEventFlags::SENSOR),
563 None,
564 );
565 events.handle_collision_event(
566 bodies,
567 colliders,
568 CollisionEvent::Stopped(toi.c1, toi.c2, CollisionEventFlags::SENSOR),
569 None,
570 );
571 }
572 }
573
574 PredictedImpacts::Impacts(frozen)
575 }
576}