Skip to main content

rapier3d/dynamics/soft_body/
soft_body_accessors.rs

1//! Accessors and simple setters of a soft body: particles, elements, material, volume preservation, sleeping and enabling.
2#[cfg(feature = "dim3")]
3use super::SoftBodyDihedral;
4#[cfg(feature = "fem")]
5use super::SoftBodySolver;
6use super::{
7    SoftBody, SoftBodyCell, SoftBodyCellModel, SoftBodyEdge, SoftBodyMaterial, SoftBodyParticle,
8    SoftVolumePiece,
9};
10use crate::dynamics::RigidBodyHandle;
11use crate::math::{DIM, Real, Vector};
12
13impl SoftBody {
14    /// A counter bumped whenever the topology changes (a tear removed elements or duplicated
15    /// particles): a renderer keying its mesh on the particles and elements rebuilds it when
16    /// this value changes.
17    pub fn topology_version(&self) -> u32 {
18        self.topology_version
19    }
20
21    /// The particles of this soft body.
22    pub fn particles(&self) -> &[SoftBodyParticle] {
23        &self.particles
24    }
25
26    /// The number of particles of this soft body.
27    pub fn num_particles(&self) -> usize {
28        self.particles.len()
29    }
30
31    /// The world-space position of the `i`-th particle.
32    pub fn particle_position(&self, i: usize) -> Vector {
33        self.particles[i].position
34    }
35
36    /// The world-space positions of every particle.
37    pub fn particle_positions(&self) -> impl ExactSizeIterator<Item = Vector> + '_ {
38        self.particles.iter().map(|p| p.position)
39    }
40
41    /// The world-space velocity of the `i`-th particle.
42    pub fn particle_velocity(&self, i: usize) -> Vector {
43        self.particles[i].velocity
44    }
45
46    /// The world-space velocities of every particle.
47    pub fn particle_velocities(&self) -> impl ExactSizeIterator<Item = Vector> + '_ {
48        self.particles.iter().map(|p| p.velocity)
49    }
50
51    /// Teleports the `i`-th particle to `position` (no velocity change). For a pinned particle
52    /// moving along a path, prefer [`Self::set_particle_kinematic_target`], which gives it the
53    /// matching velocity so friction and contacts see the motion.
54    pub fn set_particle_position(&mut self, i: usize, position: Vector) {
55        self.particles[i].position = position;
56        self.particles[i].next_position = None;
57        self.positions_modified = true;
58        self.modified = true;
59    }
60
61    /// Sets the velocity of the `i`-th particle. A pinned particle keeps moving at that velocity
62    /// (a velocity-driven kinematic particle) until it is set again or given a kinematic target.
63    pub fn set_particle_velocity(&mut self, i: usize, velocity: Vector) {
64        self.particles[i].velocity = velocity;
65        self.particles[i].next_position = None;
66        self.modified = true;
67    }
68
69    /// Moves the pinned `i`-th particle to `position` over the next step (with the matching
70    /// velocity, so dragged cloth and friction see the motion), then holds it there until the next
71    /// target or velocity is set. Ignored for a free particle.
72    pub fn set_particle_kinematic_target(&mut self, i: usize, position: Vector) {
73        let particle = &mut self.particles[i];
74        if particle.inv_mass == 0.0 {
75            particle.next_position = Some(position);
76            // A target away from the particle is a motion: the body must be awake to follow it.
77            if position != particle.position {
78                self.modified = true;
79            }
80        }
81    }
82
83    /// The distance constraints of this soft body.
84    pub fn edges(&self) -> &[SoftBodyEdge] {
85        &self.edges
86    }
87
88    /// The dihedral bending constraints of this soft body.
89    #[cfg(feature = "dim3")]
90    pub fn dihedrals(&self) -> &[SoftBodyDihedral] {
91        &self.dihedrals
92    }
93
94    /// The simplex cells (triangles in 2D, tetrahedra in 3D) of this soft body.
95    pub fn cells(&self) -> &[SoftBodyCell] {
96        &self.cells
97    }
98
99    /// The boundary of this soft body's computational mesh: segments in 2D, triangles in 3D
100    /// (particle indices), oriented outward. This is what the volume constraint integrates over; the
101    /// geometry the body collides through is its [`crate::dynamics::SoftCollisionMesh`].
102    pub fn boundary(&self) -> &[[u32; DIM]] {
103        &self.boundary
104    }
105
106    /// The material (per-family stiffness and damping) of this soft body.
107    pub fn material(&self) -> &SoftBodyMaterial {
108        &self.material
109    }
110
111    /// Sets the material of this soft body.
112    pub fn set_material(&mut self, material: SoftBodyMaterial) {
113        self.material = material;
114        self.modified = true;
115    }
116
117    /// Mutable access to the material of this soft body.
118    pub fn material_mut(&mut self) -> &mut SoftBodyMaterial {
119        self.modified = true;
120        &mut self.material
121    }
122
123    /// The constitutive model of this soft body's cells.
124    pub fn cell_model(&self) -> SoftBodyCellModel {
125        self.cell_model
126    }
127
128    /// Which solver simulates this soft body's elasticity.
129    #[cfg(feature = "fem")]
130    pub fn solver(&self) -> SoftBodySolver {
131        self.solver
132    }
133
134    /// Selects the solver simulating this soft body's elasticity (see [`SoftBodySolver`]).
135    #[cfg(feature = "fem")]
136    pub fn set_solver(&mut self, solver: SoftBodySolver) {
137        self.solver = solver;
138        self.modified = true;
139    }
140
141    /// Whether this soft body's elasticity is solved by the FEM path.
142    #[inline]
143    pub(crate) fn uses_fem(&self) -> bool {
144        #[cfg(feature = "fem")]
145        {
146            self.solver == SoftBodySolver::Fem
147        }
148        #[cfg(not(feature = "fem"))]
149        {
150            false
151        }
152    }
153
154    /// Whether the area/volume preservation constraints are enabled (see [`Self::volume_pieces`]).
155    pub fn volume_preservation_enabled(&self) -> bool {
156        self.volume_preservation
157    }
158
159    /// Enables or disables area/volume preservation: one constraint per volume piece (see
160    /// [`Self::volume_pieces`]); it stays disabled for a body without any.
161    pub fn enable_volume_preservation(&mut self, enabled: bool) {
162        self.volume_preservation = enabled && !self.volume_pieces.is_empty();
163        self.modified = true;
164    }
165
166    /// The pieces of material enclosed by a closed boundary, each with its own volume constraint
167    /// so squeezing one does not inflate another. Pieces joined by no element (appended bodies,
168    /// torn-apart halves) are separate; the holes or cavities of a piece belong to it.
169    pub fn volume_pieces(&self) -> &[SoftVolumePiece] {
170        &self.volume_pieces
171    }
172
173    /// The signed area (2D) or volume (3D) enclosed by the volume pieces at rest (`0.0` for a
174    /// body without a closed boundary).
175    pub fn rest_volume(&self) -> Real {
176        self.volume_pieces
177            .iter()
178            .map(|piece| piece.rest_volume)
179            .sum()
180    }
181
182    /// The signed area (2D) or volume (3D) currently enclosed by the volume pieces.
183    pub fn volume(&self) -> Real {
184        self.volume_pieces
185            .iter()
186            .map(|piece| piece.volume(self))
187            .sum()
188    }
189
190    /// The multiplier applied to each volume piece's rest volume to obtain its
191    /// volume-preservation target (`> 1` inflates the body).
192    pub fn volume_factor(&self) -> Real {
193        self.volume_factor
194    }
195
196    /// Sets the multiplier applied to each volume piece's rest volume to obtain its
197    /// volume-preservation target (`> 1` inflates the body).
198    pub fn set_volume_factor(&mut self, factor: Real) {
199        self.volume_factor = factor;
200        self.modified = true;
201    }
202
203    /// The particle spacing of this soft body: what the impact-adaptive substeps bound the
204    /// travel per substep by, and the default contact skin of its collision meshes' colliders.
205    pub fn particle_radius(&self) -> Real {
206        self.particle_radius
207    }
208
209    /// The rigid body standing for this soft body in the islands and holding the colliders of its
210    /// surface (invalid until inserted in a set): joints, forces and impulses applied to it act on
211    /// the body as a whole. A tear splitting the body moves each of its joints to the piece closest
212    /// to its anchor in the rest shape (see [`crate::dynamics::SoftBodyTearEvent::moved_joints`]).
213    /// Its pose is recomputed from the particles at every step, so moving it has no effect, and
214    /// removing it dissolves its whole-body cluster like any cluster proxy.
215    pub fn root_body(&self) -> RigidBodyHandle {
216        self.root_body
217    }
218
219    /// The soft body this one was split off from by a tear or a cut (`None` for a user-inserted
220    /// body): the piece that kept the handle (see [`crate::dynamics::SoftBodyTearEvent::pieces`]).
221    /// Informational only; the handle dangles once that body is removed.
222    pub fn origin(&self) -> Option<crate::dynamics::SoftBodyHandle> {
223        self.origin
224    }
225
226    /// The soft bodies split off from this one by tears and cuts, in creation order (a piece
227    /// torn off a piece is listed by that piece, not here). Informational: the engine never
228    /// reads it, and a handle dangles once that body is removed.
229    pub fn pieces(&self) -> &[crate::dynamics::SoftBodyHandle] {
230        &self.pieces
231    }
232
233    /// The dynamics settings of the particles (damping, gravity scale, sleeping...).
234    pub fn particle_settings(&self) -> &super::SoftBodyParticleSettings {
235        &self.particle_settings
236    }
237
238    /// Sets the extra internal PGS iterations per substep requested for this body's island
239    /// component (see [`super::SoftBodyParticleSettings::additional_pgs_iterations`]); takes
240    /// effect at the next step.
241    pub fn set_additional_pgs_iterations(&mut self, iterations: usize) {
242        self.particle_settings.additional_pgs_iterations = iterations;
243    }
244
245    /// The number of parallel solver colors used by this soft body's elements (informational:
246    /// elements of a same color share no particle and are solved concurrently).
247    pub fn num_solver_colors(&self) -> usize {
248        self.num_colors as usize + self.has_overflow_color as usize
249    }
250
251    /// The mass-weighted center of the particles (from the cached positions).
252    pub fn center_of_mass(&self) -> Vector {
253        let mut com = Vector::ZERO;
254        let mut mass = 0.0;
255        for p in &self.particles {
256            com += p.position * p.mass;
257            mass += p.mass;
258        }
259        if mass > 0.0 { com / mass } else { com }
260    }
261
262    /// The total nominal mass of the particles.
263    pub fn mass(&self) -> Real {
264        self.particles.iter().map(|p| p.mass).sum()
265    }
266
267    /// Whether this soft body is currently sleeping (a soft body sleeps and wakes as a unit,
268    /// with the island of the rigid bodies it touches). Updated at the end of every step, and
269    /// by [`Self::wake_up`].
270    pub fn is_sleeping(&self) -> bool {
271        self.sleeping
272    }
273
274    /// Wakes this soft body up (effective at the start of the next step, together with the
275    /// island of the bodies it touches).
276    pub fn wake_up(&mut self) {
277        self.sleeping = false;
278        self.modified = true;
279    }
280
281    /// Whether this soft body takes part in the simulation. A disabled soft body (by
282    /// [`Self::set_enabled`], or by the NaN quarantine: `PhysicsPipeline::quarantine`) is
283    /// left where it is, without colliders or constraints, until it is enabled again.
284    pub fn is_enabled(&self) -> bool {
285        self.enabled
286    }
287
288    /// Enables or disables this soft body (effective at the start of the next step). Re-enabling
289    /// a quarantined body: set its non-finite particles right first
290    /// ([`Self::set_particle_position`]), they are not repaired.
291    pub fn set_enabled(&mut self, enabled: bool) {
292        if self.enabled != enabled {
293            self.enabled = enabled;
294            self.modified = true;
295        }
296    }
297
298    /// Whether every particle's state is finite.
299    pub(crate) fn is_finite(&self) -> bool {
300        self.particles.iter().all(|p| {
301            p.position.is_finite()
302                && p.velocity.is_finite()
303                && p.force.is_finite()
304                && p.next_position.is_none_or(|t| t.is_finite())
305        })
306    }
307
308    /// Neutralizes the dynamics of a non-finite body (velocities, forces, targets); the
309    /// positions are left as they are.
310    pub(crate) fn sanitize_dynamics(&mut self) {
311        for p in &mut self.particles {
312            p.velocity = Vector::ZERO;
313            p.force = Vector::ZERO;
314            p.next_position = None;
315        }
316    }
317}