rapier3d/dynamics/soft_body/soft_body_elements.rs
1//! The element types of a soft body: particles, edges, dihedrals, cells, and the per-body solver and cell-model choices.
2#[cfg(doc)]
3use super::SoftBodyMaterial;
4#[cfg(feature = "serde-serialize")]
5use super::soft_body_material::one;
6use crate::dynamics::SpringCoefficients;
7use crate::math::{DIM, Matrix, Real, Rotation, Vector};
8#[cfg(not(feature = "std"))]
9#[allow(unused_imports)]
10use simba::scalar::{ComplexField as _, RealField as _};
11
12/// One particle (point mass) of a soft body: plain state owned by its
13/// [`crate::dynamics::SoftBody`], given its own solver body for the step when the body is awake.
14/// A pinned particle (`inv_mass == 0`) is kinematic: it moves at its velocity or toward its target.
15#[derive(Copy, Clone, Debug)]
16#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
17pub struct SoftBodyParticle {
18 /// World-space position.
19 pub(crate) position: Vector,
20 /// World-space velocity.
21 #[cfg_attr(feature = "serde-serialize", serde(default))]
22 pub(crate) velocity: Vector,
23 /// Position in the body's rest shape, relative to the rest center of mass (the
24 /// shape-matching goal shape). Follows the plastic flow of the elements (see
25 /// [`Self::initial_rest_position`]).
26 pub(crate) rest_position: Vector,
27 /// The rest position before any plastic flow: the world-space position at insertion,
28 /// relative to the rest center of mass. The cells' initial rest shapes are read from it.
29 pub(crate) initial_rest_position: Vector,
30 /// Nominal mass (used by shape matching even for pinned particles).
31 pub(crate) mass: Real,
32 /// `0.0` for pinned particles.
33 pub(crate) inv_mass: Real,
34 /// User force applied to the particle every step until reset (`SoftBody::reset_forces`).
35 #[cfg_attr(feature = "serde-serialize", serde(default))]
36 pub(crate) force: Vector,
37 /// Where a pinned particle must be at the end of the next step (`None`: it keeps moving at
38 /// its velocity).
39 #[cfg_attr(feature = "serde-serialize", serde(default))]
40 pub(crate) next_position: Option<Vector>,
41 /// Set once an element touching the particle tore (see [`Self::is_damaged`]).
42 #[cfg_attr(feature = "serde-serialize", serde(default))]
43 pub(crate) damaged: bool,
44 /// Whether some surface element has this particle as a vertex (updated with the boundary).
45 #[cfg_attr(feature = "serde-serialize", serde(default))]
46 pub(crate) on_surface: bool,
47 /// The particle this one was split from, shared by every copy of a split particle (its own
48 /// index when it was never split; `u32::MAX` also reads as its own index, see
49 /// [`Self::split_root`]).
50 #[cfg_attr(feature = "serde-serialize", serde(default = "no_split_root"))]
51 pub(crate) split_root: u32,
52}
53
54#[cfg(feature = "serde-serialize")]
55fn no_split_root() -> u32 {
56 u32::MAX
57}
58
59impl SoftBodyParticle {
60 /// The root of this particle's split family, `index` being this particle's own index: two
61 /// particles with the same root are copies of one particle a crack went through.
62 pub(crate) fn split_root(&self, index: u32) -> u32 {
63 if self.split_root == u32::MAX {
64 index
65 } else {
66 self.split_root
67 }
68 }
69
70 /// The world-space position of this particle.
71 pub fn position(&self) -> Vector {
72 self.position
73 }
74
75 /// The world-space velocity of this particle.
76 pub fn velocity(&self) -> Vector {
77 self.velocity
78 }
79
80 /// The user force currently applied to this particle (see [`crate::dynamics::SoftBody::add_particle_force`]).
81 pub fn force(&self) -> Vector {
82 self.force
83 }
84
85 /// The position a pinned particle is moving to over the next step, if one was set with
86 /// [`crate::dynamics::SoftBody::set_particle_kinematic_target`].
87 pub fn kinematic_target(&self) -> Option<Vector> {
88 self.next_position
89 }
90
91 /// The position of this particle in the rest shape (relative to the rest center of mass).
92 /// With a plastic material the rest shape follows the elements' flow (see
93 /// [`SoftBodyMaterial::plastic_yield`]); [`Self::initial_rest_position`] predates any flow.
94 pub fn rest_position(&self) -> Vector {
95 self.rest_position
96 }
97
98 /// The position of this particle in the soft body's rest shape before any plastic flow
99 /// (its position at insertion, relative to the rest center of mass).
100 pub fn initial_rest_position(&self) -> Vector {
101 self.initial_rest_position
102 }
103
104 /// The nominal mass of this particle.
105 pub fn mass(&self) -> Real {
106 self.mass
107 }
108
109 /// The inverse mass of this particle (`0.0` for pinned particles).
110 pub fn inv_mass(&self) -> Real {
111 self.inv_mass
112 }
113
114 /// Whether this particle is pinned (infinite mass, moved kinematically).
115 pub fn is_pinned(&self) -> bool {
116 self.inv_mass == 0.0
117 }
118
119 /// Whether an element touching this particle has torn (or [`crate::dynamics::SoftBody::set_particle_damaged`]
120 /// marked it). The elements around a damaged particle lose the interior strength of
121 /// [`SoftBodyMaterial::interior_strength`]: a tear runs on from where it started.
122 pub fn is_damaged(&self) -> bool {
123 self.damaged
124 }
125
126 /// Whether this particle is a vertex of the body's surface (a boundary segment in 2D, a
127 /// boundary triangle in 3D). An element whose particles are all interior gets the material's
128 /// [`SoftBodyMaterial::interior_strength`].
129 pub fn is_on_surface(&self) -> bool {
130 self.on_surface
131 }
132}
133
134/// The role of a soft-body edge (distance constraint).
135#[derive(Copy, Clone, Debug, PartialEq, Eq)]
136#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
137pub enum SoftBodyEdgeKind {
138 /// A structural edge, keeping two neighboring particles at their rest distance
139 /// (softness: [`SoftBodyMaterial::edge_softness`]).
140 Structural,
141 /// A bending edge, keeping two second-neighbor particles at their rest distance
142 /// (softness: [`SoftBodyMaterial::bend_softness`]).
143 Bend,
144}
145
146/// A distance constraint between two particles of a soft body.
147#[derive(Copy, Clone, Debug)]
148#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
149pub struct SoftBodyEdge {
150 /// The two particles.
151 pub vertices: [u32; 2],
152 /// The distance the edge tries to maintain.
153 pub rest_length: Real,
154 /// Structural or bending edge.
155 pub kind: SoftBodyEdgeKind,
156 /// If `true`, the edge only resists stretching (a rope segment), never compression.
157 pub tension_only: bool,
158 /// Softness of this edge, overriding the material's (`edge_softness` / `bend_softness`)
159 /// when set: anisotropic cloth (warp, weft and shear stiffness), stiffer seams...
160 pub softness: Option<SpringCoefficients<Real>>,
161 /// Multiplier of the material's tear thresholds for this edge (default `1.0`): a seam that
162 /// holds, a perforation that gives. Usually set through [`crate::dynamics::SoftBody::set_edge_tear_resistance`]
163 /// or [`crate::dynamics::SoftBody::set_cluster_tear_resistance`].
164 #[cfg_attr(feature = "serde-serialize", serde(default = "one"))]
165 pub tear_resistance: Real,
166 /// Accumulated impulse of the last step (warm-start state).
167 pub(crate) impulse: Real,
168 /// Accumulated plastic strain: `rest_length = initial rest length * (1 + plastic_strain)`
169 /// (see [`Self::plastic_strain`]).
170 #[cfg_attr(feature = "serde-serialize", serde(default))]
171 pub(crate) plastic_strain: Real,
172 /// Parallel solve color.
173 pub(crate) color: u8,
174 /// Set when the edge was loaded past the material's tear threshold during the last step
175 /// (or by [`crate::dynamics::SoftBody::tear_edge`]): removed by the tearing pass at the end of the step.
176 #[cfg_attr(feature = "serde-serialize", serde(skip))]
177 pub(crate) torn: bool,
178 /// The load of the edge as a fraction of its tear threshold, smoothed over the material's
179 /// `tear_smoothing` (see [`Self::stress`]).
180 #[cfg_attr(feature = "serde-serialize", serde(default))]
181 pub(crate) stress: Real,
182}
183
184impl SoftBodyEdge {
185 /// The impulse this edge applied during the last substep, along the edge direction
186 /// (positive when the edge was resisting stretching).
187 pub fn impulse(&self) -> Real {
188 self.impulse
189 }
190
191 /// Load of this edge as a fraction of its tear threshold, smoothed over
192 /// [`SoftBodyMaterial::tear_smoothing`]: `0.0` slack, `1.0` tearing. The larger of the stretch
193 /// over `tear_strain` and the force over `tear_force`; `0.0` while the material has neither.
194 pub fn stress(&self) -> Real {
195 self.stress
196 }
197}
198
199/// A dihedral bending constraint between two triangles sharing an edge (3D only).
200///
201/// `vertices[0..2]` is the shared edge, `vertices[2]` and `vertices[3]` the two opposite
202/// vertices.
203#[cfg(feature = "dim3")]
204#[derive(Copy, Clone, Debug)]
205#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
206pub struct SoftBodyDihedral {
207 /// The four particles.
208 pub vertices: [u32; 4],
209 /// The angle between the two triangle normals at rest.
210 pub rest_angle: Real,
211 /// Accumulated impulse of the last step (warm-start state).
212 pub(crate) impulse: Real,
213 /// Accumulated plastic set of the rest angle: `rest_angle = initial rest angle +
214 /// plastic_set` (see [`Self::plastic_set`]).
215 #[cfg_attr(feature = "serde-serialize", serde(default))]
216 pub(crate) plastic_set: Real,
217 /// Parallel solve color.
218 pub(crate) color: u8,
219}
220
221#[cfg(feature = "dim3")]
222impl SoftBodyDihedral {
223 /// The permanent set of this dihedral: how far its rest angle has flowed from the angle it
224 /// was created with, in radians (see [`SoftBodyMaterial::edge_plastic_yield`]).
225 pub fn plastic_set(&self) -> Real {
226 self.plastic_set
227 }
228
229 /// The rest angle this dihedral was created with (its `rest_angle` before any plastic
230 /// flow).
231 pub fn initial_rest_angle(&self) -> Real {
232 self.rest_angle - self.plastic_set
233 }
234}
235
236/// Which solver simulates a soft body's elasticity (requires the `fem` cargo feature).
237#[cfg(feature = "fem")]
238#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
239#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
240pub enum SoftBodySolver {
241 /// Every elastic element becomes constraints, warm-started and swept Gauss-Seidel by color once
242 /// per substep with the contacts and joints. Cheap and robust, but converged only as far as the
243 /// sweep count: a stiff body keeps a residual compliance, a load crosses a long body slowly.
244 #[default]
245 Constraints,
246 /// Implicit Euler elasticity: forces and tangent stiffness from the strain-energy density,
247 /// `(M + h D + h² K) Δv = b` solved over the whole body once per substep; constraints see the
248 /// body through its augmented mass. Costs a factorization per step and a solve per constraint.
249 Fem,
250}
251
252/// The constitutive model of a soft body's cells (triangles in 2D, tetrahedra in 3D).
253#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
254#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
255pub enum SoftBodyCellModel {
256 /// One constraint per cell keeping its signed area/volume at its rest value
257 /// (softness: [`SoftBodyMaterial::volume_softness`]). Cheap; combined with the edges this
258 /// gives a convincing jelly.
259 #[default]
260 Volume,
261 /// Corotational elasticity (linear elasticity in the cell's rotation-free frame) plus an exact
262 /// volume constraint, parameterized by [`SoftBodyMaterial::young_modulus`], `poisson_ratio` and
263 /// `elastic_damping_ratio`. Stable at any stiffness, recovers from inverted cells.
264 Corotational,
265 /// Stable Neo-Hookean hyperelasticity (Smith et al. 2018): the parameters (plasticity too) and
266 /// small-strain behavior of [`SoftBodyCellModel::Corotational`], but Neo-Hookean at large
267 /// strains (stiffer than linear elasticity under compression, softer under tension, inverted
268 /// cells pushed back through the collapse); 2x cost.
269 NeoHookean,
270}
271
272/// Number of warm-start impulses stored per cell (the corotational model's strain rows plus its
273/// volumetric row).
274pub(crate) const CELL_IMPULSES: usize = DIM * (DIM + 1) / 2 + 1;
275
276/// A simplex cell of a soft body: a triangle in 2D, a tetrahedron in 3D.
277#[derive(Copy, Clone, Debug)]
278#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
279pub struct SoftBodyCell {
280 /// The cell's particles, positively oriented (positive signed area/volume at rest).
281 pub vertices: [u32; DIM + 1],
282 /// The signed area (2D) or volume (3D) of the cell at rest.
283 pub rest_volume: Real,
284 /// Inverse of the rest edge matrix `[x1 - x0, x2 - x0, (x3 - x0)]`.
285 pub(crate) inv_rest_matrix: Matrix,
286 /// Accumulated plastic stretch of the rest shape (material frame, unit determinant): the rest
287 /// edge matrix is `plastic_stretch * Dm₀`, `Dm₀` the initial rest edge matrix. Bounded by the
288 /// material's `plastic_max`, which keeps flowing cells from degenerating into slivers.
289 #[cfg_attr(feature = "serde-serialize", serde(default = "identity_matrix"))]
290 pub(crate) plastic_stretch: Matrix,
291 /// Accumulated impulses of the last step (warm-start state): the volume constraint's in `[0]`, or
292 /// the corotational constraints' (strain rows in the cell frame, then volumetric).
293 pub(crate) impulses: [Real; CELL_IMPULSES],
294 /// Rotation of the polar decomposition of the cell's deformation gradient at the last step
295 /// (warm start of the corotational constraints' rotation extraction).
296 pub(crate) rotation: Rotation,
297 /// Multiplier of the material's Young modulus for this cell (default `1.0`): per-region
298 /// stiffness, usually set through [`crate::dynamics::SoftBody::set_cluster_stiffness_scale`].
299 #[cfg_attr(feature = "serde-serialize", serde(default = "one"))]
300 pub stiffness_scale: Real,
301 /// Multiplier of the material's tear strain for this cell (default `1.0`), see
302 /// [`crate::dynamics::SoftBody::set_cell_tear_resistance`].
303 #[cfg_attr(feature = "serde-serialize", serde(default = "one"))]
304 pub tear_resistance: Real,
305 /// Parallel solve color.
306 pub(crate) color: u8,
307 /// Set when the cell was strained past the material's `tear_strain` during the last step
308 /// (or by [`crate::dynamics::SoftBody::tear_cell`]): removed by the tearing pass at the end of the step.
309 #[cfg_attr(feature = "serde-serialize", serde(skip))]
310 pub(crate) torn: bool,
311 /// The tensile strain of the cell as a fraction of its tear threshold, smoothed over the
312 /// material's `tear_smoothing` (see [`Self::stress`]).
313 #[cfg_attr(feature = "serde-serialize", serde(default))]
314 pub(crate) stress: Real,
315}
316
317#[cfg(feature = "serde-serialize")]
318fn identity_matrix() -> Matrix {
319 Matrix::IDENTITY
320}
321
322impl SoftBodyCell {
323 /// The accumulated plastic stretch of this cell's rest shape (material frame, unit
324 /// determinant): its current rest shape is this stretch applied to the shape it was created
325 /// with (see [`SoftBodyMaterial::plastic_yield`]); the identity before any flow.
326 pub fn plastic_stretch(&self) -> Matrix {
327 self.plastic_stretch
328 }
329
330 /// The largest tensile strain of this cell as a fraction of its tear threshold, smoothed
331 /// over the material's [`SoftBodyMaterial::tear_smoothing`]: `0.0` slack, `1.0` tearing.
332 /// Stays `0.0` while the material has no `tear_strain` (cells only tear on their strain).
333 pub fn stress(&self) -> Real {
334 self.stress
335 }
336}