1use std::collections::{HashMap, HashSet};
2
3#[cfg(not(target_arch = "wasm32"))]
4use std::time::Instant;
5
6#[cfg(target_arch = "wasm32")]
10#[derive(Clone, Copy)]
11struct Instant;
12
13#[cfg(target_arch = "wasm32")]
14impl Instant {
15 fn now() -> Self {
16 Self
17 }
18
19 fn elapsed(&self) -> std::time::Duration {
20 std::time::Duration::ZERO
21 }
22}
23
24use rapier3d::na::UnitQuaternion;
25use rapier3d::prelude::*;
26
27use super::collision_groups::{apply_collision_groups_for_body, DebrisCollisionMode};
28use super::optimization::{
29 DebrisCleanupOptions, OptimizationMode, OptimizationResult, SleepThresholdOptions,
30 SmallBodyDampingOptions,
31};
32use crate::types::*;
33
34#[derive(Clone, Copy)]
35struct BodyState {
36 position: Isometry<Real>,
37 linvel: Vector<Real>,
38 angvel: Vector<Real>,
39 was_sleeping: bool,
40}
41
42#[derive(Clone, Copy, Debug)]
43struct BodyMetadata {
44 created_at: f32,
45 first_support_contact_at: Option<f32>,
46 damping_promoted: bool,
47 sleep_threshold_configured: bool,
48 has_support: bool,
49}
50
51#[derive(Clone, Copy)]
52struct NodeSourceState {
53 body_handle: RigidBodyHandle,
54 collider_handle: Option<ColliderHandle>,
55 world_centroid: Vec3,
56 world_velocity: Vec3,
57}
58
59struct SplitPlanningData {
60 existing: Vec<super::split_migrator::ExistingBodyState>,
61 parent_bodies: Vec<RigidBodyHandle>,
62 parent_states: HashMap<RigidBodyHandle, BodyState>,
63 node_sources: HashMap<u32, NodeSourceState>,
64}
65
66#[derive(Clone, Copy)]
67enum PlannedBodyTarget {
68 Existing(RigidBodyHandle),
69 Recycled(RigidBodyHandle),
70 Created(RigidBodyHandle),
71}
72
73impl PlannedBodyTarget {
74 fn handle(self) -> RigidBodyHandle {
75 match self {
76 Self::Existing(handle) | Self::Recycled(handle) | Self::Created(handle) => handle,
77 }
78 }
79}
80
81#[derive(Clone, Copy)]
82struct ChildTargetState {
83 pose: Isometry<Real>,
84 linvel: Vector<Real>,
85 angvel: Vector<Real>,
86 should_sleep: bool,
87}
88
89#[derive(Clone, Copy, Debug, Default)]
90pub struct SplitCost {
91 pub create_bodies: usize,
92 pub collider_migrations: usize,
93}
94
95#[derive(Clone, Copy, Debug, Default)]
96pub struct SplitEditStats {
97 pub plan_ms: f32,
98 pub apply_ms: f32,
99 pub child_pose_ms: f32,
100 pub velocity_fit_ms: f32,
101 pub sleep_init_ms: f32,
102 pub body_create_ms: f32,
103 pub collider_move_ms: f32,
104 pub collider_insert_ms: f32,
105 pub body_retire_ms: f32,
106 pub reused_bodies: usize,
107 pub recycled_bodies: usize,
108 pub created_bodies: usize,
109 pub retired_bodies: usize,
110 pub body_type_flips: usize,
111 pub moved_colliders: usize,
112 pub inserted_colliders: usize,
113 pub removed_colliders: usize,
114}
115
116#[derive(Debug, Default)]
117pub struct SplitApplyResult {
118 pub new_handles: Vec<RigidBodyHandle>,
119 pub cohort_handles: Vec<RigidBodyHandle>,
120 pub source_handles: Vec<RigidBodyHandle>,
121 pub restore_sources: Vec<(RigidBodyHandle, RigidBodyHandle)>,
122 pub stats: SplitEditStats,
123}
124
125pub struct BodyTracker {
127 node_to_body: Vec<Option<RigidBodyHandle>>,
129 node_to_collider: Vec<Option<ColliderHandle>>,
131 collider_to_node: HashMap<ColliderHandle, u32>,
133 body_to_nodes: HashMap<RigidBodyHandle, Vec<u32>>,
135 support_nodes: HashSet<u32>,
137 node_sizes: Vec<Vec3>,
139 node_colliders: Vec<Option<ScenarioCollider>>,
141 node_centroids: Vec<Vec3>,
143 node_masses: Vec<f32>,
145 node_local_offsets: Vec<Vec3>,
147 body_metadata: HashMap<RigidBodyHandle, BodyMetadata>,
149 dynamic_body_ccd_enabled: bool,
151 debris_collision_mode: DebrisCollisionMode,
153 ground_body_handle: Option<RigidBodyHandle>,
155 split_child_recentering_enabled: bool,
157 split_child_velocity_fit_enabled: bool,
159}
160
161impl BodyTracker {
162 pub fn new(
164 nodes: &[ScenarioNode],
165 node_sizes: Vec<Vec3>,
166 node_colliders: Vec<Option<ScenarioCollider>>,
167 ) -> Self {
168 let support_nodes: HashSet<u32> = nodes
169 .iter()
170 .enumerate()
171 .filter(|(_, n)| n.mass == 0.0)
172 .map(|(i, _)| i as u32)
173 .collect();
174
175 let node_centroids = nodes.iter().map(|n| n.centroid).collect();
176 let node_masses = nodes.iter().map(|n| n.mass).collect();
177
178 Self {
179 node_to_body: vec![None; nodes.len()],
180 node_to_collider: vec![None; nodes.len()],
181 collider_to_node: HashMap::new(),
182 body_to_nodes: HashMap::new(),
183 support_nodes,
184 node_sizes,
185 node_colliders,
186 node_centroids,
187 node_masses,
188 node_local_offsets: vec![Vec3::ZERO; nodes.len()],
189 body_metadata: HashMap::new(),
190 dynamic_body_ccd_enabled: false,
191 debris_collision_mode: DebrisCollisionMode::All,
192 ground_body_handle: None,
193 split_child_recentering_enabled: true,
194 split_child_velocity_fit_enabled: true,
195 }
196 }
197
198 pub fn set_dynamic_body_ccd_enabled(&mut self, enabled: bool) {
199 self.dynamic_body_ccd_enabled = enabled;
200 }
201
202 pub fn debris_collision_mode(&self) -> DebrisCollisionMode {
203 self.debris_collision_mode
204 }
205
206 pub fn set_debris_collision_mode(&mut self, mode: DebrisCollisionMode) {
207 self.debris_collision_mode = mode;
208 }
209
210 pub fn ground_body_handle(&self) -> Option<RigidBodyHandle> {
211 self.ground_body_handle
212 }
213
214 pub fn set_ground_body_handle(&mut self, handle: Option<RigidBodyHandle>) {
215 self.ground_body_handle = handle;
216 }
217
218 pub fn set_split_child_recentering_enabled(&mut self, enabled: bool) {
219 self.split_child_recentering_enabled = enabled;
220 }
221
222 pub fn set_split_child_velocity_fit_enabled(&mut self, enabled: bool) {
223 self.split_child_velocity_fit_enabled = enabled;
224 }
225
226 pub fn refresh_collision_groups(
227 &self,
228 bodies: &RigidBodySet,
229 colliders: &mut ColliderSet,
230 max_colliders_for_debris: usize,
231 ) {
232 if let Some(ground_body_handle) = self.ground_body_handle {
233 apply_collision_groups_for_body(
234 ground_body_handle,
235 bodies,
236 colliders,
237 self.ground_body_handle,
238 self.debris_collision_mode,
239 true,
240 max_colliders_for_debris,
241 );
242 }
243
244 for &body_handle in self.body_to_nodes.keys() {
245 apply_collision_groups_for_body(
246 body_handle,
247 bodies,
248 colliders,
249 self.ground_body_handle,
250 self.debris_collision_mode,
251 self.debris_collision_active(body_handle),
252 max_colliders_for_debris,
253 );
254 }
255 }
256
257 pub fn create_initial_bodies(
262 &mut self,
263 actors: &[Actor],
264 bodies: &mut RigidBodySet,
265 colliders: &mut ColliderSet,
266 created_at: f32,
267 small_body_damping: SmallBodyDampingOptions,
268 sleep_thresholds: SleepThresholdOptions,
269 max_colliders_for_debris: usize,
270 ) -> Vec<RigidBodyHandle> {
271 let mut handles = Vec::new();
272
273 for actor in actors {
274 if actor.nodes.is_empty() {
275 continue;
276 }
277
278 let has_support = actor.nodes.iter().any(|n| self.support_nodes.contains(n));
279
280 let com = self.compute_actor_com(&actor.nodes);
282
283 let body = if has_support {
284 bodies.insert(RigidBodyBuilder::fixed().translation(vector![com.x, com.y, com.z]))
285 } else {
286 bodies.insert(
287 RigidBodyBuilder::dynamic()
288 .translation(vector![com.x, com.y, com.z])
289 .ccd_enabled(self.dynamic_body_ccd_enabled),
290 )
291 };
292
293 for &node_idx in &actor.nodes {
295 let ni = node_idx as usize;
296 let centroid = self.node_centroids[ni];
297
298 let local_x = centroid.x - com.x;
300 let local_y = centroid.y - com.y;
301 let local_z = centroid.z - com.z;
302
303 let collider =
304 self.build_node_collider(node_idx, Vec3::new(local_x, local_y, local_z));
305 let collider_handle = colliders.insert_with_parent(collider, body, bodies);
306 self.attach_node(
307 node_idx,
308 body,
309 collider_handle,
310 Vec3::new(local_x, local_y, local_z),
311 );
312 }
313
314 self.body_to_nodes.insert(body, actor.nodes.clone());
315 self.update_body_metadata(body, &actor.nodes, created_at);
316 self.promote_small_body_damping(body, bodies, &small_body_damping);
317 self.configure_sleep_thresholds(body, bodies, &sleep_thresholds);
318 self.apply_collision_groups(body, bodies, colliders, max_colliders_for_debris);
319 handles.push(body);
320 }
321
322 handles
323 }
324
325 pub fn handle_split(
329 &mut self,
330 event: &SplitEvent,
331 bodies: &mut RigidBodySet,
332 colliders: &mut ColliderSet,
333 island_manager: &mut IslandManager,
334 impulse_joints: &mut ImpulseJointSet,
335 multibody_joints: &mut MultibodyJointSet,
336 created_at: f32,
337 small_body_damping: SmallBodyDampingOptions,
338 sleep_thresholds: SleepThresholdOptions,
339 max_colliders_for_debris: usize,
340 ) -> SplitApplyResult {
341 let mut result = SplitApplyResult::default();
342 let planning = self.collect_split_planning_data(event, bodies);
343 result.source_handles = planning.parent_bodies.clone();
344 let plan_started_at = Instant::now();
345 let child_support: Vec<super::split_migrator::PlannerChildSupport> = event
346 .children
347 .iter()
348 .map(|child| super::split_migrator::PlannerChildSupport {
349 is_support: child.nodes.iter().any(|n| self.support_nodes.contains(n)),
350 })
351 .collect();
352 let plan = super::split_migrator::plan_split_migration_with_support(
353 &planning.existing,
354 &event.children,
355 &child_support,
356 );
357 result.stats.plan_ms = plan_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
358 result.stats.reused_bodies = plan.reuse.len();
359 let reused_by_body: HashMap<RigidBodyHandle, usize> = plan
360 .reuse
361 .iter()
362 .map(|entry| (entry.body_handle, entry.child_index))
363 .collect();
364 let mut recycled_fixed = Vec::new();
365 let mut recycled_dynamic = Vec::new();
366 for &body_handle in &planning.parent_bodies {
367 if reused_by_body.contains_key(&body_handle) {
368 continue;
369 }
370 if bodies
371 .get(body_handle)
372 .map(|body| body.is_dynamic())
373 .unwrap_or(false)
374 {
375 recycled_dynamic.push(body_handle);
376 } else {
377 recycled_fixed.push(body_handle);
378 }
379 }
380
381 let mut child_targets: HashMap<usize, PlannedBodyTarget> = HashMap::new();
382 let mut child_target_states: HashMap<usize, ChildTargetState> = HashMap::new();
383 let apply_started_at = Instant::now();
384
385 for entry in &plan.reuse {
386 let child = &event.children[entry.child_index];
387 let child_has_support = child.nodes.iter().any(|n| self.support_nodes.contains(n));
388 let (target_state, child_pose_ms, velocity_fit_ms) =
389 self.compute_child_target_state(child, &planning, child_has_support);
390 result.stats.child_pose_ms += child_pose_ms;
391 result.stats.velocity_fit_ms += velocity_fit_ms;
392 let flipped =
393 self.reconcile_reused_body_type(entry.body_handle, child_has_support, bodies);
394 result.stats.body_type_flips += usize::from(flipped);
395 if let Some(body) = bodies.get_mut(entry.body_handle) {
396 body.set_position(target_state.pose, false);
397 }
398 child_targets.insert(
399 entry.child_index,
400 PlannedBodyTarget::Existing(entry.body_handle),
401 );
402 child_target_states.insert(entry.child_index, target_state);
403 }
404
405 for entry in &plan.create {
406 let child = &event.children[entry.child_index];
407 let child_has_support = child.nodes.iter().any(|n| self.support_nodes.contains(n));
408 let (target_state, child_pose_ms, velocity_fit_ms) =
409 self.compute_child_target_state(child, &planning, child_has_support);
410 result.stats.child_pose_ms += child_pose_ms;
411 result.stats.velocity_fit_ms += velocity_fit_ms;
412
413 let recycled_handle = if child_has_support {
414 recycled_fixed.pop().or_else(|| recycled_dynamic.pop())
415 } else {
416 recycled_dynamic.pop().or_else(|| recycled_fixed.pop())
417 };
418
419 if let Some(body_handle) = recycled_handle {
420 let flipped = self.reinitialize_recycled_body(
421 body_handle,
422 child_has_support,
423 target_state,
424 bodies,
425 );
426 result.stats.body_type_flips += usize::from(flipped);
427 result.stats.recycled_bodies += 1;
428 child_targets.insert(entry.child_index, PlannedBodyTarget::Recycled(body_handle));
429 child_target_states.insert(entry.child_index, target_state);
430 continue;
431 }
432
433 let body_create_started_at = Instant::now();
434 let body_handle = self.create_body_for_child(child, target_state, bodies);
435 result.stats.body_create_ms +=
436 body_create_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
437 result.stats.created_bodies += 1;
438 result.new_handles.push(body_handle);
439 child_targets.insert(entry.child_index, PlannedBodyTarget::Created(body_handle));
440 child_target_states.insert(entry.child_index, target_state);
441 }
442
443 for (child_index, target) in child_targets.iter().map(|(k, v)| (*k, *v)) {
444 let child = &event.children[child_index];
445 let target_body = target.handle();
446 if let Some(source_body) = self.dominant_child_body_handle(child, &planning) {
447 result.restore_sources.push((target_body, source_body));
448 }
449 let target_state = *child_target_states
450 .get(&child_index)
451 .unwrap_or_else(|| panic!("missing target state for child {}", child_index));
452 let target_pose = target_state.pose;
453
454 for &n in &child.nodes {
455 let Some(source) = planning.node_sources.get(&n) else {
456 continue;
457 };
458 let local_offset = self.local_offset_for_pose(source, &target_pose);
459 let already_on_target = self.node_body(n) == Some(target_body);
460 if already_on_target
461 && self.node_local_offset(n).is_some_and(|current| {
462 (current - local_offset).magnitude_squared() <= 1.0e-8
463 })
464 {
465 continue;
466 }
467 let move_started_at = Instant::now();
468 let collider_handle = if already_on_target {
469 self.update_node_collider_local_pose(
470 source.collider_handle,
471 local_offset,
472 colliders,
473 )
474 } else {
475 self.move_node_collider_to_body(
476 n,
477 target_body,
478 source.collider_handle,
479 local_offset,
480 bodies,
481 colliders,
482 )
483 };
484 let move_ms = move_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
485 if source.collider_handle.is_some() {
486 result.stats.moved_colliders += 1;
487 result.stats.collider_move_ms += move_ms;
488 } else {
489 result.stats.inserted_colliders += 1;
490 result.stats.collider_insert_ms += move_ms;
491 }
492 self.attach_node(n, target_body, collider_handle, local_offset);
493 }
494
495 self.body_to_nodes.insert(target_body, child.nodes.clone());
496 let reset_metadata = !matches!(target, PlannedBodyTarget::Existing(_));
497 self.refresh_body_metadata(target_body, &child.nodes, created_at, reset_metadata);
498 self.promote_small_body_damping(target_body, bodies, &small_body_damping);
499 self.configure_sleep_thresholds(target_body, bodies, &sleep_thresholds);
500 self.apply_collision_groups(target_body, bodies, colliders, max_colliders_for_debris);
501 let sleep_started_at = Instant::now();
502 self.apply_child_kinematics(
503 target_body,
504 child.nodes.iter().any(|n| self.support_nodes.contains(n)),
505 target_state,
506 bodies,
507 );
508 result.stats.sleep_init_ms += sleep_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
509 }
510
511 result.cohort_handles = child_targets
512 .values()
513 .map(|target| target.handle())
514 .collect::<HashSet<_>>()
515 .into_iter()
516 .collect();
517
518 let reused_handles: HashSet<RigidBodyHandle> = reused_by_body.keys().copied().collect();
519 let recycled_handles: HashSet<RigidBodyHandle> = child_targets
520 .values()
521 .filter_map(|target| match target {
522 PlannedBodyTarget::Recycled(handle) => Some(*handle),
523 _ => None,
524 })
525 .collect();
526 for &body_handle in &planning.parent_bodies {
527 if reused_handles.contains(&body_handle) || recycled_handles.contains(&body_handle) {
528 continue;
529 }
530 let retire_started_at = Instant::now();
531 self.body_to_nodes.remove(&body_handle);
532 self.body_metadata.remove(&body_handle);
533 if bodies.get(body_handle).is_some() {
534 bodies.remove(
535 body_handle,
536 island_manager,
537 colliders,
538 impulse_joints,
539 multibody_joints,
540 true,
541 );
542 result.stats.retired_bodies += 1;
543 }
544 result.stats.body_retire_ms +=
545 retire_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
546 }
547
548 result.stats.apply_ms = apply_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
549 result
550 }
551
552 pub fn node_body(&self, node_index: u32) -> Option<RigidBodyHandle> {
554 self.node_to_body
555 .get(node_index as usize)
556 .copied()
557 .flatten()
558 }
559
560 pub fn node_collider(&self, node_index: u32) -> Option<ColliderHandle> {
562 self.node_to_collider
563 .get(node_index as usize)
564 .copied()
565 .flatten()
566 }
567
568 pub fn collider_node(&self, collider: ColliderHandle) -> Option<u32> {
570 self.collider_to_node.get(&collider).copied()
571 }
572
573 pub fn node_local_offset(&self, node_index: u32) -> Option<Vec3> {
575 let idx = node_index as usize;
576 if self.node_to_body.get(idx).copied().flatten().is_some() {
577 self.node_local_offsets.get(idx).copied()
578 } else {
579 None
580 }
581 }
582
583 pub fn body_nodes(&self, body_handle: RigidBodyHandle) -> Vec<u32> {
585 self.body_to_nodes
586 .get(&body_handle)
587 .cloned()
588 .unwrap_or_default()
589 }
590
591 pub fn body_nodes_slice(&self, body_handle: RigidBodyHandle) -> &[u32] {
592 self.body_to_nodes
593 .get(&body_handle)
594 .map(Vec::as_slice)
595 .unwrap_or(&[])
596 }
597
598 pub fn body_node_count(&self, body_handle: RigidBodyHandle) -> usize {
599 self.body_to_nodes
600 .get(&body_handle)
601 .map(Vec::len)
602 .unwrap_or(0)
603 }
604
605 pub fn body_has_support(&self, body_handle: RigidBodyHandle) -> bool {
606 self.body_metadata
607 .get(&body_handle)
608 .map(|metadata| metadata.has_support)
609 .unwrap_or(false)
610 }
611
612 pub fn is_support(&self, node_index: u32) -> bool {
614 self.support_nodes.contains(&node_index)
615 }
616
617 pub fn body_count(&self) -> usize {
619 self.body_to_nodes.len()
620 }
621
622 pub fn collider_count(&self) -> usize {
624 self.node_to_collider
625 .iter()
626 .filter(|handle| handle.is_some())
627 .count()
628 }
629
630 pub fn dynamic_body_count(&self, bodies: &RigidBodySet) -> usize {
632 self.body_to_nodes
633 .keys()
634 .filter(|h| bodies.get(**h).map(|b| b.is_dynamic()).unwrap_or(false))
635 .count()
636 }
637
638 pub fn awake_dynamic_body_count(&self, bodies: &RigidBodySet) -> usize {
639 self.body_to_nodes
640 .keys()
641 .filter(|h| {
642 bodies
643 .get(**h)
644 .map(|b| b.is_dynamic() && !b.is_sleeping())
645 .unwrap_or(false)
646 })
647 .count()
648 }
649
650 pub fn sleeping_dynamic_body_count(&self, bodies: &RigidBodySet) -> usize {
651 self.body_to_nodes
652 .keys()
653 .filter(|h| {
654 bodies
655 .get(**h)
656 .map(|b| b.is_dynamic() && b.is_sleeping())
657 .unwrap_or(false)
658 })
659 .count()
660 }
661
662 pub fn support_body_count(&self) -> usize {
663 self.body_metadata
664 .values()
665 .filter(|metadata| metadata.has_support)
666 .count()
667 }
668
669 pub fn ccd_enabled_body_count(&self, bodies: &RigidBodySet) -> usize {
670 self.body_to_nodes
671 .keys()
672 .filter(|h| bodies.get(**h).map(|b| b.is_ccd_enabled()).unwrap_or(false))
673 .count()
674 }
675
676 pub fn estimate_split_cost(&self, event: &SplitEvent, bodies: &RigidBodySet) -> SplitCost {
677 let existing = self.collect_existing_body_states(event, bodies);
678 let child_support: Vec<super::split_migrator::PlannerChildSupport> = event
679 .children
680 .iter()
681 .map(|child| super::split_migrator::PlannerChildSupport {
682 is_support: child.nodes.iter().any(|n| self.support_nodes.contains(n)),
683 })
684 .collect();
685 let plan = super::split_migrator::plan_split_migration_with_support(
686 &existing,
687 &event.children,
688 &child_support,
689 );
690 let recyclable_bodies = existing.len().saturating_sub(plan.reuse.len());
691 SplitCost {
692 create_bodies: plan.create.len().saturating_sub(recyclable_bodies),
693 collider_migrations: event.children.iter().map(|child| child.nodes.len()).sum(),
694 }
695 }
696
697 pub fn mark_support_contact(
698 &mut self,
699 body_handle: RigidBodyHandle,
700 now_secs: f32,
701 bodies: &mut RigidBodySet,
702 colliders: &mut ColliderSet,
703 damping: SmallBodyDampingOptions,
704 sleep_thresholds: SleepThresholdOptions,
705 max_colliders_for_debris: usize,
706 ) -> bool {
707 let collider_count = self.body_node_count(body_handle);
708 let Some(metadata) = self.body_metadata.get_mut(&body_handle) else {
709 return false;
710 };
711 if metadata.has_support {
712 return false;
713 }
714 metadata.first_support_contact_at.get_or_insert(now_secs);
715 let mut changed = false;
716 if damping.mode == OptimizationMode::AfterGroundCollision
717 && !metadata.damping_promoted
718 && collider_count <= damping.collider_count_threshold
719 {
720 if Self::apply_small_body_damping(bodies, body_handle, &damping) {
721 metadata.damping_promoted = true;
722 changed = true;
723 }
724 }
725 if sleep_thresholds.mode == OptimizationMode::AfterGroundCollision
726 && !metadata.sleep_threshold_configured
727 {
728 if Self::apply_sleep_thresholds(bodies, body_handle, &sleep_thresholds) {
729 metadata.sleep_threshold_configured = true;
730 changed = true;
731 }
732 }
733 if metadata.first_support_contact_at == Some(now_secs)
734 && self.debris_collision_mode != DebrisCollisionMode::All
735 {
736 self.apply_collision_groups(body_handle, bodies, colliders, max_colliders_for_debris);
737 changed = true;
738 }
739 changed
740 }
741
742 pub fn cleanup_expired_bodies(
743 &mut self,
744 now_secs: f32,
745 cleanup: DebrisCleanupOptions,
746 bodies: &mut RigidBodySet,
747 colliders: &mut ColliderSet,
748 island_manager: &mut IslandManager,
749 impulse_joints: &mut ImpulseJointSet,
750 multibody_joints: &mut MultibodyJointSet,
751 ) -> OptimizationResult {
752 let mut result = OptimizationResult::default();
753 if cleanup.mode == OptimizationMode::Off {
754 return result;
755 }
756
757 let mut expired = Vec::new();
758 for (&body_handle, metadata) in &self.body_metadata {
759 if metadata.has_support {
760 continue;
761 }
762 let Some(body) = bodies.get(body_handle) else {
763 continue;
764 };
765 if !body.is_dynamic() {
766 continue;
767 }
768 if self.body_node_count(body_handle) > cleanup.max_colliders_for_debris {
769 continue;
770 }
771
772 let anchor = match cleanup.mode {
773 OptimizationMode::Off => None,
774 OptimizationMode::Always => Some(metadata.created_at),
775 OptimizationMode::AfterGroundCollision => metadata.first_support_contact_at,
776 };
777 let Some(anchor) = anchor else {
778 continue;
779 };
780 if cleanup.mode == OptimizationMode::AfterGroundCollision && !body.is_sleeping() {
781 continue;
782 }
783 if now_secs - anchor >= cleanup.debris_ttl_secs {
784 expired.push(body_handle);
785 }
786 }
787
788 for body_handle in expired {
789 let removed_nodes = self.remove_body(
790 body_handle,
791 bodies,
792 colliders,
793 island_manager,
794 impulse_joints,
795 multibody_joints,
796 );
797 if !removed_nodes.is_empty() {
798 result.removed_bodies.push(body_handle);
799 result.removed_nodes.extend(removed_nodes);
800 }
801 }
802
803 result
804 }
805
806 pub fn destroy_nodes(
807 &mut self,
808 nodes: &[u32],
809 bodies: &mut RigidBodySet,
810 colliders: &mut ColliderSet,
811 island_manager: &mut IslandManager,
812 impulse_joints: &mut ImpulseJointSet,
813 multibody_joints: &mut MultibodyJointSet,
814 ) -> Vec<u32> {
815 let mut removed = Vec::new();
816 let mut affected_bodies = HashSet::new();
817
818 for &node_index in nodes {
819 let idx = node_index as usize;
820 let Some(body_handle) = self.node_to_body[idx] else {
821 continue;
822 };
823 if let Some(collider_handle) = self.node_to_collider[idx] {
824 colliders.remove(collider_handle, island_manager, bodies, true);
825 }
826 self.detach_node(node_index);
827 if let Some(body_nodes) = self.body_to_nodes.get_mut(&body_handle) {
828 body_nodes.retain(|node| *node != node_index);
829 }
830 affected_bodies.insert(body_handle);
831 removed.push(node_index);
832 }
833
834 for body_handle in affected_bodies {
835 let should_remove = self
836 .body_to_nodes
837 .get(&body_handle)
838 .map(|nodes| nodes.is_empty())
839 .unwrap_or(true);
840 if should_remove {
841 self.body_to_nodes.remove(&body_handle);
842 self.body_metadata.remove(&body_handle);
843 if bodies.get(body_handle).is_some() {
844 bodies.remove(
845 body_handle,
846 island_manager,
847 colliders,
848 impulse_joints,
849 multibody_joints,
850 true,
851 );
852 }
853 } else if let Some(metadata) = self.body_metadata.get_mut(&body_handle) {
854 metadata.has_support = self
855 .body_to_nodes
856 .get(&body_handle)
857 .map(|nodes| nodes.iter().any(|node| self.support_nodes.contains(node)))
858 .unwrap_or(false);
859 }
860 }
861
862 removed
863 }
864
865 fn compute_actor_com(&self, nodes: &[u32]) -> Vec3 {
866 if nodes.is_empty() {
867 return Vec3::ZERO;
868 }
869 let mut sum = Vec3::ZERO;
870 for &n in nodes {
871 sum += self.node_centroids[n as usize];
872 }
873 sum / nodes.len() as f32
874 }
875
876 fn compute_child_target_state(
877 &self,
878 child: &SplitChild,
879 planning: &SplitPlanningData,
880 has_support: bool,
881 ) -> (ChildTargetState, f32, f32) {
882 let pose_started_at = Instant::now();
883 let pose = if self.split_child_recentering_enabled {
884 self.compute_child_target_pose(child, planning, has_support)
885 } else {
886 self.inherited_child_target_pose(child, planning)
887 };
888 let child_pose_ms = pose_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
889 let velocity_fit_started_at = Instant::now();
890 let (linvel, angvel, should_sleep) = if self.split_child_velocity_fit_enabled {
891 self.fit_child_motion(child, planning, has_support, &pose)
892 } else {
893 self.inherited_child_motion(child, planning, has_support)
894 };
895 let velocity_fit_ms = velocity_fit_started_at.elapsed().as_secs_f64() as f32 * 1_000.0;
896 (
897 ChildTargetState {
898 pose,
899 linvel,
900 angvel,
901 should_sleep,
902 },
903 child_pose_ms,
904 velocity_fit_ms,
905 )
906 }
907
908 fn inherited_child_target_pose(
909 &self,
910 child: &SplitChild,
911 planning: &SplitPlanningData,
912 ) -> Isometry<Real> {
913 let Some(body_handle) = self.dominant_child_body_handle(child, planning) else {
914 return Isometry::identity();
915 };
916 planning
917 .parent_states
918 .get(&body_handle)
919 .map(|state| state.position)
920 .unwrap_or_else(Isometry::identity)
921 }
922
923 fn compute_child_target_pose(
924 &self,
925 child: &SplitChild,
926 planning: &SplitPlanningData,
927 has_support: bool,
928 ) -> Isometry<Real> {
929 let translation = if has_support {
930 self.child_world_centroid(child, planning)
931 } else {
932 self.child_world_center_of_mass(child, planning)
933 };
934 let rotation = self.dominant_child_rotation(child, planning);
935 Isometry::from_parts(
936 Translation::new(translation.x, translation.y, translation.z),
937 rotation,
938 )
939 }
940
941 fn child_world_centroid(&self, child: &SplitChild, planning: &SplitPlanningData) -> Vec3 {
942 let mut sum = Vec3::ZERO;
943 let mut count = 0usize;
944 for &node in &child.nodes {
945 if let Some(source) = planning.node_sources.get(&node) {
946 sum += source.world_centroid;
947 count += 1;
948 }
949 }
950 if count == 0 {
951 self.compute_actor_com(&child.nodes)
952 } else {
953 sum / count as f32
954 }
955 }
956
957 fn child_world_center_of_mass(&self, child: &SplitChild, planning: &SplitPlanningData) -> Vec3 {
958 let mut weighted_sum = Vec3::ZERO;
959 let mut total_mass = 0.0f32;
960 for &node in &child.nodes {
961 if let Some(source) = planning.node_sources.get(&node) {
962 let mass = self.node_masses[node as usize].max(0.0);
963 if mass > 0.0 {
964 weighted_sum += source.world_centroid * mass;
965 total_mass += mass;
966 }
967 }
968 }
969 if total_mass <= f32::EPSILON {
970 self.child_world_centroid(child, planning)
971 } else {
972 weighted_sum / total_mass
973 }
974 }
975
976 fn dominant_child_rotation(
977 &self,
978 child: &SplitChild,
979 planning: &SplitPlanningData,
980 ) -> UnitQuaternion<Real> {
981 let Some(body_handle) = self.dominant_child_body_handle(child, planning) else {
982 return UnitQuaternion::identity();
983 };
984 planning
985 .parent_states
986 .get(&body_handle)
987 .map(|state| state.position.rotation)
988 .unwrap_or_else(UnitQuaternion::identity)
989 }
990
991 fn dominant_child_body_handle(
992 &self,
993 child: &SplitChild,
994 planning: &SplitPlanningData,
995 ) -> Option<RigidBodyHandle> {
996 let mut body_weights: HashMap<RigidBodyHandle, f32> = HashMap::new();
997 for &node in &child.nodes {
998 let Some(source) = planning.node_sources.get(&node) else {
999 continue;
1000 };
1001 *body_weights.entry(source.body_handle).or_insert(0.0) +=
1002 self.node_masses[node as usize].max(1.0);
1003 }
1004 let Some((&body_handle, _)) = body_weights
1005 .iter()
1006 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
1007 else {
1008 return None;
1009 };
1010 Some(body_handle)
1011 }
1012
1013 fn fit_child_motion(
1014 &self,
1015 child: &SplitChild,
1016 planning: &SplitPlanningData,
1017 has_support: bool,
1018 target_pose: &Isometry<Real>,
1019 ) -> (Vector<Real>, Vector<Real>, bool) {
1020 if has_support {
1021 return (Vector::zeros(), Vector::zeros(), false);
1022 }
1023
1024 let child_com = Point::from(target_pose.translation.vector);
1025 let mut linvel_sum = Vector::zeros();
1026 let mut total_mass = 0.0f32;
1027 let mut all_sleeping = true;
1028 let mut samples = Vec::new();
1029 let mut source_weights: HashMap<RigidBodyHandle, f32> = HashMap::new();
1030
1031 for &node in &child.nodes {
1032 let Some(source) = planning.node_sources.get(&node) else {
1033 continue;
1034 };
1035 let Some(parent_state) = planning.parent_states.get(&source.body_handle) else {
1036 continue;
1037 };
1038 let mass = self.node_masses[node as usize].max(1.0e-4);
1039 let velocity = vector![
1040 source.world_velocity.x,
1041 source.world_velocity.y,
1042 source.world_velocity.z
1043 ];
1044 linvel_sum += velocity * mass;
1045 total_mass += mass;
1046 all_sleeping &= parent_state.was_sleeping;
1047 *source_weights.entry(source.body_handle).or_insert(0.0) += mass;
1048 let point = point![
1049 source.world_centroid.x,
1050 source.world_centroid.y,
1051 source.world_centroid.z
1052 ];
1053 samples.push((point, velocity, mass));
1054 }
1055
1056 if samples.is_empty() || total_mass <= f32::EPSILON {
1057 return (Vector::zeros(), Vector::zeros(), false);
1058 }
1059
1060 let linvel = linvel_sum / total_mass;
1061 let mut normal = [[0.0f32; 3]; 3];
1062 let mut rhs = [0.0f32; 3];
1063 for (point, velocity, mass) in &samples {
1064 let r = point - child_com;
1065 let r_vec = Vec3::new(r.x, r.y, r.z);
1066 let v_rel = Vec3::new(
1067 velocity.x - linvel.x,
1068 velocity.y - linvel.y,
1069 velocity.z - linvel.z,
1070 );
1071 let r2 = r_vec.magnitude_squared();
1072 let rr = [
1073 [r_vec.x * r_vec.x, r_vec.x * r_vec.y, r_vec.x * r_vec.z],
1074 [r_vec.y * r_vec.x, r_vec.y * r_vec.y, r_vec.y * r_vec.z],
1075 [r_vec.z * r_vec.x, r_vec.z * r_vec.y, r_vec.z * r_vec.z],
1076 ];
1077 for row in 0..3 {
1078 for col in 0..3 {
1079 normal[row][col] += mass * ((if row == col { r2 } else { 0.0 }) - rr[row][col]);
1080 }
1081 }
1082 let cross = r_vec.cross(v_rel);
1083 rhs[0] += mass * cross.x;
1084 rhs[1] += mass * cross.y;
1085 rhs[2] += mass * cross.z;
1086 }
1087
1088 let dominant_source = source_weights
1089 .iter()
1090 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
1091 .and_then(|(handle, _)| planning.parent_states.get(handle));
1092
1093 let angvel = self
1094 .solve_symmetric_3x3(normal, rhs)
1095 .map(|omega| vector![omega.x, omega.y, omega.z])
1096 .or_else(|| dominant_source.map(|state| state.angvel))
1097 .unwrap_or_else(Vector::zeros);
1098
1099 let should_sleep =
1100 all_sleeping && linvel.norm_squared() <= 1.0e-4 && angvel.norm_squared() <= 1.0e-4;
1101
1102 (linvel, angvel, should_sleep)
1103 }
1104
1105 fn inherited_child_motion(
1106 &self,
1107 child: &SplitChild,
1108 planning: &SplitPlanningData,
1109 has_support: bool,
1110 ) -> (Vector<Real>, Vector<Real>, bool) {
1111 if has_support {
1112 return (Vector::zeros(), Vector::zeros(), false);
1113 }
1114 let Some(body_handle) = self.dominant_child_body_handle(child, planning) else {
1115 return (Vector::zeros(), Vector::zeros(), false);
1116 };
1117 let Some(state) = planning.parent_states.get(&body_handle) else {
1118 return (Vector::zeros(), Vector::zeros(), false);
1119 };
1120 let should_sleep = state.was_sleeping
1121 && state.linvel.norm_squared() <= 1.0e-4
1122 && state.angvel.norm_squared() <= 1.0e-4;
1123 (state.linvel, state.angvel, should_sleep)
1124 }
1125
1126 fn desired_body_type(has_support: bool) -> RigidBodyType {
1127 if has_support {
1128 RigidBodyType::Fixed
1129 } else {
1130 RigidBodyType::Dynamic
1131 }
1132 }
1133
1134 fn reconcile_reused_body_type(
1135 &self,
1136 body_handle: RigidBodyHandle,
1137 has_support: bool,
1138 bodies: &mut RigidBodySet,
1139 ) -> bool {
1140 let desired = Self::desired_body_type(has_support);
1141 let Some(body) = bodies.get_mut(body_handle) else {
1142 return false;
1143 };
1144 if body.body_type() == desired {
1145 return false;
1146 }
1147 body.set_body_type(desired, true);
1148 if desired == RigidBodyType::Dynamic {
1149 body.wake_up(true);
1150 }
1151 true
1152 }
1153
1154 fn reinitialize_recycled_body(
1155 &self,
1156 body_handle: RigidBodyHandle,
1157 has_support: bool,
1158 target: ChildTargetState,
1159 bodies: &mut RigidBodySet,
1160 ) -> bool {
1161 let desired = Self::desired_body_type(has_support);
1162 let Some(body) = bodies.get_mut(body_handle) else {
1163 return false;
1164 };
1165 let flipped = body.body_type() != desired;
1166 if flipped {
1167 body.set_body_type(desired, true);
1168 }
1169 body.set_position(target.pose, false);
1170 if desired == RigidBodyType::Dynamic {
1171 body.set_linvel(target.linvel, false);
1172 body.set_angvel(target.angvel, false);
1173 if target.should_sleep {
1174 body.sleep();
1175 } else {
1176 body.wake_up(true);
1177 }
1178 }
1179 flipped
1180 }
1181
1182 fn create_body_for_child(
1183 &self,
1184 child: &SplitChild,
1185 target: ChildTargetState,
1186 bodies: &mut RigidBodySet,
1187 ) -> RigidBodyHandle {
1188 let has_support = child.nodes.iter().any(|n| self.support_nodes.contains(n));
1189 if has_support {
1190 bodies.insert(RigidBodyBuilder::fixed().pose(target.pose))
1191 } else {
1192 let handle = bodies.insert(
1193 RigidBodyBuilder::dynamic()
1194 .pose(target.pose)
1195 .linvel(target.linvel)
1196 .angvel(target.angvel)
1197 .ccd_enabled(self.dynamic_body_ccd_enabled),
1198 );
1199 if target.should_sleep {
1200 if let Some(body) = bodies.get_mut(handle) {
1201 body.sleep();
1202 }
1203 }
1204 handle
1205 }
1206 }
1207
1208 fn apply_child_kinematics(
1209 &self,
1210 body_handle: RigidBodyHandle,
1211 has_support: bool,
1212 target: ChildTargetState,
1213 bodies: &mut RigidBodySet,
1214 ) {
1215 let Some(body) = bodies.get_mut(body_handle) else {
1216 return;
1217 };
1218 body.set_position(target.pose, false);
1219 if has_support || !body.is_dynamic() {
1220 return;
1221 }
1222 let current_linvel = *body.linvel();
1223 let current_angvel = *body.angvel();
1224 let velocity_changed = (current_linvel - target.linvel).norm_squared() > 1.0e-6
1225 || (current_angvel - target.angvel).norm_squared() > 1.0e-6;
1226 body.set_linvel(target.linvel, false);
1227 body.set_angvel(target.angvel, false);
1228 if target.should_sleep {
1229 body.sleep();
1230 } else if velocity_changed || body.is_sleeping() {
1231 body.wake_up(true);
1232 }
1233 }
1234
1235 fn solve_symmetric_3x3(&self, m: [[f32; 3]; 3], rhs: [f32; 3]) -> Option<Vec3> {
1236 let det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
1237 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
1238 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
1239 if det.abs() <= 1.0e-6 {
1240 return None;
1241 }
1242 let inv_det = 1.0 / det;
1243 let inv = [
1244 [
1245 (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * inv_det,
1246 (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * inv_det,
1247 (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * inv_det,
1248 ],
1249 [
1250 (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * inv_det,
1251 (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * inv_det,
1252 (m[0][2] * m[1][0] - m[0][0] * m[1][2]) * inv_det,
1253 ],
1254 [
1255 (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * inv_det,
1256 (m[0][1] * m[2][0] - m[0][0] * m[2][1]) * inv_det,
1257 (m[0][0] * m[1][1] - m[0][1] * m[1][0]) * inv_det,
1258 ],
1259 ];
1260 Some(Vec3::new(
1261 inv[0][0] * rhs[0] + inv[0][1] * rhs[1] + inv[0][2] * rhs[2],
1262 inv[1][0] * rhs[0] + inv[1][1] * rhs[1] + inv[1][2] * rhs[2],
1263 inv[2][0] * rhs[0] + inv[2][1] * rhs[1] + inv[2][2] * rhs[2],
1264 ))
1265 }
1266
1267 fn attach_node(
1268 &mut self,
1269 node_index: u32,
1270 body: RigidBodyHandle,
1271 collider: ColliderHandle,
1272 local_offset: Vec3,
1273 ) {
1274 let idx = node_index as usize;
1275 self.node_to_body[idx] = Some(body);
1276 self.node_to_collider[idx] = Some(collider);
1277 self.collider_to_node.insert(collider, node_index);
1278 self.node_local_offsets[idx] = local_offset;
1279 }
1280
1281 fn detach_node(&mut self, node_index: u32) {
1282 let idx = node_index as usize;
1283 if let Some(collider) = self.node_to_collider[idx].take() {
1284 self.collider_to_node.remove(&collider);
1285 }
1286 self.node_to_body[idx] = None;
1287 self.node_local_offsets[idx] = Vec3::ZERO;
1288 }
1289
1290 fn update_body_metadata(
1291 &mut self,
1292 body_handle: RigidBodyHandle,
1293 nodes: &[u32],
1294 created_at: f32,
1295 ) {
1296 let has_support = nodes.iter().any(|node| self.support_nodes.contains(node));
1297 self.body_metadata.insert(
1298 body_handle,
1299 BodyMetadata {
1300 created_at,
1301 first_support_contact_at: None,
1302 damping_promoted: false,
1303 sleep_threshold_configured: false,
1304 has_support,
1305 },
1306 );
1307 }
1308
1309 fn refresh_body_metadata(
1310 &mut self,
1311 body_handle: RigidBodyHandle,
1312 nodes: &[u32],
1313 created_at: f32,
1314 reset: bool,
1315 ) {
1316 if reset || !self.body_metadata.contains_key(&body_handle) {
1317 self.update_body_metadata(body_handle, nodes, created_at);
1318 return;
1319 }
1320
1321 let has_support = nodes.iter().any(|node| self.support_nodes.contains(node));
1322 if let Some(metadata) = self.body_metadata.get_mut(&body_handle) {
1323 let support_changed = metadata.has_support != has_support;
1324 metadata.has_support = has_support;
1325 if support_changed && !has_support {
1326 metadata.first_support_contact_at = None;
1327 metadata.damping_promoted = false;
1328 metadata.sleep_threshold_configured = false;
1329 }
1330 }
1331 }
1332
1333 fn promote_small_body_damping(
1334 &mut self,
1335 body_handle: RigidBodyHandle,
1336 bodies: &mut RigidBodySet,
1337 damping: &SmallBodyDampingOptions,
1338 ) {
1339 if damping.mode != OptimizationMode::Always {
1340 return;
1341 }
1342 if self.body_node_count(body_handle) > damping.collider_count_threshold {
1343 return;
1344 }
1345 if Self::apply_small_body_damping(bodies, body_handle, damping) {
1346 if let Some(metadata) = self.body_metadata.get_mut(&body_handle) {
1347 metadata.damping_promoted = true;
1348 }
1349 }
1350 }
1351
1352 fn configure_sleep_thresholds(
1353 &mut self,
1354 body_handle: RigidBodyHandle,
1355 bodies: &mut RigidBodySet,
1356 sleep_thresholds: &SleepThresholdOptions,
1357 ) {
1358 if sleep_thresholds.mode != OptimizationMode::Always {
1359 return;
1360 }
1361 if Self::apply_sleep_thresholds(bodies, body_handle, sleep_thresholds) {
1362 if let Some(metadata) = self.body_metadata.get_mut(&body_handle) {
1363 metadata.sleep_threshold_configured = true;
1364 }
1365 }
1366 }
1367
1368 fn apply_small_body_damping(
1369 bodies: &mut RigidBodySet,
1370 body_handle: RigidBodyHandle,
1371 damping: &SmallBodyDampingOptions,
1372 ) -> bool {
1373 let Some(body) = bodies.get_mut(body_handle) else {
1374 return false;
1375 };
1376 if !body.is_dynamic() {
1377 return false;
1378 }
1379 let mut changed = false;
1380 if body.linear_damping() < damping.min_linear_damping {
1381 body.set_linear_damping(damping.min_linear_damping);
1382 changed = true;
1383 }
1384 if body.angular_damping() < damping.min_angular_damping {
1385 body.set_angular_damping(damping.min_angular_damping);
1386 changed = true;
1387 }
1388 if changed {
1389 body.wake_up(true);
1390 }
1391 changed
1392 }
1393
1394 fn apply_sleep_thresholds(
1395 bodies: &mut RigidBodySet,
1396 body_handle: RigidBodyHandle,
1397 sleep_thresholds: &SleepThresholdOptions,
1398 ) -> bool {
1399 let Some(body) = bodies.get_mut(body_handle) else {
1400 return false;
1401 };
1402 if !body.is_dynamic() {
1403 return false;
1404 }
1405 let activation = body.activation_mut();
1406 let desired_linear = sleep_thresholds.linear_threshold.max(0.0);
1407 let desired_angular = sleep_thresholds.angular_threshold.max(0.0);
1408 let changed = (activation.normalized_linear_threshold - desired_linear).abs()
1409 > Real::EPSILON
1410 || (activation.angular_threshold - desired_angular).abs() > Real::EPSILON;
1411 activation.normalized_linear_threshold = desired_linear;
1412 activation.angular_threshold = desired_angular;
1413 if changed {
1414 body.wake_up(true);
1415 }
1416 changed
1417 }
1418
1419 fn remove_body(
1420 &mut self,
1421 body_handle: RigidBodyHandle,
1422 bodies: &mut RigidBodySet,
1423 colliders: &mut ColliderSet,
1424 island_manager: &mut IslandManager,
1425 impulse_joints: &mut ImpulseJointSet,
1426 multibody_joints: &mut MultibodyJointSet,
1427 ) -> Vec<u32> {
1428 let nodes = self.body_to_nodes.remove(&body_handle).unwrap_or_default();
1429 for node in &nodes {
1430 self.detach_node(*node);
1431 }
1432 self.body_metadata.remove(&body_handle);
1433 if bodies.get(body_handle).is_some() {
1434 bodies.remove(
1435 body_handle,
1436 island_manager,
1437 colliders,
1438 impulse_joints,
1439 multibody_joints,
1440 true,
1441 );
1442 }
1443 nodes
1444 }
1445
1446 fn collect_existing_body_states(
1447 &self,
1448 event: &SplitEvent,
1449 bodies: &RigidBodySet,
1450 ) -> Vec<super::split_migrator::ExistingBodyState> {
1451 let mut parent_bodies = HashSet::new();
1452 for child in &event.children {
1453 for &node in &child.nodes {
1454 if let Some(handle) = self.node_body(node) {
1455 parent_bodies.insert(handle);
1456 }
1457 }
1458 }
1459
1460 parent_bodies
1461 .into_iter()
1462 .map(|handle| super::split_migrator::ExistingBodyState {
1463 handle,
1464 node_indices: self
1465 .body_to_nodes
1466 .get(&handle)
1467 .map(|nodes| nodes.iter().copied().collect())
1468 .unwrap_or_default(),
1469 is_fixed: bodies.get(handle).map(|b| !b.is_dynamic()).unwrap_or(false),
1470 })
1471 .collect()
1472 }
1473
1474 fn collect_split_planning_data(
1475 &self,
1476 event: &SplitEvent,
1477 bodies: &RigidBodySet,
1478 ) -> SplitPlanningData {
1479 let mut parent_bodies = HashSet::new();
1480 let mut node_sources = HashMap::new();
1481
1482 for child in &event.children {
1483 for &node in &child.nodes {
1484 let idx = node as usize;
1485 let Some(body_handle) = self.node_to_body.get(idx).copied().flatten() else {
1486 continue;
1487 };
1488 let Some(body) = bodies.get(body_handle) else {
1489 continue;
1490 };
1491 let local_offset = self.node_local_offsets[idx];
1492 let world = body.position().transform_point(&point![
1493 local_offset.x,
1494 local_offset.y,
1495 local_offset.z
1496 ]);
1497 let world_velocity = body.velocity_at_point(&world);
1498 parent_bodies.insert(body_handle);
1499 node_sources.entry(node).or_insert(NodeSourceState {
1500 body_handle,
1501 collider_handle: self.node_to_collider[idx],
1502 world_centroid: Vec3::new(world.x, world.y, world.z),
1503 world_velocity: Vec3::new(world_velocity.x, world_velocity.y, world_velocity.z),
1504 });
1505 }
1506 }
1507
1508 let parent_bodies: Vec<RigidBodyHandle> = parent_bodies.into_iter().collect();
1509 let parent_states = parent_bodies
1510 .iter()
1511 .filter_map(|&handle| {
1512 let body = bodies.get(handle)?;
1513 Some((
1514 handle,
1515 BodyState {
1516 position: *body.position(),
1517 linvel: *body.linvel(),
1518 angvel: *body.angvel(),
1519 was_sleeping: body.is_sleeping(),
1520 },
1521 ))
1522 })
1523 .collect();
1524
1525 SplitPlanningData {
1526 existing: self.collect_existing_body_states(event, bodies),
1527 parent_bodies,
1528 parent_states,
1529 node_sources,
1530 }
1531 }
1532
1533 fn local_offset_for_pose(
1534 &self,
1535 source: &NodeSourceState,
1536 target_pose: &Isometry<Real>,
1537 ) -> Vec3 {
1538 let local = target_pose.inverse_transform_point(&point![
1539 source.world_centroid.x,
1540 source.world_centroid.y,
1541 source.world_centroid.z
1542 ]);
1543 Vec3::new(local.x, local.y, local.z)
1544 }
1545
1546 fn insert_node_collider(
1547 &self,
1548 node_index: u32,
1549 body_handle: RigidBodyHandle,
1550 local_offset: Vec3,
1551 bodies: &mut RigidBodySet,
1552 colliders: &mut ColliderSet,
1553 ) -> ColliderHandle {
1554 let collider = self.build_node_collider(node_index, local_offset);
1555 colliders.insert_with_parent(collider, body_handle, bodies)
1556 }
1557
1558 fn build_node_collider(&self, node_index: u32, local_offset: Vec3) -> ColliderBuilder {
1559 let ni = node_index as usize;
1560 let fallback_size = self.node_sizes[ni];
1561 let fallback_half = vector![
1562 fallback_size.x * 0.5,
1563 fallback_size.y * 0.5,
1564 fallback_size.z * 0.5
1565 ];
1566 let base = match self.node_colliders.get(ni).and_then(Option::as_ref) {
1567 Some(ScenarioCollider::Cuboid { half_extents }) => {
1568 ColliderBuilder::cuboid(half_extents.x, half_extents.y, half_extents.z)
1569 }
1570 Some(ScenarioCollider::ConvexHull { points }) => {
1571 let hull_points: Vec<Point<Real>> = points
1572 .iter()
1573 .map(|point| point![point.x, point.y, point.z])
1574 .collect();
1575 ColliderBuilder::convex_hull(&hull_points).unwrap_or_else(|| {
1576 ColliderBuilder::cuboid(fallback_half.x, fallback_half.y, fallback_half.z)
1577 })
1578 }
1579 None => ColliderBuilder::cuboid(fallback_half.x, fallback_half.y, fallback_half.z),
1580 };
1581
1582 let collider = base
1583 .translation(vector![local_offset.x, local_offset.y, local_offset.z])
1584 .active_events(ActiveEvents::CONTACT_FORCE_EVENTS | ActiveEvents::COLLISION_EVENTS)
1585 .active_hooks(ActiveHooks::FILTER_CONTACT_PAIRS | ActiveHooks::FILTER_INTERSECTION_PAIR)
1586 .contact_force_event_threshold(0.0)
1587 .friction(0.25)
1588 .restitution(0.0);
1589
1590 if self.support_nodes.contains(&node_index) {
1591 collider.mass(0.0)
1592 } else {
1593 collider.mass(self.node_masses[ni].max(0.0))
1594 }
1595 }
1596
1597 fn move_node_collider_to_body(
1598 &self,
1599 node_index: u32,
1600 body_handle: RigidBodyHandle,
1601 collider_handle: Option<ColliderHandle>,
1602 local_offset: Vec3,
1603 bodies: &mut RigidBodySet,
1604 colliders: &mut ColliderSet,
1605 ) -> ColliderHandle {
1606 if let Some(collider_handle) = collider_handle {
1607 colliders.set_parent(collider_handle, Some(body_handle), bodies);
1608 if let Some(collider) = colliders.get_mut(collider_handle) {
1609 collider.set_position_wrt_parent(Isometry::translation(
1610 local_offset.x,
1611 local_offset.y,
1612 local_offset.z,
1613 ));
1614 }
1615 collider_handle
1616 } else {
1617 self.insert_node_collider(node_index, body_handle, local_offset, bodies, colliders)
1618 }
1619 }
1620
1621 fn update_node_collider_local_pose(
1622 &self,
1623 collider_handle: Option<ColliderHandle>,
1624 local_offset: Vec3,
1625 colliders: &mut ColliderSet,
1626 ) -> ColliderHandle {
1627 let collider_handle = collider_handle.expect("existing node collider should exist");
1628 if let Some(collider) = colliders.get_mut(collider_handle) {
1629 collider.set_position_wrt_parent(Isometry::translation(
1630 local_offset.x,
1631 local_offset.y,
1632 local_offset.z,
1633 ));
1634 }
1635 collider_handle
1636 }
1637
1638 fn apply_collision_groups(
1639 &self,
1640 body_handle: RigidBodyHandle,
1641 bodies: &RigidBodySet,
1642 colliders: &mut ColliderSet,
1643 max_colliders_for_debris: usize,
1644 ) {
1645 apply_collision_groups_for_body(
1646 body_handle,
1647 bodies,
1648 colliders,
1649 self.ground_body_handle,
1650 self.debris_collision_mode,
1651 self.debris_collision_active(body_handle),
1652 max_colliders_for_debris,
1653 );
1654 }
1655
1656 fn debris_collision_active(&self, body_handle: RigidBodyHandle) -> bool {
1657 self.body_metadata
1658 .get(&body_handle)
1659 .and_then(|metadata| metadata.first_support_contact_at)
1660 .is_some()
1661 }
1662}