1#[cfg(not(feature = "std"))]
6#[allow(unused_imports)]
7use simba::scalar::{ComplexField as _, RealField as _};
8
9use crate::alloc_prelude::*;
10use crate::dynamics::RigidBodyHandle;
11use crate::math::{AngVector, AngularInertia, DIM, Pose, Real, Rotation, Vector};
12use crate::utils::{AngularInertiaOps, CrossProduct};
13
14use super::SoftBody;
15use super::soft_body_shape_matching::extract_rotation;
16
17#[derive(Clone, Debug)]
21#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
22pub struct SoftBodyCluster {
23 pub(crate) particles: Vec<u32>,
25 pub(crate) proxy: RigidBodyHandle,
27 #[cfg_attr(feature = "serde-serialize", serde(default))]
31 pub(crate) rotation: Rotation,
32 #[cfg_attr(feature = "serde-serialize", serde(default = "invalid_cell"))]
35 pub(crate) cell: u32,
36 #[cfg_attr(feature = "serde-serialize", serde(default))]
38 pub(crate) shape_matching: bool,
39 pub(crate) shape_matching_target: Option<Pose>,
40 #[cfg_attr(feature = "serde-serialize", serde(skip))]
41 pub(crate) prev_shape_matching_target: Option<Pose>,
42 #[cfg_attr(feature = "serde-serialize", serde(default))]
46 pub(crate) last_gather: (Vector, AngVector),
47 #[cfg_attr(feature = "serde-serialize", serde(default))]
50 pub(crate) shape_impulses: Vec<Vector>,
51 #[cfg_attr(feature = "serde-serialize", serde(default))]
54 pub(crate) meshes: Vec<Option<super::SoftCollisionMesh>>,
55}
56
57#[cfg(feature = "serde-serialize")]
58fn invalid_cell() -> u32 {
59 u32::MAX
60}
61
62impl SoftBodyCluster {
63 pub fn new(particles: Vec<u32>, proxy: RigidBodyHandle, shape_matching: bool) -> Self {
65 SoftBodyCluster {
66 particles,
67 proxy,
68 rotation: Rotation::IDENTITY,
69 cell: u32::MAX,
70 shape_matching,
71 shape_matching_target: None,
72 prev_shape_matching_target: None,
73 last_gather: Default::default(),
74 shape_impulses: vec![],
75 meshes: Vec::new(),
76 }
77 }
78
79 #[inline]
81 pub fn is_live(&self) -> bool {
82 !self.proxy.is_invalid()
83 }
84
85 pub(crate) fn tombstone(&mut self) -> (Vec<u32>, RigidBodyHandle) {
88 self.cell = u32::MAX;
89 self.shape_matching = false;
90 self.shape_matching_target = None;
91 self.prev_shape_matching_target = None;
92 self.shape_impulses.clear();
93 self.meshes.clear();
94 (
95 core::mem::take(&mut self.particles),
96 core::mem::replace(&mut self.proxy, RigidBodyHandle::invalid()),
97 )
98 }
99
100 pub fn particles(&self) -> &[u32] {
102 &self.particles
103 }
104
105 pub fn proxy(&self) -> RigidBodyHandle {
107 self.proxy
108 }
109
110 pub(crate) fn meshes_mut(&mut self) -> impl Iterator<Item = &mut super::SoftCollisionMesh> {
112 self.meshes.iter_mut().flatten()
113 }
114
115 pub fn meshes(&self) -> impl Iterator<Item = &super::SoftCollisionMesh> {
117 self.meshes.iter().flatten()
118 }
119
120 pub fn mesh(&self, i: u32) -> Option<&super::SoftCollisionMesh> {
122 self.meshes.get(i as usize).and_then(|mesh| mesh.as_ref())
123 }
124
125 pub fn shape_matching_enabled(&self) -> bool {
127 self.shape_matching
128 }
129
130 pub fn set_shape_matching_target(&mut self, target: Option<Pose>) {
132 self.shape_matching_target = target;
133 }
134}
135
136#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
139pub struct SoftClusterRemoval {
140 pub particles: usize,
142 pub edges: usize,
144 pub cells: usize,
146 pub dihedrals: usize,
148 pub surface_elements: usize,
150 pub attachments: usize,
152 pub soft_body_removed: bool,
154}
155
156impl SoftBody {
157 pub fn clusters(&self) -> &[SoftBodyCluster] {
161 &self.clusters
162 }
163
164 pub fn cluster(&self, i: u32) -> Option<&SoftBodyCluster> {
166 self.clusters.get(i as usize).filter(|c| c.is_live())
167 }
168
169 pub fn cluster_mut(&mut self, i: u32) -> Option<&mut SoftBodyCluster> {
171 let result = self.clusters.get_mut(i as usize).filter(|c| c.is_live());
172 self.modified |= result.is_some();
173 result
174 }
175
176 pub fn cluster_proxy(&self, i: u32) -> Option<RigidBodyHandle> {
178 self.cluster(i).map(|c| c.proxy)
179 }
180
181 pub fn live_clusters(&self) -> impl Iterator<Item = (u32, &SoftBodyCluster)> {
183 self.clusters
184 .iter()
185 .enumerate()
186 .filter(|(_, c)| c.is_live())
187 .map(|(i, c)| (i as u32, c))
188 }
189
190 pub fn num_live_clusters(&self) -> usize {
192 self.clusters.iter().filter(|c| c.is_live()).count()
193 }
194
195 pub(crate) fn clusters_are_disjoint(&self, a: u32, b: u32) -> bool {
198 let (Some(a), Some(b)) = (self.cluster(a), self.cluster(b)) else {
199 return true;
200 };
201 let (mut i, mut j) = (0, 0);
202 let (a, b) = (a.particles(), b.particles());
203 while i < a.len() && j < b.len() {
204 match a[i].cmp(&b[j]) {
205 core::cmp::Ordering::Less => i += 1,
206 core::cmp::Ordering::Greater => j += 1,
207 core::cmp::Ordering::Equal => return false,
208 }
209 }
210 true
211 }
212
213 pub fn enable_cluster_shape_matching(&mut self, i: u32, enabled: bool) {
217 if let Some(c) = self.clusters.get_mut(i as usize) {
218 if c.is_live() && c.shape_matching != enabled {
219 c.shape_matching = enabled;
220 self.modified = true;
221 }
222 }
223 }
224
225 pub fn set_cluster_stiffness_scale(&mut self, i: u32, scale: Real) {
229 let Some(cluster) = self.clusters.get(i as usize).filter(|c| c.is_live()) else {
230 return;
231 };
232 let contained: Vec<bool> = self
233 .cells
234 .iter()
235 .map(|c| {
236 c.vertices
237 .iter()
238 .all(|v| cluster.particles.binary_search(v).is_ok())
239 })
240 .collect();
241 for (c, inside) in self.cells.iter_mut().zip(contained) {
242 if inside {
243 c.stiffness_scale = scale.max(0.0);
244 }
245 }
246 self.modified = true;
247 }
248
249 pub fn set_cluster_edge_softness(
252 &mut self,
253 i: u32,
254 softness: Option<crate::dynamics::SpringCoefficients<Real>>,
255 ) {
256 let Some(cluster) = self.clusters.get(i as usize).filter(|c| c.is_live()) else {
257 return;
258 };
259 let contained: Vec<bool> = self
260 .edges
261 .iter()
262 .map(|e| {
263 e.vertices
264 .iter()
265 .all(|v| cluster.particles.binary_search(v).is_ok())
266 })
267 .collect();
268 for (e, inside) in self.edges.iter_mut().zip(contained) {
269 if inside {
270 e.softness = softness;
271 }
272 }
273 self.modified = true;
274 }
275
276 pub fn set_cluster_tear_resistance(&mut self, i: u32, resistance: Real) {
280 let Some(cluster) = self.clusters.get(i as usize).filter(|c| c.is_live()) else {
281 return;
282 };
283 let resistance = resistance.max(0.0);
284 let inside = |vertices: &[u32]| {
285 vertices
286 .iter()
287 .all(|v| cluster.particles.binary_search(v).is_ok())
288 };
289 let edges: Vec<bool> = self.edges.iter().map(|e| inside(&e.vertices)).collect();
290 let cells: Vec<bool> = self.cells.iter().map(|c| inside(&c.vertices)).collect();
291 for (e, inside) in self.edges.iter_mut().zip(edges) {
292 if inside {
293 e.tear_resistance = resistance;
294 }
295 }
296 for (c, inside) in self.cells.iter_mut().zip(cells) {
297 if inside {
298 c.tear_resistance = resistance;
299 }
300 }
301 self.modified = true;
302 }
303
304 pub fn set_cluster_pinned(&mut self, i: u32, pinned: bool) {
308 let Some(cluster) = self.clusters.get(i as usize).filter(|c| c.is_live()) else {
309 return;
310 };
311 for &v in cluster.particles.clone().iter() {
312 self.set_particle_pinned(v as usize, pinned);
313 }
314 }
315
316 pub fn set_cluster_kinematic_target(&mut self, i: u32, pose: Pose) {
320 let Some(cluster) = self.clusters.get(i as usize).filter(|c| c.is_live()) else {
321 return;
322 };
323 let mut nominal = 0.0;
325 let mut rest_com = Vector::ZERO;
326 for &v in &cluster.particles {
327 let p = &self.particles[v as usize];
328 nominal += p.mass;
329 rest_com += p.rest_position * p.mass;
330 }
331 if nominal > 0.0 {
332 rest_com /= nominal;
333 }
334 for &v in cluster.particles.clone().iter() {
335 let offset = self.particles[v as usize].rest_position - rest_com;
336 self.set_particle_kinematic_target(v as usize, pose * offset);
337 }
338 }
339
340 pub(crate) fn rebuild_cluster_refs(&mut self) {
342 self.cluster_refs.clear();
343 self.cluster_refs.resize(self.particles.len(), 0);
344 for c in &self.clusters {
345 if c.is_live() {
346 for &v in &c.particles {
347 if let Some(r) = self.cluster_refs.get_mut(v as usize) {
348 *r += 1;
349 }
350 }
351 }
352 }
353 }
354
355 pub(crate) fn matching_cell(&self, particles: &[u32]) -> u32 {
358 if particles.len() != DIM + 1 {
359 return u32::MAX;
360 }
361 for (i, cell) in self.cells.iter().enumerate() {
362 let mut vs = cell.vertices;
363 vs.sort_unstable();
364 if vs == particles[..DIM + 1] {
365 return i as u32;
366 }
367 }
368 u32::MAX
369 }
370
371 pub(crate) fn inherit_cluster_membership(&mut self, duplicated: &[(u32, u32)]) {
374 self.cluster_refs.resize(self.particles.len(), 0);
375 for &(copy, source) in duplicated {
376 for c in &mut self.clusters {
377 if c.is_live() && c.particles.binary_search(&source).is_ok() {
378 if let Err(pos) = c.particles.binary_search(©) {
379 c.particles.insert(pos, copy);
380 if !c.shape_impulses.is_empty() {
381 c.shape_impulses
383 .insert(pos.min(c.shape_impulses.len()), Vector::ZERO);
384 }
385 if let Some(r) = self.cluster_refs.get_mut(copy as usize) {
386 *r += 1;
387 }
388 }
389 }
390 }
391 }
392 self.update_cluster_cells();
394 }
395
396 pub(crate) fn update_cluster_cells(&mut self) {
398 let mut matches = Vec::new();
399 for c in &self.clusters {
400 matches.push(if c.is_live() {
401 self.matching_cell(&c.particles)
402 } else {
403 u32::MAX
404 });
405 }
406 for (c, cell) in self.clusters.iter_mut().zip(matches) {
407 c.cell = cell;
408 }
409 }
410
411 pub(crate) fn remove_dead_particles(
415 &mut self,
416 dead: &[bool],
417 ) -> (SoftClusterRemoval, Vec<u32>) {
418 let mut counts = SoftClusterRemoval::default();
419 if !dead.contains(&true) {
420 return (counts, Vec::new());
421 }
422 debug_assert_eq!(dead.len(), self.particles.len());
423
424 let mut remap = vec![u32::MAX; self.particles.len()];
426 let mut next = 0u32;
427 for (i, &d) in dead.iter().enumerate() {
428 if !d {
429 remap[i] = next;
430 next += 1;
431 }
432 }
433 counts.particles = self.particles.len() - next as usize;
434
435 let alive = |vs: &[u32]| vs.iter().all(|&v| !dead[v as usize]);
436
437 let before = self.edges.len();
439 self.edges.retain(|e| alive(&e.vertices));
440 counts.edges = before - self.edges.len();
441 for e in &mut self.edges {
442 for v in &mut e.vertices {
443 *v = remap[*v as usize];
444 }
445 }
446
447 let mut cell_remap = vec![u32::MAX; self.cells.len()];
449 let mut next_cell = 0u32;
450 for (i, c) in self.cells.iter().enumerate() {
451 if alive(&c.vertices) {
452 cell_remap[i] = next_cell;
453 next_cell += 1;
454 }
455 }
456 let before = self.cells.len();
457 self.cells.retain(|c| alive(&c.vertices));
458 counts.cells = before - self.cells.len();
459 for c in &mut self.cells {
460 for v in &mut c.vertices {
461 *v = remap[*v as usize];
462 }
463 }
464
465 #[cfg(feature = "dim3")]
466 {
467 let before = self.dihedrals.len();
468 self.dihedrals.retain(|d| alive(&d.vertices));
469 counts.dihedrals = before - self.dihedrals.len();
470 for d in &mut self.dihedrals {
471 for v in &mut d.vertices {
472 *v = remap[*v as usize];
473 }
474 }
475 }
476
477 let before = self.boundary.len();
478 self.boundary.retain(|s| alive(s));
479 counts.surface_elements = before - self.boundary.len();
480 for s in &mut self.boundary {
481 for v in s.iter_mut() {
482 *v = remap[*v as usize];
483 }
484 }
485
486 let before = self.attachments.len();
488 self.attachments.retain(|a| !dead[a.particle as usize]);
489 counts.attachments = before - self.attachments.len();
490 for a in &mut self.attachments {
491 a.particle = remap[a.particle as usize];
492 }
493 if counts.attachments > 0 {
494 self.attachments_modified = true;
495 }
496 self.attachments_modified |= counts.particles > 0;
497
498 for c in &mut self.clusters {
501 if c.is_live() {
502 if c.shape_impulses.len() == c.particles.len() {
503 let mut keep = c.particles.iter().map(|&v| !dead[v as usize]);
504 c.shape_impulses.retain(|_| keep.next().unwrap());
505 } else {
506 c.shape_impulses.clear();
507 }
508 c.particles.retain(|&v| !dead[v as usize]);
509 for v in &mut c.particles {
510 *v = remap[*v as usize];
511 }
512 }
513 }
514
515 let mut family_root = vec![u32::MAX; self.particles.len()];
517 for (i, p) in self.particles.iter_mut().enumerate() {
518 if dead[i] {
519 continue;
520 }
521 let old = p.split_root(i as u32) as usize;
522 if let Some(root) = family_root.get_mut(old) {
523 if *root == u32::MAX {
524 *root = remap[i];
525 }
526 p.split_root = *root;
527 } else {
528 p.split_root = remap[i];
529 }
530 }
531
532 let mut keep = dead.iter().map(|d| !d);
534 self.particles.retain(|_| keep.next().unwrap());
535 self.rebuild_cluster_refs();
536
537 let remap_tables = super::collision_mesh::SoftTopologyRemap {
540 cells: &cell_remap,
541 particles: &remap,
542 split: &[],
543 inserted: &[],
544 };
545 self.for_each_mesh_mut(|body, mesh| {
546 mesh.remap_topology(body, &remap_tables);
547 mesh.clear_contacts();
548 });
549
550 self.rebuild_boundary_tables();
552 self.rebuild_volume_pieces(&remap);
553 self.update_cluster_cells();
554 self.recolor();
555 self.modified = true;
556 self.topology_version = self.topology_version.wrapping_add(1);
557 (counts, remap)
558 }
559
560 pub(crate) fn cluster_com_and_mass(&self, cluster: &SoftBodyCluster) -> (Vector, Real) {
562 let mut com = Vector::ZERO;
563 let mut mass = 0.0;
564 for &v in &cluster.particles {
565 let p = &self.particles[v as usize];
566 com += p.position * p.mass;
567 mass += p.mass;
568 }
569 if mass > 0.0 {
570 com /= mass;
571 }
572 (com, mass)
573 }
574
575 pub(crate) fn cluster_frame(&self, ci: usize) -> Option<ClusterFrame> {
579 let cluster = self.clusters.get(ci)?;
580 if !cluster.is_live() {
581 return None;
582 }
583
584 let mut mass = 0.0;
586 let mut com = Vector::ZERO;
587 for &v in &cluster.particles {
588 let p = &self.particles[v as usize];
589 if p.inv_mass > 0.0 {
590 mass += p.mass;
591 com += p.position * p.mass;
592 }
593 }
594 let inv_mass = crate::utils::inv(mass);
595 let origin = if mass > 0.0 {
596 com * inv_mass
597 } else {
598 let (com_all, _) = self.cluster_com_and_mass(cluster);
600 com_all
601 };
602
603 let mut linvel = Vector::ZERO;
604 let mut angmom = AngVector::default();
605 #[cfg(feature = "dim2")]
606 let mut inertia: AngularInertia = 0.0;
607 #[cfg(feature = "dim3")]
608 let mut inertia = parry::utils::SdpMatrix3::zero();
609 for &v in &cluster.particles {
610 let p = &self.particles[v as usize];
611 if p.inv_mass > 0.0 {
612 let r = p.position - origin;
613 linvel += p.velocity * p.mass;
614 angmom += r.gcross(p.velocity * p.mass);
615 #[cfg(feature = "dim2")]
616 {
617 inertia += p.mass * r.length_squared();
618 }
619 #[cfg(feature = "dim3")]
620 {
621 let d = p.mass * r.length_squared();
622 inertia = parry::utils::SdpMatrix3 {
623 m11: inertia.m11 + d - p.mass * r.x * r.x,
624 m12: inertia.m12 - p.mass * r.x * r.y,
625 m13: inertia.m13 - p.mass * r.x * r.z,
626 m22: inertia.m22 + d - p.mass * r.y * r.y,
627 m23: inertia.m23 - p.mass * r.y * r.z,
628 m33: inertia.m33 + d - p.mass * r.z * r.z,
629 };
630 }
631 }
632 }
633 let inv_inertia = pseudo_inverse_inertia(inertia, inertia_noise_floor(mass, origin));
637 let linvel = linvel * inv_mass;
638 let angvel = inv_inertia.transform_vector(angmom);
639
640 let rotation = if let Some(cell) = self.cells.get(cluster.cell as usize) {
644 cell.rotation
645 } else {
646 let mut rest_com = Vector::ZERO;
647 let mut nominal_mass = 0.0;
648 for &v in &cluster.particles {
649 let p = &self.particles[v as usize];
650 rest_com += p.rest_position * p.mass;
651 nominal_mass += p.mass;
652 }
653 if nominal_mass > 0.0 {
654 rest_com /= nominal_mass;
655 }
656 let mut fit_com = Vector::ZERO;
657 for &v in &cluster.particles {
658 let p = &self.particles[v as usize];
659 fit_com += p.position * p.mass;
660 }
661 if nominal_mass > 0.0 {
662 fit_com /= nominal_mass;
663 }
664 let mut a = crate::math::Matrix::ZERO;
665 for &v in &cluster.particles {
666 let p = &self.particles[v as usize];
667 let x = (p.position - fit_com) * p.mass;
668 let q = p.rest_position - rest_com;
669 #[cfg(feature = "dim2")]
670 {
671 a += crate::math::Matrix::from_cols(x * q.x, x * q.y);
672 }
673 #[cfg(feature = "dim3")]
674 {
675 a += crate::math::Matrix::from_cols(x * q.x, x * q.y, x * q.z);
676 }
677 }
678 extract_rotation(a, cluster.rotation)
679 };
680
681 Some(ClusterFrame {
682 pose: Pose::from_parts(origin, rotation),
683 inv_mass,
684 inv_inertia,
685 linvel,
686 angvel,
687 })
688 }
689}
690
691impl SoftBody {
692 pub(crate) fn gather_cluster_velocity(
696 &self,
697 ci: usize,
698 inv_inertia: &AngularInertia,
699 pos: impl Fn(u32) -> Vector,
700 vel: impl Fn(u32) -> Vector,
701 ) -> (Vector, Vector, AngVector) {
702 let cluster = &self.clusters[ci];
703 let mut mass = 0.0;
704 let mut com = Vector::ZERO;
705 for &v in &cluster.particles {
706 let p = &self.particles[v as usize];
707 if p.inv_mass > 0.0 {
708 mass += p.mass;
709 com += pos(v) * p.mass;
710 }
711 }
712 if mass > 0.0 {
713 com /= mass;
714 }
715 let inv_mass = crate::utils::inv(mass);
716 let mut linvel = Vector::ZERO;
717 let mut angmom = AngVector::default();
718 for &v in &cluster.particles {
719 let p = &self.particles[v as usize];
720 if p.inv_mass > 0.0 {
721 let r = pos(v) - com;
722 linvel += vel(v) * p.mass;
723 angmom += r.gcross(vel(v) * p.mass);
724 }
725 }
726 (com, linvel * inv_mass, inv_inertia.transform_vector(angmom))
727 }
728}
729
730#[derive(Copy, Clone, Debug)]
732pub(crate) struct ClusterFrame {
733 pub pose: Pose,
736 pub inv_mass: Real,
738 pub inv_inertia: AngularInertia,
741 pub linvel: Vector,
743 pub angvel: AngVector,
745}
746
747pub(crate) fn inertia_noise_floor(mass: Real, com: Vector) -> Real {
751 let noise_extent = Real::EPSILON.sqrt() * (1.0 + com.length());
752 mass * noise_extent * noise_extent
753}
754
755#[cfg(feature = "dim2")]
759pub(crate) fn pseudo_inverse_inertia(inertia: AngularInertia, abs_floor: Real) -> AngularInertia {
760 if inertia <= abs_floor {
761 0.0
762 } else {
763 crate::utils::inv(inertia)
764 }
765}
766
767#[cfg(feature = "dim3")]
769pub(crate) fn pseudo_inverse_inertia(inertia: AngularInertia, abs_floor: Real) -> AngularInertia {
770 const RELATIVE_EPS: Real = 1.0e-5;
771 let m = na::Matrix3::new(
772 inertia.m11,
773 inertia.m12,
774 inertia.m13,
775 inertia.m12,
776 inertia.m22,
777 inertia.m23,
778 inertia.m13,
779 inertia.m23,
780 inertia.m33,
781 );
782 let eig = m.symmetric_eigen();
783 let max = eig.eigenvalues.amax();
784 if max <= abs_floor {
785 return parry::utils::SdpMatrix3::zero();
786 }
787 let mut inv = na::Matrix3::zeros();
788 for k in 0..3 {
789 let lambda = eig.eigenvalues[k];
790 if lambda > (max * RELATIVE_EPS).max(abs_floor) {
791 let u = eig.eigenvectors.column(k);
792 inv += u * u.transpose() / lambda;
793 }
794 }
795 parry::utils::SdpMatrix3 {
796 m11: inv[(0, 0)],
797 m12: inv[(0, 1)],
798 m13: inv[(0, 2)],
799 m22: inv[(1, 1)],
800 m23: inv[(1, 2)],
801 m33: inv[(2, 2)],
802 }
803}