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}