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