rapier3d/dynamics/soft_body/soft_body_material.rs
1//! The `SoftBodyMaterial`: per-family stiffness, damping, plasticity and tearing parameters.
2use crate::dynamics::SpringCoefficients;
3use crate::dynamics::soft_body::SoftEdgePlasticFlow;
4use crate::math::Real;
5#[cfg(not(feature = "std"))]
6#[allow(unused_imports)]
7use simba::scalar::{ComplexField as _, RealField as _};
8
9/// The stiffness and damping of every soft-body constraint family: a `natural_frequency` (Hz) and
10/// `damping_ratio` per family, normalized by the constraint's effective mass (independent of mass,
11/// substeps and timestep); a frequency far above the substep rate saturates instead of exploding.
12#[derive(Copy, Clone, Debug, PartialEq)]
13#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
14pub struct SoftBodyMaterial {
15 /// Softness of the structural edges.
16 pub edge_softness: SpringCoefficients<Real>,
17 /// Softness of the bending edges and dihedral constraints.
18 pub bend_softness: SpringCoefficients<Real>,
19 /// Softness of the per-cell volume constraints ([`crate::dynamics::SoftBodyCellModel::Volume`]) and of the global
20 /// volume-preservation constraint.
21 pub volume_softness: SpringCoefficients<Real>,
22 /// Softness of the shape-matching constraints.
23 pub shape_matching_softness: SpringCoefficients<Real>,
24 /// Young's modulus of the elastic cells ([`crate::dynamics::SoftBodyCellModel::Corotational`]
25 /// and [`crate::dynamics::SoftBodyCellModel::NeoHookean`]), in force per unit area (3D) or
26 /// length (2D); their natural frequency derives from it, so a finer mesh does not get stiffer.
27 pub young_modulus: Real,
28 /// Poisson's ratio of the elastic cells, in `[0, 0.5)`.
29 pub poisson_ratio: Real,
30 /// Damping ratio of the elastic cells.
31 pub elastic_damping_ratio: Real,
32 /// Plastic yield of the elastic cells: the strain (Frobenius norm of the deviatoric part of
33 /// `sym(RᵀF) - I`, read from stress by the constraint solver) beyond which a cell's
34 /// rest shape flows toward its current shape (default: `0.0`, no plasticity).
35 pub plastic_yield: Real,
36 /// Rate (per second) at which the strain in excess of the yield is absorbed into the rest
37 /// shape (default: `1.0`); the flow is deviatoric (volume preserving).
38 pub plastic_creep: Real,
39 /// Largest accumulated plastic deformation of a cell, the Frobenius norm of `P - I` with `P`
40 /// its rest shape's plastic stretch (default: `1.0`). Flow past it is projected back onto the
41 /// bound, so a crushed cell cannot flow toward a sliver; an inverted cell never flows.
42 pub plastic_max: Real,
43 /// Rate (per second) at which the particles' velocities are pulled toward the body's best-fit
44 /// rigid motion, damping every deformation mode but not the whole-body motion; a few units per
45 /// second settle a stiff slender body's residual bending sway (default: `0.0`, off).
46 pub deformation_damping: Real,
47 /// Plastic yield of the edges (structural and bending): the strain `|length / rest_length - 1|`
48 /// beyond which the rest length flows toward the current length (default: `0.0`, none), at the
49 /// rate [`Self::edge_plastic_creep`], up to `edge_plastic_max`; tears use the initial length.
50 #[cfg_attr(feature = "serde-serialize", serde(default))]
51 pub edge_plastic_yield: Real,
52 /// Rate (per second) at which an edge's strain in excess of its yield is absorbed into its
53 /// rest length (default: `1.0`); `Real::INFINITY` (or anything above the step rate) sets it at
54 /// once.
55 #[cfg_attr(feature = "serde-serialize", serde(default = "one"))]
56 pub edge_plastic_creep: Real,
57 /// Largest permanent set an edge may accumulate, as a fraction of its initial length (default:
58 /// `0.5`); past it the edge stops yielding and stays elastic, so a crushed edge cannot flow to
59 /// nothing.
60 #[cfg_attr(feature = "serde-serialize", serde(default = "half"))]
61 pub edge_plastic_max: Real,
62 /// Whether the edges take a permanent set under a squeeze, a stretch, or both (default:
63 /// both).
64 #[cfg_attr(feature = "serde-serialize", serde(default))]
65 pub edge_plastic_flow: SoftEdgePlasticFlow,
66 /// Strain beyond which elements break (default: `None`, unbreakable): an edge stretched past
67 /// this fraction of its initial rest length, or a corotational cell whose largest tensile
68 /// principal strain (capped at `1.0`) exceeds it, tears at step end; volume cells never do.
69 #[cfg_attr(feature = "serde-serialize", serde(default))]
70 pub tear_strain: Option<Real>,
71 /// Force beyond which the edges break (default: `None`): an edge whose force along its
72 /// direction (its impulse over the substep, positive when resisting stretching) exceeds it is
73 /// torn at the end of the step. Either criterion tears an edge; cells tear on strain only.
74 #[cfg_attr(feature = "serde-serialize", serde(default))]
75 pub tear_force: Option<Real>,
76 /// Time constant, in seconds, over which an element's load is smoothed exponentially before the
77 /// tear test (default: `0.0`, one step past the threshold tears); applies to the strain and
78 /// force criteria alike, and [`crate::dynamics::SoftBodyEdge::stress`] reads the smoothed load.
79 #[cfg_attr(feature = "serde-serialize", serde(default))]
80 pub tear_smoothing: Real,
81 /// How much tougher an undamaged interior element (no particle on the surface or on an earlier
82 /// tear) is than a surface one (default: `1.0`): it needs this many times its tear threshold,
83 /// so tears start at the surface or existing damage and run inward; scales `tear_resistance`.
84 #[cfg_attr(feature = "serde-serialize", serde(default = "one"))]
85 pub interior_strength: Real,
86 /// Maximum number of edges torn per step (default: `u32::MAX`, no limit): the most loaded go
87 /// first, the others wait for the next step (edges past twice their threshold always tear).
88 /// Bounding it paces the cracks of a taut sheet but lets a projectile be caught as in a net.
89 #[cfg_attr(
90 feature = "serde-serialize",
91 serde(default = "default_max_tears_per_step")
92 )]
93 pub max_tears_per_step: u32,
94 /// The smallest piece, in measure elements, a tear may split off (default: `None`: ten
95 /// triangles for a 3D cloth, three cells in 2D or six in 3D, three segments for a rope). A tear
96 /// leaving a smaller piece waits (cuts are exempt); `Some(1)` lets a tear shed single elements.
97 #[cfg_attr(feature = "serde-serialize", serde(default))]
98 pub min_piece: Option<u32>,
99}
100
101#[cfg(feature = "serde-serialize")]
102fn default_max_tears_per_step() -> u32 {
103 u32::MAX
104}
105
106#[cfg(feature = "serde-serialize")]
107pub(super) fn default_true() -> bool {
108 true
109}
110
111#[cfg(feature = "serde-serialize")]
112pub(super) fn one() -> Real {
113 1.0
114}
115
116#[cfg(feature = "serde-serialize")]
117fn half() -> Real {
118 0.5
119}
120
121impl Default for SoftBodyMaterial {
122 fn default() -> Self {
123 Self {
124 edge_softness: SpringCoefficients::new(30.0, 1.0),
125 bend_softness: SpringCoefficients::new(10.0, 1.0),
126 volume_softness: SpringCoefficients::new(30.0, 1.0),
127 shape_matching_softness: SpringCoefficients::new(10.0, 1.0),
128 young_modulus: 1.0e4,
129 poisson_ratio: 0.3,
130 elastic_damping_ratio: 1.0,
131 plastic_yield: 0.0,
132 plastic_creep: 1.0,
133 plastic_max: 1.0,
134 deformation_damping: 0.0,
135 edge_plastic_yield: 0.0,
136 edge_plastic_creep: 1.0,
137 edge_plastic_max: 0.5,
138 edge_plastic_flow: SoftEdgePlasticFlow::Both,
139 tear_strain: None,
140 tear_force: None,
141 tear_smoothing: 0.0,
142 interior_strength: 1.0,
143 max_tears_per_step: u32::MAX,
144 min_piece: None,
145 }
146 }
147}
148
149impl SoftBodyMaterial {
150 /// A material with the same softness for every spring family (edges, bending, volume, shape
151 /// matching); the elastic-cell parameters keep their defaults.
152 pub fn uniform(softness: SpringCoefficients<Real>) -> Self {
153 Self {
154 edge_softness: softness,
155 bend_softness: softness,
156 volume_softness: softness,
157 shape_matching_softness: softness,
158 ..Default::default()
159 }
160 }
161
162 /// Whether some tear criterion is set (`tear_strain` or `tear_force`).
163 pub fn tears(&self) -> bool {
164 self.tear_strain.is_some() || self.tear_force.is_some()
165 }
166
167 /// Load of an edge as a fraction of its tear threshold: the larger of its stretch (`length`
168 /// over its initial rest length `rest`) over `tear_strain` and its `force` along its direction
169 /// (positive when resisting stretching) over `tear_force`; compression or no threshold: `0.0`.
170 pub(crate) fn edge_tear_load(&self, length: Real, rest: Real, force: Real) -> Real {
171 let mut load: Real = 0.0;
172 if let Some(tear) = self.tear_strain {
173 if rest > 0.0 {
174 load = (length / rest - 1.0).max(0.0) / tear.max(Real::EPSILON);
175 }
176 }
177 if let Some(tear) = self.tear_force {
178 load = load.max(force.max(0.0) / tear.max(Real::EPSILON));
179 }
180 load
181 }
182
183 /// The load of a cell whose largest tensile strain is `strain`, as a fraction of its tear
184 /// threshold (`0.0` without a `tear_strain`).
185 pub(crate) fn cell_tear_load(&self, strain: Real) -> Real {
186 match self.tear_strain {
187 Some(tear) => strain.max(0.0) / tear.max(Real::EPSILON),
188 None => 0.0,
189 }
190 }
191
192 /// Blends the load `load` measured over a step of length `dt` into the smoothed load
193 /// `stress`, over the `tear_smoothing` time constant (no smoothing: the load itself).
194 pub(crate) fn smooth_stress(&self, stress: Real, load: Real, dt: Real) -> Real {
195 if self.tear_smoothing <= 0.0 {
196 load
197 } else {
198 let blend = 1.0 - (-dt / self.tear_smoothing).exp();
199 stress + (load - stress) * blend
200 }
201 }
202
203 /// The Lamé parameters `(μ, λ)` of the elastic cells.
204 pub fn lame_parameters(&self) -> (Real, Real) {
205 let nu = self.poisson_ratio.clamp(0.0, 0.499);
206 let e = self.young_modulus.max(0.0);
207 let mu = e / (2.0 * (1.0 + nu));
208 let lambda = e * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
209 (mu, lambda)
210 }
211}