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