1use std::collections::{HashMap, HashSet};
2use std::ops::{Deref, DerefMut};
3use std::sync::{
4 atomic::{AtomicUsize, Ordering},
5 Arc, Mutex,
6};
7
8use rapier3d::prelude::*;
9
10#[cfg(not(target_arch = "wasm32"))]
11use std::time::Instant;
12
13#[cfg(target_arch = "wasm32")]
14#[derive(Clone, Copy)]
15struct Instant;
16
17#[cfg(target_arch = "wasm32")]
18impl Instant {
19 fn now() -> Self {
20 Self
21 }
22
23 fn elapsed(&self) -> std::time::Duration {
24 std::time::Duration::ZERO
25 }
26}
27
28use crate::types::Vec3 as SolverVec3;
29use crate::{ScenarioDesc, SolverSettings, Vec3};
30
31use super::body_tracker::SplitEditStats;
32use super::destructible::{DestructibleSet, SplitCohort, StepResult};
33use super::fracture_policy::FracturePolicy;
34use super::optimization::OptimizationResult;
35use super::resimulation::BodySnapshots;
36use super::{
37 DebrisCleanupOptions, DebrisCollisionMode, SleepThresholdOptions, SmallBodyDampingOptions,
38};
39
40#[derive(Clone, Copy, Debug)]
41pub struct ContactImpactOptions {
42 pub enabled: bool,
43 pub min_total_impulse: f32,
44 pub min_external_speed: f32,
45 pub min_internal_speed: f32,
46 pub cooldown_secs: f32,
47 pub force_scale: f32,
48 pub max_force_magnitude: f32,
49 pub splash_radius: f32,
50 pub splash_falloff_exponent: f32,
51 pub internal_contact_scale: f32,
52}
53
54impl Default for ContactImpactOptions {
55 fn default() -> Self {
56 Self {
57 enabled: true,
58 min_total_impulse: 8.0,
59 min_external_speed: 0.75,
60 min_internal_speed: 0.25,
61 cooldown_secs: 0.12,
62 force_scale: 30.0,
63 max_force_magnitude: f32::INFINITY,
64 splash_radius: 2.0,
65 splash_falloff_exponent: 2.0,
66 internal_contact_scale: 0.5,
67 }
68 }
69}
70
71#[derive(Clone, Copy, Debug)]
72pub struct GracePeriodOptions {
73 pub sibling_steps: u32,
74 pub impact_source_steps: u32,
75}
76
77impl Default for GracePeriodOptions {
78 fn default() -> Self {
79 Self {
80 sibling_steps: 1,
81 impact_source_steps: 2,
82 }
83 }
84}
85
86#[derive(Clone, Copy, Debug, Default)]
87pub struct DestructionRuntimeOptions {
88 pub contact_impacts: ContactImpactOptions,
89 pub grace: GracePeriodOptions,
90 pub destructible: DestructibleRuntimeOptions,
91}
92
93#[derive(Clone, Copy, Debug, Default)]
94pub struct DestructibleRuntimeOptions {
95 pub sleep_thresholds: Option<SleepThresholdOptions>,
96 pub small_body_damping: Option<SmallBodyDampingOptions>,
97 pub debris_cleanup: Option<DebrisCleanupOptions>,
98 pub debris_collision_mode: Option<DebrisCollisionMode>,
99 pub split_child_recentering_enabled: Option<bool>,
100 pub split_child_velocity_fit_enabled: Option<bool>,
101 pub skip_single_bodies: Option<bool>,
102}
103
104pub struct RapierWorldAccess<'a> {
111 pub bodies: &'a mut RigidBodySet,
112 pub colliders: &'a mut ColliderSet,
113 pub island_manager: &'a mut IslandManager,
114 pub broad_phase: &'a mut BroadPhaseBvh,
115 pub narrow_phase: &'a mut NarrowPhase,
116 pub impulse_joints: &'a mut ImpulseJointSet,
117 pub multibody_joints: &'a mut MultibodyJointSet,
118 pub ccd_solver: &'a mut CCDSolver,
119}
120
121#[derive(Clone, Debug, Default)]
122pub struct FrameResult {
123 pub rapier_passes: u32,
124 pub contact_pairs: usize,
125 pub active_contact_pairs: usize,
126 pub contact_manifolds: usize,
127 pub accepted_impacts: usize,
128 pub rejected_below_impulse: usize,
129 pub rejected_below_speed: usize,
130 pub rejected_cooldown: usize,
131 pub support_contacts: usize,
132 pub fractures: usize,
133 pub new_bodies: usize,
134 pub split_events: usize,
135 pub split_edits: SplitEditStats,
136 pub split_sanitize_ms: f32,
137 pub split_estimate_ms: f32,
138 pub split_cohorts: Vec<SplitCohort>,
139 pub optimization: OptimizationResult,
140 pub resim_restore_ms: f32,
141 pub resim_snapshot_ms: f32,
142 pub sibling_grace_cohorts: usize,
143 pub sibling_grace_bodies: usize,
144 pub sibling_grace_pairs: usize,
145 pub sibling_grace_filtered_pairs: usize,
146}
147
148pub enum FrameDirective {
149 Resimulate,
150 Done(FrameResult),
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
154struct BodyKey(u32, u32);
155
156impl From<RigidBodyHandle> for BodyKey {
157 fn from(handle: RigidBodyHandle) -> Self {
158 let (index, generation) = handle.into_raw_parts();
159 Self(index, generation)
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
164struct BodyPairKey(BodyKey, BodyKey);
165
166fn canonical_body_pair_key(body1: RigidBodyHandle, body2: RigidBodyHandle) -> Option<BodyPairKey> {
167 if body1 == body2 {
168 return None;
169 }
170 let key1 = BodyKey::from(body1);
171 let key2 = BodyKey::from(body2);
172 Some(if key1 <= key2 {
173 BodyPairKey(key1, key2)
174 } else {
175 BodyPairKey(key2, key1)
176 })
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
180struct ImpactCooldownKey {
181 pair: BodyPairKey,
182 target_body: BodyKey,
183}
184
185#[derive(Clone)]
186struct GraceCohort {
187 remaining_steps: u32,
188 bodies: Vec<RigidBodyHandle>,
189}
190
191#[derive(Clone)]
192struct GracePairSet {
193 remaining_steps: u32,
194 pairs: Vec<BodyPairKey>,
195}
196
197#[derive(Clone)]
198struct RuntimeHooks {
199 active_pairs: Arc<HashSet<BodyPairKey>>,
200 filtered_pairs: Arc<AtomicUsize>,
201}
202
203impl PhysicsHooks for RuntimeHooks {
204 fn filter_contact_pair(&self, context: &PairFilterContext) -> Option<SolverFlags> {
205 let Some(body1) = context.rigid_body1 else {
206 return Some(SolverFlags::default());
207 };
208 let Some(body2) = context.rigid_body2 else {
209 return Some(SolverFlags::default());
210 };
211 let Some(pair) = canonical_body_pair_key(body1, body2) else {
212 return Some(SolverFlags::default());
213 };
214 if self.active_pairs.contains(&pair) {
215 self.filtered_pairs.fetch_add(1, Ordering::Relaxed);
216 None
217 } else {
218 Some(SolverFlags::default())
219 }
220 }
221
222 fn filter_intersection_pair(&self, context: &PairFilterContext) -> bool {
223 let Some(body1) = context.rigid_body1 else {
224 return true;
225 };
226 let Some(body2) = context.rigid_body2 else {
227 return true;
228 };
229 let Some(pair) = canonical_body_pair_key(body1, body2) else {
230 return true;
231 };
232 if self.active_pairs.contains(&pair) {
233 self.filtered_pairs.fetch_add(1, Ordering::Relaxed);
234 false
235 } else {
236 true
237 }
238 }
239}
240
241#[derive(Clone, Copy)]
242struct BufferedContactForceEvent {
243 dt: Real,
244 collider1: ColliderHandle,
245 collider2: ColliderHandle,
246 total_force_magnitude: Real,
247}
248
249#[derive(Default)]
250struct BufferedPassEvents {
251 collisions: Vec<CollisionEvent>,
252 contact_forces: Vec<BufferedContactForceEvent>,
253}
254
255pub struct PassAdapter<'a, H: PhysicsHooks + ?Sized, E: EventHandler + ?Sized> {
256 runtime: RuntimeHooks,
257 user_hooks: &'a H,
258 user_events: &'a E,
259 buffered_events: Arc<Mutex<BufferedPassEvents>>,
260}
261
262impl<H, E> PhysicsHooks for PassAdapter<'_, H, E>
263where
264 H: PhysicsHooks + ?Sized,
265 E: EventHandler + ?Sized,
266{
267 fn filter_contact_pair(&self, context: &PairFilterContext) -> Option<SolverFlags> {
268 self.runtime
269 .filter_contact_pair(context)
270 .and_then(|_| self.user_hooks.filter_contact_pair(context))
271 }
272
273 fn filter_intersection_pair(&self, context: &PairFilterContext) -> bool {
274 self.runtime.filter_intersection_pair(context)
275 && self.user_hooks.filter_intersection_pair(context)
276 }
277
278 fn modify_solver_contacts(&self, context: &mut ContactModificationContext) {
279 self.user_hooks.modify_solver_contacts(context);
280 }
281}
282
283impl<H, E> EventHandler for PassAdapter<'_, H, E>
284where
285 H: PhysicsHooks + Sync + ?Sized,
286 E: EventHandler + ?Sized,
287{
288 fn handle_collision_event(
289 &self,
290 _bodies: &RigidBodySet,
291 _colliders: &ColliderSet,
292 event: CollisionEvent,
293 _contact_pair: Option<&ContactPair>,
294 ) {
295 self.buffered_events
296 .lock()
297 .expect("buffered pass events mutex poisoned")
298 .collisions
299 .push(event);
300 }
301
302 fn handle_contact_force_event(
303 &self,
304 dt: Real,
305 _bodies: &RigidBodySet,
306 _colliders: &ColliderSet,
307 contact_pair: &ContactPair,
308 total_force_magnitude: Real,
309 ) {
310 self.buffered_events
311 .lock()
312 .expect("buffered pass events mutex poisoned")
313 .contact_forces
314 .push(BufferedContactForceEvent {
315 dt,
316 collider1: contact_pair.collider1,
317 collider2: contact_pair.collider2,
318 total_force_magnitude,
319 });
320 }
321}
322
323#[derive(Clone, Copy, Debug, Default)]
324struct GraceStepStats {
325 active_cohorts: usize,
326 active_bodies: usize,
327 active_pairs: usize,
328 filtered_pairs: usize,
329}
330
331struct GraceState {
332 sibling_steps: u32,
333 impact_source_steps: u32,
334 cohorts: Vec<GraceCohort>,
335 pair_sets: Vec<GracePairSet>,
336 active_pairs: Arc<HashSet<BodyPairKey>>,
337 filtered_pairs: Arc<AtomicUsize>,
338}
339
340impl GraceState {
341 fn new(options: GracePeriodOptions) -> Self {
342 Self {
343 sibling_steps: options.sibling_steps,
344 impact_source_steps: options.impact_source_steps,
345 cohorts: Vec::new(),
346 pair_sets: Vec::new(),
347 active_pairs: Arc::new(HashSet::new()),
348 filtered_pairs: Arc::new(AtomicUsize::new(0)),
349 }
350 }
351
352 fn set_options(&mut self, options: GracePeriodOptions) {
353 self.sibling_steps = options.sibling_steps;
354 self.impact_source_steps = options.impact_source_steps;
355 self.cohorts.clear();
356 self.pair_sets.clear();
357 self.rebuild_pairs();
358 }
359
360 fn register_split_cohorts(&mut self, split_cohorts: &[SplitCohort]) {
361 if self.sibling_steps == 0 {
362 return;
363 }
364 for cohort in split_cohorts {
365 let unique_bodies: Vec<_> = cohort
366 .target_bodies
367 .iter()
368 .copied()
369 .collect::<HashSet<_>>()
370 .into_iter()
371 .collect();
372 if unique_bodies.len() > 1 {
373 self.cohorts.push(GraceCohort {
374 remaining_steps: self.sibling_steps,
375 bodies: unique_bodies,
376 });
377 }
378 }
379 self.rebuild_pairs();
380 }
381
382 fn register_impact_fracture_pairs(
383 &mut self,
384 impact_sources: &HashSet<RigidBodyHandle>,
385 impacted_bodies: &HashSet<RigidBodyHandle>,
386 split_cohorts: &[SplitCohort],
387 ) {
388 if self.impact_source_steps == 0
389 || impact_sources.is_empty()
390 || impacted_bodies.is_empty()
391 || split_cohorts.is_empty()
392 {
393 return;
394 }
395
396 let matching_targets: Vec<Vec<RigidBodyHandle>> = split_cohorts
397 .iter()
398 .filter(|cohort| {
399 cohort
400 .source_bodies
401 .iter()
402 .any(|body| impacted_bodies.contains(body))
403 })
404 .map(|cohort| {
405 cohort
406 .target_bodies
407 .iter()
408 .chain(cohort.source_bodies.iter())
409 .copied()
410 .collect()
411 })
412 .collect();
413
414 for &source_body in impact_sources {
415 let mut pairs = Vec::new();
416 let mut seen = HashSet::new();
417 for targets in &matching_targets {
418 for &target_body in targets {
419 let Some(pair) = canonical_body_pair_key(source_body, target_body) else {
420 continue;
421 };
422 if seen.insert(pair) {
423 pairs.push(pair);
424 }
425 }
426 }
427 if !pairs.is_empty() {
428 self.pair_sets.push(GracePairSet {
429 remaining_steps: self.impact_source_steps,
430 pairs,
431 });
432 }
433 }
434 self.rebuild_pairs();
435 }
436
437 fn begin_step(&self) -> (RuntimeHooks, GraceStepStats) {
438 self.filtered_pairs.store(0, Ordering::Relaxed);
439 let unique_bodies = self
440 .cohorts
441 .iter()
442 .flat_map(|cohort| cohort.bodies.iter().copied())
443 .collect::<HashSet<_>>()
444 .len();
445 (
446 RuntimeHooks {
447 active_pairs: Arc::clone(&self.active_pairs),
448 filtered_pairs: Arc::clone(&self.filtered_pairs),
449 },
450 GraceStepStats {
451 active_cohorts: self.cohorts.len(),
452 active_bodies: unique_bodies,
453 active_pairs: self.active_pairs.len(),
454 filtered_pairs: 0,
455 },
456 )
457 }
458
459 fn finish_step(&mut self, mut stats: GraceStepStats) -> GraceStepStats {
460 stats.filtered_pairs = self.filtered_pairs.swap(0, Ordering::Relaxed);
461 for cohort in &mut self.cohorts {
462 cohort.remaining_steps = cohort.remaining_steps.saturating_sub(1);
463 }
464 for pair_set in &mut self.pair_sets {
465 pair_set.remaining_steps = pair_set.remaining_steps.saturating_sub(1);
466 }
467 self.cohorts.retain(|cohort| cohort.remaining_steps > 0);
468 self.pair_sets
469 .retain(|pair_set| pair_set.remaining_steps > 0);
470 self.rebuild_pairs();
471 stats
472 }
473
474 fn rebuild_pairs(&mut self) {
475 let mut active_pairs = HashSet::new();
476 for cohort in &self.cohorts {
477 for i in 0..cohort.bodies.len() {
478 for j in (i + 1)..cohort.bodies.len() {
479 if let Some(pair) = canonical_body_pair_key(cohort.bodies[i], cohort.bodies[j])
480 {
481 active_pairs.insert(pair);
482 }
483 }
484 }
485 }
486 for pair_set in &self.pair_sets {
487 for &pair in &pair_set.pairs {
488 active_pairs.insert(pair);
489 }
490 }
491 self.active_pairs = Arc::new(active_pairs);
492 }
493}
494
495#[derive(Clone)]
496struct ImpactCandidate {
497 cooldown_key: ImpactCooldownKey,
498 node_index: u32,
499 target_body: RigidBodyHandle,
500 total_impulse: f32,
501 closing_speed: f32,
502 local_point: SolverVec3,
503 local_force: Vector<Real>,
504 is_internal: bool,
505}
506
507#[derive(Clone, Copy)]
508struct BodyMotionSnapshot {
509 position: Isometry<Real>,
510 linvel: Vector<Real>,
511 angvel: AngVector<Real>,
512}
513
514#[derive(Default)]
515struct PassAnalysis {
516 support_contacts: HashSet<RigidBodyHandle>,
517 impact_candidates: Vec<ImpactCandidate>,
518 impact_sources: HashSet<RigidBodyHandle>,
519 impacted_bodies: HashSet<RigidBodyHandle>,
520 contact_pairs: usize,
521 active_contact_pairs: usize,
522 contact_manifolds: usize,
523}
524
525struct ActiveFrameState {
526 now_secs: f32,
527 dt: f32,
528 remaining_resim_passes: usize,
529 snapshot: Option<BodySnapshots>,
530 pre_step_motion: HashMap<RigidBodyHandle, BodyMotionSnapshot>,
531 impact_sources: HashSet<RigidBodyHandle>,
532 pending_grace_stats: Option<GraceStepStats>,
533 result: FrameResult,
534}
535
536pub struct DestructionRuntime {
537 destructible: DestructibleSet,
538 options: DestructionRuntimeOptions,
539 cooldowns: HashMap<ImpactCooldownKey, f32>,
540 grace: GraceState,
541 frame: Option<ActiveFrameState>,
542}
543
544impl DestructionRuntime {
545 pub fn new(destructible: DestructibleSet, options: DestructionRuntimeOptions) -> Self {
547 let mut runtime = Self {
548 destructible,
549 options,
550 cooldowns: HashMap::new(),
551 grace: GraceState::new(options.grace),
552 frame: None,
553 };
554 runtime.apply_destructible_options(options.destructible);
555 runtime
556 }
557
558 pub fn from_scenario(
560 scenario: &ScenarioDesc,
561 settings: SolverSettings,
562 gravity: Vec3,
563 fracture_policy: FracturePolicy,
564 options: DestructionRuntimeOptions,
565 ) -> Option<Self> {
566 let destructible =
567 DestructibleSet::from_scenario(scenario, settings, gravity, fracture_policy)?;
568 Some(Self::new(destructible, options))
569 }
570
571 pub fn options(&self) -> DestructionRuntimeOptions {
572 self.options
573 }
574
575 pub fn set_options(&mut self, options: DestructionRuntimeOptions) {
576 self.options = options;
577 self.grace.set_options(options.grace);
578 self.apply_destructible_options(options.destructible);
579 }
580
581 pub fn into_inner(self) -> DestructibleSet {
582 self.destructible
583 }
584
585 pub fn initialize(
586 &mut self,
587 bodies: &mut RigidBodySet,
588 colliders: &mut ColliderSet,
589 ) -> Vec<RigidBodyHandle> {
590 self.destructible.initialize(bodies, colliders)
591 }
592
593 pub fn set_ground_body_handle(&mut self, handle: Option<RigidBodyHandle>) {
594 self.destructible.set_ground_body_handle(handle);
595 }
596
597 pub fn refresh_collision_groups(&mut self, bodies: &RigidBodySet, colliders: &mut ColliderSet) {
598 self.destructible
599 .refresh_collision_groups(bodies, colliders);
600 }
601
602 pub fn set_sleep_thresholds(&mut self, options: SleepThresholdOptions) {
603 self.destructible.set_sleep_thresholds(options);
604 }
605
606 pub fn set_small_body_damping(&mut self, options: SmallBodyDampingOptions) {
607 self.destructible.set_small_body_damping(options);
608 }
609
610 pub fn set_debris_cleanup(&mut self, options: DebrisCleanupOptions) {
611 self.destructible.set_debris_cleanup(options);
612 }
613
614 pub fn set_debris_collision_mode(&mut self, mode: DebrisCollisionMode) {
615 self.destructible.set_debris_collision_mode(mode);
616 }
617
618 pub fn set_resimulation_options(&mut self, options: super::resimulation::ResimulationOptions) {
619 self.destructible.set_resimulation_options(options);
620 }
621
622 pub fn set_dynamic_body_ccd_enabled(&mut self, enabled: bool) {
623 self.destructible.set_dynamic_body_ccd_enabled(enabled);
624 }
625
626 pub fn set_split_child_recentering_enabled(&mut self, enabled: bool) {
627 self.destructible
628 .set_split_child_recentering_enabled(enabled);
629 }
630
631 pub fn set_split_child_velocity_fit_enabled(&mut self, enabled: bool) {
632 self.destructible
633 .set_split_child_velocity_fit_enabled(enabled);
634 }
635
636 pub fn set_skip_single_bodies(&mut self, enabled: bool) {
637 self.destructible.set_skip_single_bodies(enabled);
638 }
639
640 pub fn begin_frame(&mut self, now_secs: f32, dt: f32, bodies: &RigidBodySet) {
645 let resimulation = self.destructible.resimulation_options();
646 let mut result = FrameResult::default();
647 let snapshot = if self.destructible.needs_resimulation_snapshot() {
648 let snapshot_started_at = Instant::now();
649 let captured = self.destructible.capture_resimulation_snapshot(bodies);
650 result.resim_snapshot_ms +=
651 snapshot_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
652 Some(captured)
653 } else {
654 None
655 };
656 self.frame = Some(ActiveFrameState {
657 now_secs,
658 dt,
659 remaining_resim_passes: if resimulation.enabled {
660 resimulation.max_passes
661 } else {
662 0
663 },
664 snapshot,
665 pre_step_motion: capture_body_motion(bodies),
666 impact_sources: HashSet::new(),
667 pending_grace_stats: None,
668 result,
669 });
670 }
671
672 pub fn begin_pass<'a, H, E>(
673 &mut self,
674 user_hooks: &'a H,
675 user_events: &'a E,
676 ) -> PassAdapter<'a, H, E>
677 where
678 H: PhysicsHooks + ?Sized,
679 E: EventHandler + ?Sized,
680 {
681 let (runtime_hooks, stats) = self.grace.begin_step();
682 if let Some(frame) = self.frame.as_mut() {
683 frame.pending_grace_stats = Some(stats);
684 }
685 PassAdapter {
686 runtime: runtime_hooks,
687 user_hooks,
688 user_events,
689 buffered_events: Arc::new(Mutex::new(BufferedPassEvents::default())),
690 }
691 }
692
693 pub fn finish_pass<H, E>(
695 &mut self,
696 pass: PassAdapter<'_, H, E>,
697 world: &mut RapierWorldAccess<'_>,
698 ) -> FrameDirective
699 where
700 H: PhysicsHooks + Sync + ?Sized,
701 E: EventHandler + ?Sized,
702 {
703 let mut frame = self
704 .frame
705 .take()
706 .expect("begin_frame must be called before finish_pass");
707 frame.result.rapier_passes += 1;
708
709 self.cooldowns.retain(|_, until| *until > frame.now_secs);
710 let buffered_events = pass
711 .buffered_events
712 .lock()
713 .expect("buffered pass events mutex poisoned");
714 let analysis =
715 self.collect_pass_analysis(frame.dt, &frame.pre_step_motion, &buffered_events, world);
716 frame
717 .impact_sources
718 .extend(analysis.impact_sources.iter().copied());
719 frame.result.contact_pairs = analysis.contact_pairs;
720 frame.result.active_contact_pairs = analysis.active_contact_pairs;
721 frame.result.contact_manifolds = analysis.contact_manifolds;
722
723 self.apply_impact_candidates(frame.now_secs, &analysis, &mut frame.result);
724
725 let solver_result = self.destructible.step_with_time(
726 frame.now_secs,
727 world.bodies,
728 world.colliders,
729 world.island_manager,
730 world.impulse_joints,
731 world.multibody_joints,
732 );
733 accumulate_step_result(&mut frame.result, solver_result.clone());
734
735 if let Some(pending_stats) = frame.pending_grace_stats.take() {
736 let grace_stats = self.grace.finish_step(pending_stats);
737 frame.result.sibling_grace_cohorts = frame
738 .result
739 .sibling_grace_cohorts
740 .max(grace_stats.active_cohorts);
741 frame.result.sibling_grace_bodies = frame
742 .result
743 .sibling_grace_bodies
744 .max(grace_stats.active_bodies);
745 frame.result.sibling_grace_pairs = frame
746 .result
747 .sibling_grace_pairs
748 .max(grace_stats.active_pairs);
749 frame.result.sibling_grace_filtered_pairs += grace_stats.filtered_pairs;
750 }
751
752 if !solver_result.split_cohorts.is_empty() {
753 self.grace
754 .register_split_cohorts(&solver_result.split_cohorts);
755 self.grace.register_impact_fracture_pairs(
756 &analysis.impact_sources,
757 &analysis.impacted_bodies,
758 &solver_result.split_cohorts,
759 );
760 }
761
762 let fractured = solver_result.split_events > 0 || solver_result.new_bodies > 0;
763 if fractured && frame.remaining_resim_passes > 0 {
764 if let Some(snapshot) = frame.snapshot.as_ref() {
765 let restore_started_at = Instant::now();
766 snapshot.restore(world.bodies);
767 snapshot.restore_split_children(world.bodies, &solver_result.split_cohorts);
768 frame.result.resim_restore_ms +=
769 restore_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
770 }
771 frame.remaining_resim_passes = frame.remaining_resim_passes.saturating_sub(1);
772 if frame.remaining_resim_passes > 0 {
773 let snapshot_started_at = Instant::now();
774 frame.snapshot = Some(
775 self.destructible
776 .capture_resimulation_snapshot(world.bodies),
777 );
778 frame.result.resim_snapshot_ms +=
779 snapshot_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
780 frame.pre_step_motion = capture_body_motion(world.bodies);
781 }
782 self.frame = Some(frame);
783 return FrameDirective::Resimulate;
784 }
785
786 for body_handle in analysis.support_contacts {
787 if self.destructible.mark_body_support_contact(
788 body_handle,
789 frame.now_secs,
790 world.bodies,
791 world.colliders,
792 ) {
793 frame.result.support_contacts += 1;
794 }
795 }
796
797 if frame.result.rapier_passes > 1 {
798 self.restore_unblocked_impact_source_motion(
799 &frame.impact_sources,
800 &frame.pre_step_motion,
801 world,
802 );
803 }
804
805 frame.result.optimization = self.destructible.process_optimizations(
806 frame.now_secs,
807 world.bodies,
808 world.colliders,
809 world.island_manager,
810 world.impulse_joints,
811 world.multibody_joints,
812 );
813
814 flush_buffered_events(pass.user_events, &buffered_events, world);
815 self.frame = None;
816 FrameDirective::Done(frame.result)
817 }
818
819 pub fn step_frame<H, E, S>(
827 &mut self,
828 now_secs: f32,
829 dt: f32,
830 world: &mut RapierWorldAccess<'_>,
831 user_hooks: &H,
832 user_events: &E,
833 mut step: S,
834 ) -> FrameResult
835 where
836 H: PhysicsHooks + Sync + ?Sized,
837 E: EventHandler + ?Sized,
838 S: FnMut(&PassAdapter<'_, H, E>, &mut RapierWorldAccess<'_>),
839 {
840 self.begin_frame(now_secs, dt, world.bodies);
841 loop {
842 let pass = self.begin_pass(user_hooks, user_events);
843 step(&pass, world);
844 match self.finish_pass(pass, world) {
845 FrameDirective::Resimulate => continue,
846 FrameDirective::Done(result) => return result,
847 }
848 }
849 }
850
851 fn collect_pass_analysis(
852 &self,
853 dt: f32,
854 pre_step_motion: &HashMap<RigidBodyHandle, BodyMotionSnapshot>,
855 buffered_events: &BufferedPassEvents,
856 world: &RapierWorldAccess<'_>,
857 ) -> PassAnalysis {
858 let mut analysis = PassAnalysis::default();
859 let dt = dt.max(1.0e-6);
860
861 for pair in world.narrow_phase.contact_pairs() {
862 analysis.contact_pairs += 1;
863 analysis.contact_manifolds += pair.manifolds.len();
864 if !pair.has_any_active_contact {
865 continue;
866 }
867 analysis.active_contact_pairs += 1;
868
869 let Some(collider1) = world.colliders.get(pair.collider1) else {
870 continue;
871 };
872 let Some(collider2) = world.colliders.get(pair.collider2) else {
873 continue;
874 };
875 let Some(body1) = collider1.parent() else {
876 continue;
877 };
878 let Some(body2) = collider2.parent() else {
879 continue;
880 };
881
882 let Some(_body_pair_key) = canonical_body_pair_key(body1, body2) else {
883 continue;
884 };
885
886 let node1 = self.destructible.collider_node(pair.collider1);
887 let node2 = self.destructible.collider_node(pair.collider2);
888 if node1.is_none() && node2.is_none() {
889 continue;
890 }
891
892 if !self.options.contact_impacts.enabled
893 || self.options.contact_impacts.force_scale <= 0.0
894 {
895 continue;
896 }
897
898 let pair_is_destructible_on_both_sides = node1.is_some() && node2.is_some();
899
900 if let Some(candidate) = self.build_external_candidate(
901 pair,
902 dt,
903 pair.collider1,
904 body1,
905 body2,
906 node1,
907 false,
908 pair_is_destructible_on_both_sides,
909 pre_step_motion,
910 world,
911 ) {
912 analysis.impact_candidates.push(candidate);
913 analysis.impact_sources.insert(body2);
914 analysis.impacted_bodies.insert(body1);
915 }
916 if let Some(candidate) = self.build_external_candidate(
917 pair,
918 dt,
919 pair.collider2,
920 body2,
921 body1,
922 node2,
923 true,
924 pair_is_destructible_on_both_sides,
925 pre_step_motion,
926 world,
927 ) {
928 analysis.impact_candidates.push(candidate);
929 analysis.impact_sources.insert(body1);
930 analysis.impacted_bodies.insert(body2);
931 }
932 }
933
934 for &event in &buffered_events.collisions {
935 let CollisionEvent::Started(collider1_handle, collider2_handle, _) = event else {
936 continue;
937 };
938 let Some(collider1) = world.colliders.get(collider1_handle) else {
939 continue;
940 };
941 let Some(collider2) = world.colliders.get(collider2_handle) else {
942 continue;
943 };
944 let Some(body1) = collider1.parent() else {
945 continue;
946 };
947 let Some(body2) = collider2.parent() else {
948 continue;
949 };
950 if let Some(node_index) = self.destructible.collider_node(collider1_handle) {
951 if self.support_contact_for_pair(node_index, body1, collider2_handle, body2, world)
952 {
953 analysis.support_contacts.insert(body1);
954 }
955 }
956 if let Some(node_index) = self.destructible.collider_node(collider2_handle) {
957 if self.support_contact_for_pair(node_index, body2, collider1_handle, body1, world)
958 {
959 analysis.support_contacts.insert(body2);
960 }
961 }
962 }
963
964 analysis
965 }
966
967 fn build_external_candidate(
968 &self,
969 pair: &ContactPair,
970 dt: f32,
971 target_collider: ColliderHandle,
972 target_body: RigidBodyHandle,
973 other_body: RigidBodyHandle,
974 target_node: Option<u32>,
975 target_is_second: bool,
976 is_internal: bool,
977 pre_step_motion: &HashMap<RigidBodyHandle, BodyMotionSnapshot>,
978 world: &RapierWorldAccess<'_>,
979 ) -> Option<ImpactCandidate> {
980 let target_node = target_node?;
981 if self.destructible.is_support(target_node) {
982 return None;
983 }
984
985 let (world_point, force_world, total_impulse) =
986 self.contact_geometry(pair, target_collider, target_is_second, dt, world)?;
987 let closing_speed = self.closing_speed(
988 world_point,
989 force_world,
990 target_body,
991 other_body,
992 pre_step_motion,
993 world,
994 );
995 let Some(target_rb) = world.bodies.get(target_body) else {
996 return None;
997 };
998 let local_point = target_rb.position().inverse_transform_point(&world_point);
999 let local_force = target_rb.position().rotation.inverse() * force_world;
1000
1001 Some(ImpactCandidate {
1002 cooldown_key: ImpactCooldownKey {
1003 pair: canonical_body_pair_key(target_body, other_body)?,
1004 target_body: BodyKey::from(target_body),
1005 },
1006 node_index: target_node,
1007 target_body,
1008 total_impulse,
1009 closing_speed,
1010 local_point: SolverVec3::new(local_point.x, local_point.y, local_point.z),
1011 local_force,
1012 is_internal,
1013 })
1014 }
1015
1016 fn contact_geometry(
1017 &self,
1018 pair: &ContactPair,
1019 target_collider: ColliderHandle,
1020 target_is_second: bool,
1021 dt: f32,
1022 world: &RapierWorldAccess<'_>,
1023 ) -> Option<(Point<Real>, Vector<Real>, f32)> {
1024 let total_impulse = pair.total_impulse();
1025 let total_impulse_mag = pair.total_impulse_magnitude();
1026 if total_impulse_mag <= 1.0e-6 {
1027 return None;
1028 }
1029
1030 let (manifold, contact) = pair.find_deepest_contact()?;
1031 let target_local_point = if target_is_second {
1032 contact.local_p2
1033 } else {
1034 contact.local_p1
1035 };
1036 let collider = world.colliders.get(target_collider)?;
1037 let world_point = collider.position() * target_local_point;
1038 let force_world = if target_is_second {
1039 total_impulse / dt
1040 } else {
1041 -total_impulse / dt
1042 };
1043 let force_world = if force_world.norm_squared() > 1.0e-12 {
1044 force_world
1045 } else {
1046 let normal = if target_is_second {
1047 manifold.data.normal
1048 } else {
1049 -manifold.data.normal
1050 };
1051 normal * (total_impulse_mag / dt)
1052 };
1053 Some((world_point, force_world, total_impulse_mag))
1054 }
1055
1056 fn closing_speed(
1057 &self,
1058 world_point: Point<Real>,
1059 force_world: Vector<Real>,
1060 target_body: RigidBodyHandle,
1061 other_body: RigidBodyHandle,
1062 pre_step_motion: &HashMap<RigidBodyHandle, BodyMotionSnapshot>,
1063 world: &RapierWorldAccess<'_>,
1064 ) -> f32 {
1065 if let (Some(target_motion), Some(other_motion)) = (
1066 pre_step_motion.get(&target_body),
1067 pre_step_motion.get(&other_body),
1068 ) {
1069 let target_velocity = motion_velocity_at_point(target_motion, world_point);
1070 let other_velocity = motion_velocity_at_point(other_motion, world_point);
1071 return relative_speed_along_force(force_world, other_velocity - target_velocity);
1072 }
1073
1074 let Some(target_rb) = world.bodies.get(target_body) else {
1075 return 0.0;
1076 };
1077 let Some(other_rb) = world.bodies.get(other_body) else {
1078 return 0.0;
1079 };
1080 let target_velocity = target_rb.velocity_at_point(&world_point);
1081 let other_velocity = other_rb.velocity_at_point(&world_point);
1082 relative_speed_along_force(force_world, other_velocity - target_velocity)
1083 }
1084
1085 fn support_contact_for_pair(
1086 &self,
1087 node_index: u32,
1088 body_handle: RigidBodyHandle,
1089 other_collider: ColliderHandle,
1090 other_body: RigidBodyHandle,
1091 world: &RapierWorldAccess<'_>,
1092 ) -> bool {
1093 if self.destructible.is_support(node_index) {
1094 return false;
1095 }
1096 if other_body == body_handle {
1097 return false;
1098 }
1099 world
1100 .bodies
1101 .get(other_body)
1102 .map(|body| body.is_fixed())
1103 .unwrap_or(false)
1104 || self
1105 .destructible
1106 .collider_node(other_collider)
1107 .map(|other_node| self.destructible.is_support(other_node))
1108 .unwrap_or(false)
1109 || self.destructible.body_has_support(other_body)
1110 }
1111
1112 fn apply_impact_candidates(
1113 &mut self,
1114 now_secs: f32,
1115 analysis: &PassAnalysis,
1116 result: &mut FrameResult,
1117 ) {
1118 let mut candidates = analysis.impact_candidates.clone();
1119 candidates.sort_by(|a, b| {
1120 b.total_impulse
1121 .partial_cmp(&a.total_impulse)
1122 .unwrap_or(std::cmp::Ordering::Equal)
1123 });
1124
1125 for candidate in candidates {
1126 let min_speed = if candidate.is_internal {
1127 self.options.contact_impacts.min_internal_speed
1128 } else {
1129 self.options.contact_impacts.min_external_speed
1130 };
1131 if candidate.total_impulse < self.options.contact_impacts.min_total_impulse {
1132 result.rejected_below_impulse += 1;
1133 continue;
1134 }
1135 if candidate.closing_speed < min_speed {
1136 result.rejected_below_speed += 1;
1137 continue;
1138 }
1139 if let Some(until) = self.cooldowns.get(&candidate.cooldown_key) {
1140 if *until > now_secs {
1141 result.rejected_cooldown += 1;
1142 continue;
1143 }
1144 }
1145
1146 let mut local_force = candidate.local_force;
1147 let mut force_scale = self.options.contact_impacts.force_scale
1148 * if candidate.is_internal {
1149 self.options.contact_impacts.internal_contact_scale
1150 } else {
1151 1.0
1152 };
1153 let scaled_mag = local_force.norm() * force_scale;
1154 if self.options.contact_impacts.max_force_magnitude.is_finite()
1155 && self.options.contact_impacts.max_force_magnitude > 0.0
1156 && scaled_mag > self.options.contact_impacts.max_force_magnitude
1157 {
1158 force_scale *= self.options.contact_impacts.max_force_magnitude / scaled_mag;
1159 }
1160 local_force *= force_scale;
1161 if local_force.norm_squared() <= 1.0e-12 {
1162 continue;
1163 }
1164
1165 self.apply_localized_force(
1166 candidate.target_body,
1167 candidate.node_index,
1168 candidate.local_point,
1169 local_force,
1170 );
1171
1172 self.cooldowns.insert(
1173 candidate.cooldown_key,
1174 now_secs + self.options.contact_impacts.cooldown_secs.max(0.0),
1175 );
1176 result.accepted_impacts += 1;
1177 }
1178 }
1179
1180 fn apply_localized_force(
1181 &mut self,
1182 body_handle: RigidBodyHandle,
1183 node_index: u32,
1184 hit_local: SolverVec3,
1185 local_force: Vector<Real>,
1186 ) {
1187 let splash_radius = self.options.contact_impacts.splash_radius.max(0.0);
1188 let splash_exp = self
1189 .options
1190 .contact_impacts
1191 .splash_falloff_exponent
1192 .max(0.01);
1193
1194 let impacted_nodes: Vec<(u32, SolverVec3, f32)> = self
1195 .destructible
1196 .body_nodes_slice(body_handle)
1197 .iter()
1198 .copied()
1199 .filter_map(|other_node| {
1200 let other_local = self.destructible.node_local_offset(other_node)?;
1201 let dx = other_local.x - hit_local.x;
1202 let dy = other_local.y - hit_local.y;
1203 let dz = other_local.z - hit_local.z;
1204 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
1205 let weight = if other_node == node_index {
1206 1.0
1207 } else if splash_radius <= 0.0 || dist > splash_radius {
1208 0.0
1209 } else {
1210 (1.0 - dist / splash_radius).max(0.0).powf(splash_exp)
1211 };
1212 (weight > 0.0).then_some((other_node, other_local, weight))
1213 })
1214 .collect();
1215
1216 for (other_node, other_local, weight) in impacted_nodes {
1217 self.destructible.add_force(
1218 other_node,
1219 other_local,
1220 SolverVec3::new(
1221 local_force.x * weight,
1222 local_force.y * weight,
1223 local_force.z * weight,
1224 ),
1225 );
1226 }
1227 }
1228
1229 fn apply_destructible_options(&mut self, options: DestructibleRuntimeOptions) {
1230 if let Some(value) = options.sleep_thresholds {
1231 self.destructible.set_sleep_thresholds(value);
1232 }
1233 if let Some(value) = options.small_body_damping {
1234 self.destructible.set_small_body_damping(value);
1235 }
1236 if let Some(value) = options.debris_cleanup {
1237 self.destructible.set_debris_cleanup(value);
1238 }
1239 if let Some(value) = options.debris_collision_mode {
1240 self.destructible.set_debris_collision_mode(value);
1241 }
1242 if let Some(value) = options.split_child_recentering_enabled {
1243 self.destructible.set_split_child_recentering_enabled(value);
1244 }
1245 if let Some(value) = options.split_child_velocity_fit_enabled {
1246 self.destructible
1247 .set_split_child_velocity_fit_enabled(value);
1248 }
1249 if let Some(value) = options.skip_single_bodies {
1250 self.destructible.set_skip_single_bodies(value);
1251 }
1252 }
1253
1254 fn restore_unblocked_impact_source_motion(
1255 &self,
1256 impact_sources: &HashSet<RigidBodyHandle>,
1257 pre_step_motion: &HashMap<RigidBodyHandle, BodyMotionSnapshot>,
1258 world: &mut RapierWorldAccess<'_>,
1259 ) {
1260 for &body_handle in impact_sources {
1261 if !self.destructible.body_nodes_slice(body_handle).is_empty() {
1262 continue;
1263 }
1264 if source_has_active_destructible_contact(body_handle, world, &self.destructible) {
1265 continue;
1266 }
1267 let Some(motion) = pre_step_motion.get(&body_handle) else {
1268 continue;
1269 };
1270 let Some(body) = world.bodies.get_mut(body_handle) else {
1271 continue;
1272 };
1273 body.set_linvel(motion.linvel, true);
1274 body.set_angvel(motion.angvel, true);
1275 body.wake_up(true);
1276 }
1277 }
1278}
1279
1280impl Deref for DestructionRuntime {
1281 type Target = DestructibleSet;
1282
1283 fn deref(&self) -> &Self::Target {
1284 &self.destructible
1285 }
1286}
1287
1288impl DerefMut for DestructionRuntime {
1289 fn deref_mut(&mut self) -> &mut Self::Target {
1290 &mut self.destructible
1291 }
1292}
1293
1294fn accumulate_step_result(result: &mut FrameResult, step: StepResult) {
1295 result.fractures += step.fractures;
1296 result.new_bodies += step.new_bodies;
1297 result.split_events += step.split_events;
1298 result.split_edits.plan_ms += step.split_edits.plan_ms;
1299 result.split_edits.apply_ms += step.split_edits.apply_ms;
1300 result.split_edits.child_pose_ms += step.split_edits.child_pose_ms;
1301 result.split_edits.velocity_fit_ms += step.split_edits.velocity_fit_ms;
1302 result.split_edits.sleep_init_ms += step.split_edits.sleep_init_ms;
1303 result.split_edits.body_create_ms += step.split_edits.body_create_ms;
1304 result.split_edits.collider_move_ms += step.split_edits.collider_move_ms;
1305 result.split_edits.collider_insert_ms += step.split_edits.collider_insert_ms;
1306 result.split_edits.body_retire_ms += step.split_edits.body_retire_ms;
1307 result.split_edits.reused_bodies += step.split_edits.reused_bodies;
1308 result.split_edits.recycled_bodies += step.split_edits.recycled_bodies;
1309 result.split_edits.created_bodies += step.split_edits.created_bodies;
1310 result.split_edits.retired_bodies += step.split_edits.retired_bodies;
1311 result.split_edits.body_type_flips += step.split_edits.body_type_flips;
1312 result.split_edits.moved_colliders += step.split_edits.moved_colliders;
1313 result.split_edits.inserted_colliders += step.split_edits.inserted_colliders;
1314 result.split_edits.removed_colliders += step.split_edits.removed_colliders;
1315 result.split_sanitize_ms += step.split_sanitize_ms;
1316 result.split_estimate_ms += step.split_estimate_ms;
1317 result.split_cohorts.extend(step.split_cohorts);
1318}
1319
1320fn capture_body_motion(set: &RigidBodySet) -> HashMap<RigidBodyHandle, BodyMotionSnapshot> {
1321 set.iter()
1322 .map(|(handle, body)| {
1323 (
1324 handle,
1325 BodyMotionSnapshot {
1326 position: *body.position(),
1327 linvel: *body.linvel(),
1328 angvel: *body.angvel(),
1329 },
1330 )
1331 })
1332 .collect()
1333}
1334
1335fn source_has_active_destructible_contact(
1336 body_handle: RigidBodyHandle,
1337 world: &RapierWorldAccess<'_>,
1338 destructible: &DestructibleSet,
1339) -> bool {
1340 let Some(body) = world.bodies.get(body_handle) else {
1341 return false;
1342 };
1343
1344 for &collider_handle in body.colliders() {
1345 for pair in world.narrow_phase.contact_pairs_with(collider_handle) {
1346 if !pair.has_any_active_contact {
1347 continue;
1348 }
1349 let other_collider = if pair.collider1 == collider_handle {
1350 pair.collider2
1351 } else {
1352 pair.collider1
1353 };
1354 if destructible.collider_node(other_collider).is_some() {
1355 return true;
1356 }
1357 }
1358 }
1359 false
1360}
1361
1362fn motion_velocity_at_point(
1363 snapshot: &BodyMotionSnapshot,
1364 world_point: Point<Real>,
1365) -> Vector<Real> {
1366 let offset = world_point.coords - snapshot.position.translation.vector;
1367 snapshot.linvel + snapshot.angvel.cross(&offset)
1368}
1369
1370fn relative_speed_along_force(force_world: Vector<Real>, relative_velocity: Vector<Real>) -> f32 {
1371 if force_world.norm_squared() <= 1.0e-12 {
1372 return relative_velocity.norm();
1373 }
1374 let direction = force_world.normalize();
1375 let projected = relative_velocity.dot(&direction).abs();
1376 if projected > 1.0e-5 {
1377 projected
1378 } else {
1379 relative_velocity.norm()
1380 }
1381}
1382
1383fn flush_buffered_events<E: EventHandler + ?Sized>(
1384 user_events: &E,
1385 buffered_events: &BufferedPassEvents,
1386 world: &RapierWorldAccess<'_>,
1387) {
1388 for &event in &buffered_events.collisions {
1389 let contact_pair = match event {
1390 CollisionEvent::Started(collider1, collider2, _)
1391 | CollisionEvent::Stopped(collider1, collider2, _) => {
1392 world.narrow_phase.contact_pair(collider1, collider2)
1393 }
1394 };
1395 user_events.handle_collision_event(world.bodies, world.colliders, event, contact_pair);
1396 }
1397
1398 for event in &buffered_events.contact_forces {
1399 let Some(contact_pair) = world
1400 .narrow_phase
1401 .contact_pair(event.collider1, event.collider2)
1402 else {
1403 continue;
1404 };
1405 user_events.handle_contact_force_event(
1406 event.dt,
1407 world.bodies,
1408 world.colliders,
1409 contact_pair,
1410 event.total_force_magnitude,
1411 );
1412 }
1413}