Skip to main content

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}