Skip to main content

rapier3d/dynamics/soft_body/soft_body_set/
soft_body_set_step_sync.rs

1//! Per-step maintenance of the set: applying user changes, updating proxies and colliders, syncing particle positions, tearing and cutting.
2use super::soft_body_set_proxies::{rebuilt_surface_shape, sync_soft_body};
3use super::soft_body_set_split::{ProxyPoses, rebase_proxy_attachments};
4use super::{SoftBodyIslandEvent, SoftBodySet};
5use crate::alloc_prelude::*;
6use crate::dynamics::soft_body::{SoftBody, SoftBodyHandle, SoftBodyTearEvent};
7use crate::dynamics::{
8    ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, RigidBodySet,
9};
10use crate::geometry::{ColliderHandle, ColliderPosition, ColliderSet};
11use crate::math::{DIM, Pose, Real, Vector};
12use crate::pipeline::EventHandler;
13#[cfg(not(feature = "std"))]
14#[allow(unused_imports)]
15use simba::scalar::{ComplexField as _, RealField as _};
16
17impl SoftBodySet {
18    /// Applies the user's changes since the last step: wakes the soft bodies whose settings
19    /// changed, enables or disables their root body, moves the colliders of moved particles, and
20    /// updates attachment index and island links. A non-finite particle quarantines its body.
21    pub(crate) fn apply_user_changes(
22        &mut self,
23        bodies: &mut RigidBodySet,
24        colliders: &mut ColliderSet,
25        params: &IntegrationParameters,
26        quarantined: &mut Vec<SoftBodyHandle>,
27    ) {
28        // The colliders' shapes say which surfaces are oriented (the user may have changed them).
29        for (_, sb) in self.bodies.iter_mut() {
30            for mesh in sb.meshes_mut() {
31                mesh.read_orientation(colliders);
32            }
33        }
34        let mut relink = false;
35        let handles: Vec<SoftBodyHandle> = self
36            .bodies
37            .iter()
38            .filter(|(_, sb)| sb.modified || sb.positions_modified || sb.attachments_modified)
39            .map(|(h, _)| SoftBodyHandle(h))
40            .collect();
41        for handle in handles {
42            let sb = &mut self.bodies[handle.0];
43            if core::mem::take(&mut sb.modified) {
44                if sb.enabled && !sb.is_finite() {
45                    sb.sanitize_dynamics();
46                    sb.enabled = false;
47                    quarantined.push(handle);
48                }
49                if let Some(rb) = bodies.get_mut(sb.root_body) {
50                    if rb.is_enabled() != sb.enabled {
51                        rb.set_enabled(sb.enabled);
52                    }
53                    if sb.enabled {
54                        rb.wake_up(true);
55                    }
56                }
57            }
58            if core::mem::take(&mut sb.positions_modified) && sb.enabled {
59                Self::update_colliders(sb, bodies, colliders, false);
60                for mesh in sb.meshes_mut() {
61                    // A teleport can author a tangle with no accumulated travel: the next
62                    // self-crossing sweep must run.
63                    mesh.crossing_sweep_travel = Real::MAX;
64                }
65            }
66            // Velocities authored between steps outrun the speculative margin computed at the
67            // end of the last step, so the margin is raised here to cover the coming step's reach.
68            if sb.enabled && params.soft_bodies.recovery.authored_velocity_margin {
69                let mut max_speed: Real = 0.0;
70                for p in &sb.particles {
71                    max_speed = max_speed.max(p.velocity.length());
72                }
73                let margin = max_speed * params.dt;
74                if margin > 0.0 {
75                    for cluster in sb.clusters.iter().filter(|cluster| cluster.is_live()) {
76                        if let Some(rb) = bodies.get_mut_internal(cluster.proxy()) {
77                            rb.soft_motion_margin = rb.soft_motion_margin.max(margin);
78                        }
79                        // The margin only pads the deformable colliders: flagged as modified so
80                        // the broad phase updates their AABBs with it.
81                        for mesh in cluster.meshes() {
82                            let _ = colliders.get_mut(mesh.collider());
83                        }
84                    }
85                }
86            }
87            if core::mem::take(&mut sb.attachments_modified) {
88                relink = true;
89                self.island_events.push(SoftBodyIslandEvent { handle });
90            }
91        }
92        if relink || self.attached_to_stale {
93            self.attached_to_stale = false;
94            self.attached_to.clear();
95            for (h, sb) in self.bodies.iter() {
96                for (i, a) in sb.attachments.iter().enumerate() {
97                    self.attached_to
98                        .entry(a.body)
99                        .or_default()
100                        .push((SoftBodyHandle(h), i as u32));
101                }
102            }
103        }
104    }
105
106    /// Updates every live cluster's proxy rigid body from the particles: pose from the cluster's
107    /// frame, velocity from its rigid fit, mass properties from its reduced mass matrix. The
108    /// proxies' colliders keep their world pose (local pose re-based) until the frame-local phase.
109    pub(crate) fn update_cluster_proxies(
110        sb: &mut SoftBody,
111        bodies: &mut RigidBodySet,
112        colliders: &mut ColliderSet,
113    ) {
114        let _ = colliders;
115        for ci in 0..sb.clusters.len() {
116            let Some(mut frame) = sb.cluster_frame(ci) else {
117                continue;
118            };
119            if !frame.pose.is_finite() || !frame.linvel.is_finite() || !frame.angvel.is_finite() {
120                // A non-finite fit means non-finite particles: the quarantine handles the body;
121                // the proxy keeps its previous (finite) state meanwhile.
122                continue;
123            }
124            let proxy = sb.clusters[ci].proxy;
125            let Some(rb) = bodies.get_mut_internal(proxy) else {
126                continue;
127            };
128            // Dead zone: a pose change below the frame fit's rest noise keeps the previous pose
129            // bit-for-bit, so the frozen contact anchors are not re-linearized by fit noise.
130            let prev = rb.pos.position;
131            let dt = (frame.pose.translation - prev.translation).length();
132            // The 2D angle is signed: take its magnitude so either direction leaves the dead zone.
133            #[cfg(feature = "dim2")]
134            let dr = frame.pose.rotation.angle_between(&prev.rotation).abs();
135            #[cfg(feature = "dim3")]
136            let dr = frame.pose.rotation.angle_between(prev.rotation);
137            if dt < 1.0e-6 && dr < 1.0e-6 {
138                frame.pose = prev;
139            }
140            sb.clusters[ci].rotation = frame.pose.rotation;
141            sb.clusters[ci].last_gather = (frame.linvel, frame.angvel);
142            rb.pos.position = frame.pose;
143            rb.pos.next_position = frame.pose;
144            rb.vels.linvel = frame.linvel;
145            rb.vels.angvel = frame.angvel;
146            rb.mprops.local_mprops.inv_mass = frame.inv_mass;
147            rb.mprops.local_mprops.local_com = Vector::ZERO;
148            rb.mprops.world_com = frame.pose.translation;
149            rb.mprops.effective_inv_mass = Vector::splat(frame.inv_mass);
150            rb.mprops.effective_world_inv_inertia = frame.inv_inertia;
151        }
152    }
153
154    /// Moves the soft body's colliders to its current particle positions. `rebuild`: the surface
155    /// topology changed (a tear), so the surface shape is rebuilt instead of deformed in place.
156    pub(super) fn update_colliders(
157        sb: &SoftBody,
158        bodies: &RigidBodySet,
159        colliders: &mut ColliderSet,
160        rebuild: bool,
161    ) {
162        // The mesh geometry is expressed in its collider's frame (proxy pose composed with the
163        // collider's local pose); any shared frame works, so a proxy pose one update stale is fine.
164        for cluster in sb.clusters.iter().filter(|cluster| cluster.is_live()) {
165            let frame = bodies
166                .get(cluster.proxy())
167                .map(|rb| rb.pos.position)
168                .unwrap_or(Pose::IDENTITY);
169            for mesh in cluster.meshes() {
170                let Some(co) = colliders.get_mut(mesh.collider()) else {
171                    continue;
172                };
173                co.deform_pose(frame);
174                let pose = *co.position();
175                if rebuild {
176                    match rebuilt_surface_shape(co.shape(), mesh.local_vertices(sb, &pose), mesh) {
177                        Some(shape) => co.replace_deformed_shape(shape),
178                        None => co.set_enabled(false),
179                    }
180                } else {
181                    co.deform_shape(|shape| mesh.deform_shape(sb, &pose, shape));
182                }
183            }
184        }
185    }
186
187    /// Updates each soft body's derived state at the end of a step: surface orientation, sleep
188    /// state, colliders of the awake ones, the coming step's speculative margin and the
189    /// impact-adaptive substep request. A body with a non-finite particle is quarantined.
190    pub fn sync_particle_positions(
191        &mut self,
192        bodies: &mut RigidBodySet,
193        colliders: &mut ColliderSet,
194        params: &IntegrationParameters,
195        quarantined: &mut Vec<SoftBodyHandle>,
196    ) {
197        self.sync_particle_positions_and_collect(
198            bodies,
199            colliders,
200            params,
201            params.dt,
202            quarantined,
203            &mut Vec::new(),
204        );
205    }
206
207    /// [`Self::sync_particle_positions`], also collecting into `synced_colliders` the deformed
208    /// surface colliders and the rigid colliders moved to their proxy's fresh pose, whose
209    /// broad-phase AABBs must follow. `params` are the full step's (sizing the coming step's
210    /// margin and substeps); `last_pass_dt` is the length of the step's last CCD pass.
211    pub(crate) fn sync_particle_positions_and_collect(
212        &mut self,
213        bodies: &mut RigidBodySet,
214        colliders: &mut ColliderSet,
215        params: &IntegrationParameters,
216        last_pass_dt: Real,
217        quarantined: &mut Vec<SoftBodyHandle>,
218        synced_colliders: &mut Vec<ColliderHandle>,
219    ) {
220        // Every body reads its own particles and writes only itself: updated in parallel, then
221        // the writes to the shared sets are applied in body order. Disabled bodies are left
222        // alone, like sleeping ones.
223        let mut handles = Vec::with_capacity(self.bodies.len());
224        let mut soft_bodies: Vec<(&mut SoftBody, bool)> = self
225            .bodies
226            .iter_mut()
227            .map(|(h, sb)| {
228                handles.push(SoftBodyHandle(h));
229                let inactive = bodies
230                    .get(sb.root_body)
231                    .is_none_or(|rb| rb.is_sleeping() || !rb.is_enabled());
232                (sb, inactive)
233            })
234            .collect();
235        let mut outcomes = Vec::with_capacity(soft_bodies.len());
236        let sync = |(sb, inactive): &mut (&mut SoftBody, bool)| {
237            sync_soft_body(sb, *inactive, params, last_pass_dt)
238        };
239        #[cfg(feature = "parallel")]
240        {
241            use rayon::prelude::*;
242            soft_bodies
243                .par_iter_mut()
244                .map(sync)
245                .collect_into_vec(&mut outcomes);
246        }
247        #[cfg(not(feature = "parallel"))]
248        outcomes.extend(soft_bodies.iter_mut().map(sync));
249
250        for (((sb, _), outcome), handle) in soft_bodies.iter_mut().zip(outcomes).zip(handles) {
251            let Some(margin) = outcome.margin else {
252                continue;
253            };
254            if !outcome.finite {
255                // Non-finite state: contained here, before it reaches the colliders.
256                sb.sanitize_dynamics();
257                sb.enabled = false;
258                if let Some(rb) = bodies.get_mut(sb.root_body) {
259                    rb.set_enabled(false);
260                }
261                quarantined.push(handle);
262                continue;
263            }
264            let Some(rb) = bodies.get_mut_internal(sb.root_body) else {
265                continue;
266            };
267            rb.additional_solver_iterations = outcome.additional_solver_iterations;
268            rb.additional_pgs_iterations = sb.particle_settings.additional_pgs_iterations;
269            // The cluster proxies first (pose, velocity, reduced mass): the colliders are
270            // expressed in the fresh whole-body frame.
271            Self::update_cluster_proxies(sb, bodies, colliders);
272            for cluster in sb.clusters.iter().filter(|cluster| cluster.is_live()) {
273                let frame = bodies
274                    .get(cluster.proxy())
275                    .map(|rb| rb.pos.position)
276                    .unwrap_or(Pose::IDENTITY);
277                for mesh in cluster.meshes() {
278                    if let Some(co) = colliders.get_mut(mesh.collider()) {
279                        // Deformed in place, in the collider's frame: parry refits the BVH.
280                        co.deform_pose(frame);
281                        let pose = *co.position();
282                        co.deform_shape(|shape| mesh.deform_shape(sb, &pose, shape));
283                        synced_colliders.push(mesh.collider());
284                    }
285                }
286                // The speculative margin pads the proxy's deformable colliders only: the rigid
287                // colliders a user hung on it get the AABB of any dynamic body's collider.
288                let Some(rb) = bodies.get_mut_internal(cluster.proxy()) else {
289                    continue;
290                };
291                rb.soft_motion_margin = margin;
292                let proxy_pose = rb.pos.position;
293                // Flagged as modified: the meshes were deformed and the rigid ones move below.
294                for handle in rb.colliders() {
295                    let Some(co) = colliders.get_mut(*handle) else {
296                        continue;
297                    };
298                    // The rigid ones follow its fresh pose: the end-of-step advance leaves the
299                    // proxies' colliders to this sync.
300                    if !co.is_deformable_collider() {
301                        if let Some(parent) = co.parent.as_ref() {
302                            co.pos = ColliderPosition(proxy_pose * parent.pos_wrt_parent);
303                            synced_colliders.push(*handle);
304                        }
305                    }
306                }
307            }
308        }
309    }
310
311    /// Applies the tear marks left by the solver (elements loaded past their tear threshold) and
312    /// by `SoftBody::tear_edge`/`tear_cell`, and reports each tear to `events`. Runs at the end
313    /// of a step, before [`Self::sync_particle_positions`].
314    pub(crate) fn apply_pending_tears(
315        &mut self,
316        islands: &mut IslandManager,
317        bodies: &mut RigidBodySet,
318        colliders: &mut ColliderSet,
319        impulse_joints: &mut ImpulseJointSet,
320        multibody_joints: &mut MultibodyJointSet,
321        events: &dyn EventHandler,
322    ) {
323        let pending: Vec<SoftBodyHandle> = self
324            .bodies
325            .iter()
326            .filter(|(_, sb)| sb.tearing_pending)
327            .map(|(h, _)| SoftBodyHandle(h))
328            .collect();
329        let mut torn = Vec::new();
330        for handle in pending {
331            let (edges, cells) = self.bodies[handle.0].take_torn();
332            if let Some(event) = self.tear(
333                handle,
334                &edges,
335                &cells,
336                islands,
337                bodies,
338                colliders,
339                impulse_joints,
340                multibody_joints,
341            ) {
342                torn.push(event);
343            }
344        }
345        for event in &torn {
346            events.handle_soft_body_tear_event(self, event);
347        }
348    }
349
350    /// Tears a soft body right away and rebuilds its colliders, losing no material: a crack opens
351    /// through a particle of every torn edge and cell (see [`SoftBody::tear_edge`]), disconnected
352    /// pieces become soft bodies, crossed clusters split. Returns the event, `None` if unchanged.
353    #[allow(clippy::too_many_arguments)]
354    pub fn tear(
355        &mut self,
356        handle: SoftBodyHandle,
357        edges: &[u32],
358        cells: &[u32],
359        islands: &mut IslandManager,
360        bodies: &mut RigidBodySet,
361        colliders: &mut ColliderSet,
362        impulse_joints: &mut ImpulseJointSet,
363        multibody_joints: &mut MultibodyJointSet,
364    ) -> Option<SoftBodyTearEvent> {
365        let _ = multibody_joints;
366        self.change_topology(
367            handle,
368            islands,
369            bodies,
370            colliders,
371            impulse_joints,
372            |sb, event| sb.tear_topology(edges, cells, event),
373        )
374    }
375
376    /// Applies a topology change to a soft body (`change` returns whether anything changed and
377    /// records it in the event), then splits the clusters the change disconnected, rebuilds the
378    /// colliders and wakes the body up.
379    fn change_topology(
380        &mut self,
381        handle: SoftBodyHandle,
382        islands: &mut IslandManager,
383        bodies: &mut RigidBodySet,
384        colliders: &mut ColliderSet,
385        impulse_joints: &mut ImpulseJointSet,
386        change: impl FnOnce(&mut SoftBody, &mut SoftBodyTearEvent) -> bool,
387    ) -> Option<SoftBodyTearEvent> {
388        let sb = self.bodies.get_mut(handle.0)?;
389        let mut event = SoftBodyTearEvent {
390            soft_body: handle,
391            ..Default::default()
392        };
393        if !change(sb, &mut event) {
394            return None;
395        }
396
397        // The clusters the crack ran through come apart with their material, then so does the
398        // body: every piece the crack disconnected becomes a soft body of its own.
399        let seeds = event.seeds();
400        let mut poses = ProxyPoses::default();
401        let split = self.split_clusters_along(
402            handle,
403            &seeds,
404            islands,
405            bodies,
406            colliders,
407            impulse_joints,
408            &mut event,
409            &mut poses,
410        );
411        let mut handles = self.split_body_along(
412            handle,
413            &seeds,
414            islands,
415            bodies,
416            colliders,
417            impulse_joints,
418            &mut event,
419            &mut poses,
420        );
421        let split = split || !handles.is_empty();
422        handles.insert(0, handle);
423
424        for &piece in &handles {
425            let sb = &mut self.bodies[piece.0];
426            if split {
427                // The pieces' frames, fresh and retained: the colliders are expressed in them.
428                Self::update_cluster_proxies(sb, bodies, colliders);
429            }
430            // The colliders follow the new surface at once: the next step's narrow phase must
431            // not report elements that no longer exist.
432            Self::update_colliders(sb, bodies, colliders, true);
433        }
434        if split {
435            rebase_proxy_attachments(&poses, bodies, colliders, impulse_joints);
436        }
437        for &piece in &handles {
438            if let Some(rb) = bodies.get_mut(self.bodies[piece.0].root_body) {
439                rb.wake_up(true);
440            }
441        }
442        Some(event)
443    }
444
445    /// Cuts a soft body right away along a blade, a world segment (2D) or triangle (3D), splitting
446    /// the particles of the edges the blade meets (pinned ones never) and losing no material (see
447    /// [`SoftBody::rest_measure`]). Returns the tear event, or `None` when nothing changed.
448    #[allow(clippy::too_many_arguments)]
449    pub fn cut(
450        &mut self,
451        handle: SoftBodyHandle,
452        blade: &[Vector; DIM],
453        islands: &mut IslandManager,
454        bodies: &mut RigidBodySet,
455        colliders: &mut ColliderSet,
456        impulse_joints: &mut ImpulseJointSet,
457        multibody_joints: &mut MultibodyJointSet,
458    ) -> Option<SoftBodyTearEvent> {
459        let _ = multibody_joints;
460        self.change_topology(
461            handle,
462            islands,
463            bodies,
464            colliders,
465            impulse_joints,
466            |sb, event| sb.cut_topology(blade, event),
467        )
468    }
469
470    /// Refreshes every mesh's vertex cache from the particles, before a narrow-phase update
471    /// (see `SoftCollisionMesh::cached_vertex`); in parallel under the `parallel` feature.
472    pub(crate) fn refresh_vertex_caches(&mut self) {
473        #[cfg(feature = "parallel")]
474        {
475            use rayon::prelude::*;
476            let mut bodies: Vec<&mut SoftBody> = self.bodies.iter_mut().map(|(_, sb)| sb).collect();
477            bodies
478                .par_iter_mut()
479                .for_each(|sb| sb.refresh_vertex_caches());
480        }
481        #[cfg(not(feature = "parallel"))]
482        for (_, sb) in self.bodies.iter_mut() {
483            sb.refresh_vertex_caches();
484        }
485    }
486
487    /// Wakes up the given soft body (effective at the start of the next step).
488    pub fn wake_up(&mut self, handle: SoftBodyHandle, bodies: &mut RigidBodySet, strong: bool) {
489        if let Some(sb) = self.bodies.get_mut(handle.0) {
490            sb.wake_up();
491            if let Some(rb) = bodies.get_mut(sb.root_body) {
492                rb.wake_up(strong);
493            }
494        }
495    }
496}