Skip to main content

rapier3d/dynamics/soft_body/soft_body_set/
soft_body_set_clusters.rs

1//! The cluster paths of a `SoftBodySet`: adding, removing and dissolving clusters.
2use super::soft_body_set_proxies::spawn_proxy;
3use super::{SoftBodyIslandEvent, SoftBodySet};
4use crate::alloc_prelude::*;
5use crate::dynamics::soft_body::{SoftBodyCluster, SoftBodyHandle, SoftClusterRemoval};
6use crate::dynamics::{
7    ImpulseJointSet, IslandManager, MultibodyJointSet, RigidBodyHandle, RigidBodySet,
8};
9use crate::geometry::ColliderSet;
10use crate::math::Rotation;
11
12impl SoftBodySet {
13    /// Adds a cluster to a soft body: a set of its particles (invalid indices ignored, duplicates
14    /// merged) backed by a fresh [`crate::dynamics::RigidBodyType::SoftFrame`] proxy body that
15    /// joints and colliders attach to. Returns its stable index, `None` if nothing valid remains.
16    pub fn add_cluster(
17        &mut self,
18        handle: SoftBodyHandle,
19        particles: &[u32],
20        bodies: &mut RigidBodySet,
21        colliders: &mut ColliderSet,
22    ) -> Option<u32> {
23        let sb = self.bodies.get_mut(handle.0)?;
24        let n = sb.particles.len() as u32;
25        let mut list: Vec<u32> = particles.iter().copied().filter(|&v| v < n).collect();
26        list.sort_unstable();
27        list.dedup();
28        if list.is_empty() {
29            return None;
30        }
31        let index = sb.clusters.len() as u32;
32        let proxy = spawn_proxy(&sb.particle_settings, sb.user_data, handle, index, bodies);
33        for &v in &list {
34            sb.cluster_refs[v as usize] += 1;
35        }
36        let cell = sb.matching_cell(&list);
37        sb.clusters.push(SoftBodyCluster {
38            particles: list,
39            proxy,
40            meshes: Vec::new(),
41            rotation: Rotation::IDENTITY,
42            cell,
43            shape_matching: false,
44            shape_matching_target: None,
45            prev_shape_matching_target: None,
46            last_gather: Default::default(),
47            shape_impulses: Vec::new(),
48        });
49        sb.modified = true;
50        // Update right away: a joint attached before the next step must see the cluster's
51        // real frame and reduced mass.
52        Self::update_cluster_proxies(sb, bodies, colliders);
53        self.island_events.push(SoftBodyIslandEvent { handle });
54        Some(index)
55    }
56
57    /// Removes the `cluster`-th cluster of a soft body, its proxy rigid body, and the particles
58    /// only this cluster covered (with their elements and attachments); removing the last cluster
59    /// removes the body. Returns what was deleted, or `None` if the body or cluster is missing.
60    #[allow(clippy::too_many_arguments)]
61    pub fn remove_cluster(
62        &mut self,
63        handle: SoftBodyHandle,
64        cluster: u32,
65        islands: &mut IslandManager,
66        bodies: &mut RigidBodySet,
67        colliders: &mut ColliderSet,
68        impulse_joints: &mut ImpulseJointSet,
69        multibody_joints: &mut MultibodyJointSet,
70    ) -> Option<SoftClusterRemoval> {
71        let (counts, proxy) = self.dissolve_cluster(handle, cluster, islands, bodies, colliders)?;
72        // The proxy body itself, with its own colliders and joints (the dissolution above
73        // tombstoned the cluster, so the removal hook no-ops).
74        bodies.remove(
75            proxy,
76            islands,
77            colliders,
78            impulse_joints,
79            multibody_joints,
80            self,
81            true,
82        );
83        Some(counts)
84    }
85
86    /// The proxy-removal hook of [`RigidBodySet::remove`]: dissolves the cluster the removed
87    /// proxy stood for. The proxy body itself (and its colliders and joints) is removed by the
88    /// caller.
89    pub(crate) fn on_proxy_removed(
90        &mut self,
91        handle: SoftBodyHandle,
92        cluster: u32,
93        islands: &mut IslandManager,
94        bodies: &mut RigidBodySet,
95        colliders: &mut ColliderSet,
96    ) {
97        let _ = self.dissolve_cluster(handle, cluster, islands, bodies, colliders);
98    }
99
100    /// Dissolves a cluster: tombstones it, removes the particles it uniquely covered (with their
101    /// elements and attachments), rebuilds the surface shape, re-points the root body if needed,
102    /// and removes the body with its last cluster. Keeps the proxy body; returns counts and its
103    /// handle.
104    fn dissolve_cluster(
105        &mut self,
106        handle: SoftBodyHandle,
107        cluster: u32,
108        islands: &mut IslandManager,
109        bodies: &mut RigidBodySet,
110        colliders: &mut ColliderSet,
111    ) -> Option<(SoftClusterRemoval, RigidBodyHandle)> {
112        // First pass, on the soft body alone: tombstone the cluster and remove the particles
113        // only it covered (with their elements and attachments).
114        let (mut counts, proxy, root, last) = {
115            let sb = self.bodies.get_mut(handle.0)?;
116            let c = sb.clusters.get_mut(cluster as usize)?;
117            if !c.is_live() {
118                return None;
119            }
120            let (particles, proxy) = c.tombstone();
121
122            // Reference counts: the particles only this cluster covered die.
123            let mut dead = alloc::vec![false; sb.particles.len()];
124            for &v in &particles {
125                if let Some(r) = sb.cluster_refs.get_mut(v as usize) {
126                    *r = r.saturating_sub(1);
127                    if *r == 0 {
128                        dead[v as usize] = true;
129                    }
130                }
131            }
132            let (counts, _) = sb.remove_dead_particles(&dead);
133
134            // The root body follows the first live cluster when cluster 0 goes.
135            if sb.root_body == proxy {
136                let next_root = sb
137                    .live_clusters()
138                    .next()
139                    .map(|(_, c)| c.proxy)
140                    .unwrap_or(RigidBodyHandle::invalid());
141                sb.root_body = next_root;
142            }
143            let last = sb.num_live_clusters() == 0;
144            (counts, proxy, sb.root_body, last)
145        };
146        counts.soft_body_removed = last;
147
148        if last {
149            // The last cluster: the soft body goes with it (its proxy is removed by the caller).
150            let sb = self.bodies.remove(handle.0).unwrap();
151            islands.persistent.unlink_soft_body_attachments(handle);
152            islands.persistent.unlink_soft_body_proxy_chain(handle);
153            self.attached_to_stale |= !sb.attachments.is_empty();
154            return Some((counts, proxy));
155        }
156
157        if counts.particles > 0 {
158            // The surface shape lost the dead particles' vertices: rebuild it.
159            Self::update_colliders(&self.bodies[handle.0], bodies, colliders, true);
160        }
161        self.attached_to_stale |= counts.attachments > 0;
162        self.island_events.push(SoftBodyIslandEvent { handle });
163        if let Some(rb) = bodies.get_mut(root) {
164            rb.wake_up(true);
165        }
166        Some((counts, proxy))
167    }
168}