Skip to main content

blast_stress_solver/
stress_processor.rs

1//! Low-level safe wrapper around the C `StressProcessor`.
2
3use crate::ffi;
4use crate::types::Vec3;
5
6/// Low-level node descriptor for the StressProcessor.
7#[repr(C)]
8#[derive(Clone, Copy, Debug, Default)]
9pub struct StressNodeDesc {
10    pub com: Vec3,
11    pub mass: f32,
12    pub inertia: f32,
13}
14
15/// Low-level bond descriptor for the StressProcessor.
16#[repr(C)]
17#[derive(Clone, Copy, Debug, Default)]
18pub struct StressBondDesc {
19    pub centroid: Vec3,
20    pub node0: u32,
21    pub node1: u32,
22}
23
24/// Angular + linear velocity.
25#[repr(C)]
26#[derive(Clone, Copy, Debug, Default)]
27pub struct StressVelocity {
28    pub ang: Vec3,
29    pub lin: Vec3,
30}
31
32/// Angular + linear impulse.
33#[repr(C)]
34#[derive(Clone, Copy, Debug, Default)]
35pub struct StressImpulse {
36    pub ang: Vec3,
37    pub lin: Vec3,
38}
39
40/// Data preparation parameters.
41#[repr(C)]
42#[derive(Clone, Copy, Debug, Default)]
43pub struct StressDataParams {
44    pub equalize_masses: bool,
45    pub center_bonds: bool,
46}
47
48/// Solver iteration parameters.
49#[derive(Clone, Copy, Debug)]
50pub struct StressSolverParams {
51    pub max_iterations: u32,
52    pub tolerance: f32,
53    pub warm_start: bool,
54}
55
56impl Default for StressSolverParams {
57    fn default() -> Self {
58        Self {
59            max_iterations: 32,
60            tolerance: 1.0e-6,
61            warm_start: false,
62        }
63    }
64}
65
66/// Squared error residuals from the solver.
67#[derive(Clone, Copy, Debug, Default)]
68pub struct StressErrorSq {
69    pub ang: f32,
70    pub lin: f32,
71}
72
73/// Low-level stress solver (conjugate gradient on a bond graph).
74///
75/// This wraps the C++ `StressProcessor` via FFI. For most use cases,
76/// prefer [`ExtStressSolver`](crate::ExtStressSolver) which includes
77/// NvBlast actor management and fracture support.
78pub struct StressProcessor {
79    handle: *mut ffi::StressProcessorHandle,
80}
81
82unsafe impl Send for StressProcessor {}
83unsafe impl Sync for StressProcessor {}
84
85impl StressProcessor {
86    /// Create a new processor from node and bond descriptors.
87    pub fn new(
88        nodes: &[StressNodeDesc],
89        bonds: &[StressBondDesc],
90        params: StressDataParams,
91    ) -> Option<Self> {
92        if nodes.is_empty() || bonds.is_empty() {
93            return None;
94        }
95        let ffi_params = ffi::FfiStressDataParams {
96            equalize_masses: params.equalize_masses as u8,
97            center_bonds: params.center_bonds as u8,
98        };
99        let handle = unsafe {
100            ffi::stress_processor_create(
101                nodes.as_ptr() as *const ffi::FfiStressNodeDesc,
102                nodes.len() as u32,
103                bonds.as_ptr() as *const ffi::FfiStressBondDesc,
104                bonds.len() as u32,
105                ffi_params,
106            )
107        };
108        if handle.is_null() {
109            None
110        } else {
111            Some(Self { handle })
112        }
113    }
114
115    pub fn node_count(&self) -> u32 {
116        unsafe { ffi::stress_processor_node_count(self.handle) }
117    }
118
119    pub fn bond_count(&self) -> u32 {
120        unsafe { ffi::stress_processor_bond_count(self.handle) }
121    }
122
123    pub fn node_desc(&self, index: u32) -> Option<StressNodeDesc> {
124        let mut desc = ffi::FfiStressNodeDesc::default();
125        let ok = unsafe { ffi::stress_processor_get_node_desc(self.handle, index, &mut desc) };
126        if ok != 0 {
127            Some(StressNodeDesc {
128                com: desc.com,
129                mass: desc.mass,
130                inertia: desc.inertia,
131            })
132        } else {
133            None
134        }
135    }
136
137    pub fn bond_desc(&self, index: u32) -> Option<StressBondDesc> {
138        let mut desc = ffi::FfiStressBondDesc::default();
139        let ok = unsafe { ffi::stress_processor_get_bond_desc(self.handle, index, &mut desc) };
140        if ok != 0 {
141            Some(StressBondDesc {
142                centroid: desc.centroid,
143                node0: desc.node0,
144                node1: desc.node1,
145            })
146        } else {
147            None
148        }
149    }
150
151    /// Remove a bond from the solver. Returns `true` on success.
152    pub fn remove_bond(&mut self, bond_index: u32) -> bool {
153        unsafe { ffi::stress_processor_remove_bond(self.handle, bond_index) != 0 }
154    }
155
156    /// Solve for impulses given node velocities.
157    ///
158    /// `impulses` must have length == `bond_count()`, `velocities` must have length == `node_count()`.
159    pub fn solve(
160        &mut self,
161        impulses: &mut [StressImpulse],
162        velocities: &[StressVelocity],
163        params: StressSolverParams,
164        resume: bool,
165    ) -> Result<(i32, StressErrorSq), &'static str> {
166        if impulses.len() as u32 != self.bond_count() {
167            return Err("impulse array must match bond count");
168        }
169        if velocities.len() as u32 != self.node_count() {
170            return Err("velocity array must match node count");
171        }
172        let ffi_params = ffi::FfiStressSolverParams {
173            max_iterations: params.max_iterations,
174            tolerance: params.tolerance,
175            warm_start: params.warm_start as u8,
176            _pad: [0; 3],
177        };
178        let mut error = ffi::FfiStressErrorSq::default();
179        let iterations = unsafe {
180            ffi::stress_processor_solve(
181                self.handle,
182                impulses.as_mut_ptr() as *mut ffi::FfiStressImpulse,
183                velocities.as_ptr() as *const ffi::FfiStressVelocity,
184                ffi_params,
185                &mut error,
186                resume as u8,
187            )
188        };
189        if iterations < 0 {
190            Err("solver returned an error")
191        } else {
192            Ok((
193                iterations,
194                StressErrorSq {
195                    ang: error.ang,
196                    lin: error.lin,
197                },
198            ))
199        }
200    }
201
202    /// Check if the compiled solver uses SIMD.
203    pub fn using_simd() -> bool {
204        unsafe { ffi::stress_processor_using_simd() != 0 }
205    }
206}
207
208impl Drop for StressProcessor {
209    fn drop(&mut self) {
210        unsafe { ffi::stress_processor_destroy(self.handle) }
211    }
212}