Skip to main content

blast_stress_solver/
types.rs

1use std::fmt;
2use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
3
4/// 3D vector used for positions, forces, torques, and impulses.
5#[repr(C)]
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct Vec3 {
8    pub x: f32,
9    pub y: f32,
10    pub z: f32,
11}
12
13impl Vec3 {
14    pub const ZERO: Self = Self {
15        x: 0.0,
16        y: 0.0,
17        z: 0.0,
18    };
19
20    pub const fn new(x: f32, y: f32, z: f32) -> Self {
21        Self { x, y, z }
22    }
23
24    pub fn dot(self, other: Self) -> f32 {
25        self.x * other.x + self.y * other.y + self.z * other.z
26    }
27
28    pub fn cross(self, other: Self) -> Self {
29        Self::new(
30            self.y * other.z - self.z * other.y,
31            self.z * other.x - self.x * other.z,
32            self.x * other.y - self.y * other.x,
33        )
34    }
35
36    pub fn magnitude_squared(self) -> f32 {
37        self.dot(self)
38    }
39
40    pub fn magnitude(self) -> f32 {
41        self.magnitude_squared().sqrt()
42    }
43
44    pub fn normalize(self) -> Self {
45        let mag = self.magnitude();
46        if mag > 0.0 {
47            self / mag
48        } else {
49            Self::default()
50        }
51    }
52}
53
54impl Add for Vec3 {
55    type Output = Self;
56    fn add(self, rhs: Self) -> Self {
57        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
58    }
59}
60
61impl AddAssign for Vec3 {
62    fn add_assign(&mut self, rhs: Self) {
63        self.x += rhs.x;
64        self.y += rhs.y;
65        self.z += rhs.z;
66    }
67}
68
69impl Sub for Vec3 {
70    type Output = Self;
71    fn sub(self, rhs: Self) -> Self {
72        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
73    }
74}
75
76impl SubAssign for Vec3 {
77    fn sub_assign(&mut self, rhs: Self) {
78        self.x -= rhs.x;
79        self.y -= rhs.y;
80        self.z -= rhs.z;
81    }
82}
83
84impl Mul<f32> for Vec3 {
85    type Output = Self;
86    fn mul(self, rhs: f32) -> Self {
87        Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
88    }
89}
90
91impl MulAssign<f32> for Vec3 {
92    fn mul_assign(&mut self, rhs: f32) {
93        self.x *= rhs;
94        self.y *= rhs;
95        self.z *= rhs;
96    }
97}
98
99impl Div<f32> for Vec3 {
100    type Output = Self;
101    fn div(self, rhs: f32) -> Self {
102        Self::new(self.x / rhs, self.y / rhs, self.z / rhs)
103    }
104}
105
106impl DivAssign<f32> for Vec3 {
107    fn div_assign(&mut self, rhs: f32) {
108        self.x /= rhs;
109        self.y /= rhs;
110        self.z /= rhs;
111    }
112}
113
114impl Neg for Vec3 {
115    type Output = Self;
116    fn neg(self) -> Self {
117        Self::new(-self.x, -self.y, -self.z)
118    }
119}
120
121/// Node descriptor for the extended stress solver.
122/// A node with `mass == 0.0` acts as a fixed support.
123#[derive(Clone, Copy, Debug, Default)]
124pub struct NodeDesc {
125    pub centroid: Vec3,
126    pub mass: f32,
127    pub volume: f32,
128}
129
130/// Bond descriptor connecting two nodes.
131#[derive(Clone, Copy, Debug, Default)]
132pub struct BondDesc {
133    pub centroid: Vec3,
134    pub normal: Vec3,
135    pub area: f32,
136    pub node0: u32,
137    pub node1: u32,
138}
139
140/// Settings for the extended stress solver.
141#[derive(Clone, Copy, Debug)]
142pub struct SolverSettings {
143    pub max_solver_iterations_per_frame: u32,
144    pub graph_reduction_level: u32,
145    pub compression_elastic_limit: f32,
146    pub compression_fatal_limit: f32,
147    pub tension_elastic_limit: f32,
148    pub tension_fatal_limit: f32,
149    pub shear_elastic_limit: f32,
150    pub shear_fatal_limit: f32,
151}
152
153impl Default for SolverSettings {
154    fn default() -> Self {
155        Self {
156            max_solver_iterations_per_frame: 32,
157            graph_reduction_level: 0,
158            compression_elastic_limit: 1.0,
159            compression_fatal_limit: 2.0,
160            tension_elastic_limit: -1.0,
161            tension_fatal_limit: -1.0,
162            shear_elastic_limit: -1.0,
163            shear_fatal_limit: -1.0,
164        }
165    }
166}
167
168/// Decomposed stress result for a bond (Pascals).
169#[derive(Clone, Copy, Debug, Default)]
170pub struct BondStressResult {
171    pub compression: f32,
172    pub tension: f32,
173    pub shear: f32,
174}
175
176/// Mapped stress severity percentages (0..1).
177#[derive(Clone, Copy, Debug)]
178pub struct StressSeverity {
179    pub compression: f32,
180    pub tension: f32,
181    pub shear: f32,
182}
183
184impl StressSeverity {
185    pub fn max_component(&self) -> f32 {
186        self.compression.max(self.tension).max(self.shear)
187    }
188}
189
190/// Which failure mode caused a bond to break.
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum StressFailure {
193    Compression,
194    Tension,
195    Shear,
196}
197
198impl fmt::Display for StressFailure {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            StressFailure::Compression => write!(f, "compression"),
202            StressFailure::Tension => write!(f, "tension"),
203            StressFailure::Shear => write!(f, "shear"),
204        }
205    }
206}
207
208/// Material stress limits for evaluating bond failure.
209#[derive(Clone, Copy, Debug)]
210pub struct StressLimits {
211    pub compression_elastic_limit: f32,
212    pub compression_fatal_limit: f32,
213    pub tension_elastic_limit: f32,
214    pub tension_fatal_limit: f32,
215    pub shear_elastic_limit: f32,
216    pub shear_fatal_limit: f32,
217}
218
219impl Default for StressLimits {
220    fn default() -> Self {
221        Self {
222            compression_elastic_limit: 1.0,
223            compression_fatal_limit: 2.0,
224            tension_elastic_limit: -1.0,
225            tension_fatal_limit: -1.0,
226            shear_elastic_limit: -1.0,
227            shear_fatal_limit: -1.0,
228        }
229    }
230}
231
232impl StressLimits {
233    fn resolve(limit: f32, fallback: f32) -> f32 {
234        if limit > 0.0 {
235            limit
236        } else if fallback > 0.0 {
237            fallback
238        } else {
239            1.0
240        }
241    }
242
243    pub fn compression_elastic(&self) -> f32 {
244        Self::resolve(self.compression_elastic_limit, 1.0)
245    }
246
247    pub fn compression_fatal(&self) -> f32 {
248        Self::resolve(self.compression_fatal_limit, self.compression_elastic())
249    }
250
251    pub fn tension_elastic(&self) -> f32 {
252        Self::resolve(self.tension_elastic_limit, self.compression_elastic())
253    }
254
255    pub fn tension_fatal(&self) -> f32 {
256        Self::resolve(self.tension_fatal_limit, self.compression_fatal())
257    }
258
259    pub fn shear_elastic(&self) -> f32 {
260        Self::resolve(self.shear_elastic_limit, self.compression_elastic())
261    }
262
263    pub fn shear_fatal(&self) -> f32 {
264        Self::resolve(self.shear_fatal_limit, self.compression_fatal())
265    }
266
267    fn map_stress_value(stress: f32, elastic: f32, fatal: f32) -> f32 {
268        if stress <= 0.0 {
269            return 0.0;
270        }
271        let elastic = if elastic > 0.0 { elastic } else { fatal };
272        let fatal = if fatal > 0.0 { fatal } else { elastic.max(1.0) };
273        if elastic > 0.0 && stress < elastic {
274            (stress / elastic * 0.5).clamp(0.0, 0.5)
275        } else if fatal > elastic && elastic > 0.0 {
276            (0.5 + 0.5 * (stress - elastic) / (fatal - elastic)).clamp(0.5, 1.0)
277        } else {
278            (stress / fatal).clamp(0.0, 1.0)
279        }
280    }
281
282    /// Compute severity (0..1) for each stress component.
283    pub fn severity(&self, stress: &BondStressResult) -> StressSeverity {
284        StressSeverity {
285            compression: Self::map_stress_value(
286                stress.compression,
287                self.compression_elastic(),
288                self.compression_fatal(),
289            ),
290            tension: Self::map_stress_value(
291                stress.tension,
292                self.tension_elastic(),
293                self.tension_fatal(),
294            ),
295            shear: Self::map_stress_value(stress.shear, self.shear_elastic(), self.shear_fatal()),
296        }
297    }
298
299    /// Return the failure mode if any stress component exceeds its fatal limit.
300    pub fn failure_mode(&self, stress: &BondStressResult) -> Option<StressFailure> {
301        if stress.compression > self.compression_fatal() {
302            Some(StressFailure::Compression)
303        } else if stress.tension > self.tension_fatal() {
304            Some(StressFailure::Tension)
305        } else if stress.shear > self.shear_fatal() {
306            Some(StressFailure::Shear)
307        } else {
308            None
309        }
310    }
311}
312
313/// Force application mode.
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
315#[repr(u32)]
316pub enum ForceMode {
317    Force = 0,
318    Acceleration = 1,
319}
320
321/// A bond fracture within a fracture command.
322#[derive(Clone, Copy, Debug, Default)]
323pub struct BondFracture {
324    pub userdata: u32,
325    pub node_index0: u32,
326    pub node_index1: u32,
327    pub health: f32,
328}
329
330/// Fracture commands for a single actor.
331#[derive(Clone, Debug, Default)]
332pub struct FractureCommand {
333    pub actor_index: u32,
334    pub bond_fractures: Vec<BondFracture>,
335}
336
337/// A child actor produced by a split.
338#[derive(Clone, Debug)]
339pub struct SplitChild {
340    pub actor_index: u32,
341    pub nodes: Vec<u32>,
342}
343
344/// Result of applying fracture commands — one split event per parent that split.
345#[derive(Clone, Debug)]
346pub struct SplitEvent {
347    pub parent_actor_index: u32,
348    pub children: Vec<SplitChild>,
349}
350
351/// An actor in the stress graph (a connected component of nodes).
352#[derive(Clone, Debug)]
353pub struct Actor {
354    pub actor_index: u32,
355    pub nodes: Vec<u32>,
356}
357
358/// A node in a scenario description.
359#[derive(Clone, Copy, Debug, Default)]
360pub struct ScenarioNode {
361    pub centroid: Vec3,
362    pub mass: f32,
363    pub volume: f32,
364}
365
366/// A bond in a scenario description.
367#[derive(Clone, Copy, Debug, Default)]
368pub struct ScenarioBond {
369    pub node0: u32,
370    pub node1: u32,
371    pub centroid: Vec3,
372    pub normal: Vec3,
373    pub area: f32,
374}
375
376/// Optional exact collider shape for a scenario node.
377#[derive(Clone, Debug)]
378pub enum ScenarioCollider {
379    Cuboid { half_extents: Vec3 },
380    ConvexHull { points: Vec<Vec3> },
381}
382
383/// Full scenario description (nodes + bonds).
384#[derive(Clone, Debug, Default)]
385pub struct ScenarioDesc {
386    pub nodes: Vec<ScenarioNode>,
387    pub bonds: Vec<ScenarioBond>,
388    /// Optional exact per-node collider sizes for Rapier integration.
389    /// When omitted, Rapier helpers fall back to cube-root volume estimates.
390    pub node_sizes: Vec<Vec3>,
391    /// Optional exact per-node collider shapes for Rapier integration.
392    /// When omitted, Rapier helpers fall back to cuboids sized from `node_sizes`.
393    pub collider_shapes: Vec<Option<ScenarioCollider>>,
394}
395
396impl ScenarioDesc {
397    /// Convert to NodeDesc/BondDesc slices suitable for the solver.
398    pub fn to_solver_descs(&self) -> (Vec<NodeDesc>, Vec<BondDesc>) {
399        let nodes = self
400            .nodes
401            .iter()
402            .map(|n| NodeDesc {
403                centroid: n.centroid,
404                mass: n.mass,
405                volume: n.volume,
406            })
407            .collect();
408        let bonds = self
409            .bonds
410            .iter()
411            .map(|b| BondDesc {
412                centroid: b.centroid,
413                normal: b.normal,
414                area: b.area,
415                node0: b.node0,
416                node1: b.node1,
417            })
418            .collect();
419        (nodes, bonds)
420    }
421}