Skip to main content

blast_stress_solver/
ext_stress_solver.rs

1//! High-level stress solver with NvBlast actor management and fracture support.
2
3use crate::ffi;
4use crate::types::*;
5
6/// High-level stress solver that manages NvBlast families, actors, and fracture commands.
7///
8/// This is the primary API for most users. It wraps the C++ `ExtStressSolver` which internally
9/// manages an NvBlast asset, family, and actors alongside the stress computation.
10pub struct ExtStressSolver {
11    handle: *mut ffi::ExtStressSolverHandle,
12}
13
14unsafe impl Send for ExtStressSolver {}
15unsafe impl Sync for ExtStressSolver {}
16
17impl ExtStressSolver {
18    /// Create a solver from node and bond descriptors with the given settings.
19    pub fn new(nodes: &[NodeDesc], bonds: &[BondDesc], settings: &SolverSettings) -> Option<Self> {
20        if nodes.is_empty() || bonds.is_empty() {
21            return None;
22        }
23
24        let ffi_nodes: Vec<ffi::FfiExtStressNodeDesc> = nodes
25            .iter()
26            .map(|n| ffi::FfiExtStressNodeDesc {
27                centroid: n.centroid,
28                mass: n.mass,
29                volume: n.volume,
30            })
31            .collect();
32
33        let ffi_bonds: Vec<ffi::FfiExtStressBondDesc> = bonds
34            .iter()
35            .map(|b| ffi::FfiExtStressBondDesc {
36                centroid: b.centroid,
37                normal: b.normal,
38                area: b.area,
39                node0: b.node0,
40                node1: b.node1,
41            })
42            .collect();
43
44        let ffi_settings = to_ffi_settings(settings);
45
46        let handle = unsafe {
47            ffi::ext_stress_solver_create(
48                ffi_nodes.as_ptr(),
49                ffi_nodes.len() as u32,
50                ffi_bonds.as_ptr(),
51                ffi_bonds.len() as u32,
52                &ffi_settings,
53            )
54        };
55
56        if handle.is_null() {
57            None
58        } else {
59            Some(Self { handle })
60        }
61    }
62
63    /// Update solver settings.
64    pub fn set_settings(&mut self, settings: &SolverSettings) {
65        let ffi_settings = to_ffi_settings(settings);
66        unsafe { ffi::ext_stress_solver_set_settings(self.handle, &ffi_settings) }
67    }
68
69    /// Reset accumulated forces.
70    pub fn reset(&mut self) {
71        unsafe { ffi::ext_stress_solver_reset(self.handle) }
72    }
73
74    /// Apply a force to a specific node.
75    pub fn add_force(&mut self, node_index: u32, position: Vec3, force: Vec3, mode: ForceMode) {
76        unsafe {
77            ffi::ext_stress_solver_add_force(
78                self.handle,
79                node_index,
80                &position,
81                &force,
82                mode as u32,
83            )
84        }
85    }
86
87    /// Apply gravity to all actors.
88    pub fn add_gravity(&mut self, gravity: Vec3) {
89        unsafe { ffi::ext_stress_solver_add_gravity(self.handle, &gravity) }
90    }
91
92    /// Apply gravity to a specific actor.
93    pub fn add_actor_gravity(&mut self, actor_index: u32, gravity: Vec3) -> bool {
94        unsafe { ffi::ext_stress_solver_add_actor_gravity(self.handle, actor_index, &gravity) != 0 }
95    }
96
97    /// Run one solver update (computes stresses from accumulated forces).
98    pub fn update(&mut self) {
99        unsafe { ffi::ext_stress_solver_update(self.handle) }
100    }
101
102    /// Number of bonds that exceeded their fatal stress limit after the last `update()`.
103    pub fn overstressed_bond_count(&self) -> u32 {
104        unsafe { ffi::ext_stress_solver_overstressed_bond_count(self.handle) }
105    }
106
107    /// Whether the solver converged in the last `update()`.
108    pub fn converged(&self) -> bool {
109        unsafe { ffi::ext_stress_solver_converged(self.handle) != 0 }
110    }
111
112    /// Linear error residual from the last solve.
113    pub fn linear_error(&self) -> f32 {
114        unsafe { ffi::ext_stress_solver_get_linear_error(self.handle) }
115    }
116
117    /// Angular error residual from the last solve.
118    pub fn angular_error(&self) -> f32 {
119        unsafe { ffi::ext_stress_solver_get_angular_error(self.handle) }
120    }
121
122    /// Number of actors currently in the family.
123    pub fn actor_count(&self) -> u32 {
124        unsafe { ffi::ext_stress_solver_actor_count(self.handle) }
125    }
126
127    /// Total number of graph nodes.
128    pub fn node_count(&self) -> u32 {
129        unsafe { ffi::ext_stress_solver_graph_node_count(self.handle) }
130    }
131
132    /// Total number of bonds.
133    pub fn bond_count(&self) -> u32 {
134        unsafe { ffi::ext_stress_solver_bond_count(self.handle) }
135    }
136
137    /// Collect the current actor table.
138    pub fn actors(&self) -> Vec<Actor> {
139        let actor_count = self.actor_count();
140        if actor_count == 0 {
141            return Vec::new();
142        }
143        let node_count = self.node_count();
144
145        let mut actor_buffer = vec![
146            ffi::FfiExtStressActor {
147                actor_index: u32::MAX,
148                nodes: std::ptr::null(),
149                node_count: 0,
150            };
151            actor_count as usize
152        ];
153        let mut nodes_buffer = vec![0u32; node_count as usize];
154        let mut out_actor_count = 0u32;
155        let mut out_node_count = 0u32;
156
157        unsafe {
158            ffi::ext_stress_solver_collect_actors(
159                self.handle,
160                actor_buffer.as_mut_ptr(),
161                actor_count,
162                nodes_buffer.as_mut_ptr(),
163                node_count,
164                &mut out_actor_count,
165                &mut out_node_count,
166            );
167        }
168
169        let mut result = Vec::with_capacity(out_actor_count as usize);
170        for i in 0..out_actor_count as usize {
171            let ffi_actor = &actor_buffer[i];
172            let nodes = if !ffi_actor.nodes.is_null() && ffi_actor.node_count > 0 {
173                let offset = unsafe { ffi_actor.nodes.offset_from(nodes_buffer.as_ptr()) } as usize;
174                nodes_buffer[offset..offset + ffi_actor.node_count as usize].to_vec()
175            } else {
176                Vec::new()
177            };
178            result.push(Actor {
179                actor_index: ffi_actor.actor_index,
180                nodes,
181            });
182        }
183        result
184    }
185
186    /// Generate fracture commands for all actors with overstressed bonds.
187    pub fn generate_fracture_commands(&self) -> Vec<FractureCommand> {
188        let actor_count = self.actor_count();
189        let bond_count = self.bond_count();
190        if actor_count == 0 || bond_count == 0 {
191            return Vec::new();
192        }
193
194        let mut command_buffer = vec![
195            ffi::FfiExtStressFractureCommands {
196                actor_index: u32::MAX,
197                bond_fractures: std::ptr::null_mut(),
198                bond_fracture_count: 0,
199            };
200            actor_count as usize
201        ];
202        let mut bond_buffer = vec![ffi::FfiExtStressBondFracture::default(); bond_count as usize];
203        let mut out_command_count = 0u32;
204        let mut out_bond_count = 0u32;
205
206        unsafe {
207            ffi::ext_stress_solver_generate_fracture_commands_per_actor(
208                self.handle,
209                command_buffer.as_mut_ptr(),
210                actor_count,
211                bond_buffer.as_mut_ptr(),
212                bond_count,
213                &mut out_command_count,
214                &mut out_bond_count,
215            );
216        }
217
218        let mut result = Vec::with_capacity(out_command_count as usize);
219        for i in 0..out_command_count as usize {
220            let cmd = &command_buffer[i];
221            let fractures = if !cmd.bond_fractures.is_null() && cmd.bond_fracture_count > 0 {
222                let offset =
223                    unsafe { cmd.bond_fractures.offset_from(bond_buffer.as_ptr()) } as usize;
224                bond_buffer[offset..offset + cmd.bond_fracture_count as usize]
225                    .iter()
226                    .map(|f| BondFracture {
227                        userdata: f.userdata,
228                        node_index0: f.node_index0,
229                        node_index1: f.node_index1,
230                        health: f.health,
231                    })
232                    .collect()
233            } else {
234                Vec::new()
235            };
236            result.push(FractureCommand {
237                actor_index: cmd.actor_index,
238                bond_fractures: fractures,
239            });
240        }
241        result
242    }
243
244    /// Apply fracture commands and return split events.
245    pub fn apply_fracture_commands(&mut self, commands: &[FractureCommand]) -> Vec<SplitEvent> {
246        if commands.is_empty() {
247            return Vec::new();
248        }
249
250        // Flatten bond fractures into a contiguous buffer
251        let total_bonds: usize = commands.iter().map(|c| c.bond_fractures.len()).sum();
252        let mut flat_bonds = vec![ffi::FfiExtStressBondFracture::default(); total_bonds];
253        let mut ffi_commands = Vec::with_capacity(commands.len());
254        let mut offset = 0usize;
255
256        for cmd in commands {
257            for (i, f) in cmd.bond_fractures.iter().enumerate() {
258                flat_bonds[offset + i] = ffi::FfiExtStressBondFracture {
259                    userdata: f.userdata,
260                    node_index0: f.node_index0,
261                    node_index1: f.node_index1,
262                    health: f.health,
263                };
264            }
265            ffi_commands.push(ffi::FfiExtStressFractureCommands {
266                actor_index: cmd.actor_index,
267                bond_fractures: if cmd.bond_fractures.is_empty() {
268                    std::ptr::null_mut()
269                } else {
270                    unsafe { flat_bonds.as_mut_ptr().add(offset) }
271                },
272                bond_fracture_count: cmd.bond_fractures.len() as u32,
273            });
274            offset += cmd.bond_fractures.len();
275        }
276
277        // Allocate output buffers
278        let node_count = self.node_count() as usize;
279        let max_events = commands.len();
280        let max_children = node_count;
281
282        let mut events_buffer = vec![
283            ffi::FfiExtStressSplitEvent {
284                parent_actor_index: u32::MAX,
285                children: std::ptr::null_mut(),
286                child_count: 0,
287            };
288            max_events
289        ];
290        let mut child_buffer = vec![
291            ffi::FfiExtStressActor {
292                actor_index: u32::MAX,
293                nodes: std::ptr::null(),
294                node_count: 0,
295            };
296            max_children
297        ];
298        let mut nodes_buffer = vec![0u32; node_count];
299        let mut out_event_count = 0u32;
300        let mut out_child_count = 0u32;
301        let mut out_node_count = 0u32;
302
303        unsafe {
304            ffi::ext_stress_solver_apply_fracture_commands(
305                self.handle,
306                ffi_commands.as_ptr(),
307                ffi_commands.len() as u32,
308                events_buffer.as_mut_ptr(),
309                max_events as u32,
310                child_buffer.as_mut_ptr(),
311                max_children as u32,
312                &mut out_event_count,
313                &mut out_child_count,
314                nodes_buffer.as_mut_ptr(),
315                node_count as u32,
316                &mut out_node_count,
317            );
318        }
319
320        // Parse results
321        let mut result = Vec::with_capacity(out_event_count as usize);
322        for i in 0..out_event_count as usize {
323            let evt = &events_buffer[i];
324            let mut children = Vec::with_capacity(evt.child_count as usize);
325
326            if !evt.children.is_null() && evt.child_count > 0 {
327                let child_offset =
328                    unsafe { evt.children.offset_from(child_buffer.as_ptr()) } as usize;
329                for ci in 0..evt.child_count as usize {
330                    let child = &child_buffer[child_offset + ci];
331                    let nodes = if !child.nodes.is_null() && child.node_count > 0 {
332                        let node_off =
333                            unsafe { child.nodes.offset_from(nodes_buffer.as_ptr()) } as usize;
334                        nodes_buffer[node_off..node_off + child.node_count as usize].to_vec()
335                    } else {
336                        Vec::new()
337                    };
338                    children.push(SplitChild {
339                        actor_index: child.actor_index,
340                        nodes,
341                    });
342                }
343            }
344
345            result.push(SplitEvent {
346                parent_actor_index: evt.parent_actor_index,
347                children,
348            });
349        }
350        result
351    }
352
353    /// Get excess forces for an actor that separated from the structure.
354    /// Returns `(force, torque)` if the actor exists.
355    pub fn get_excess_forces(&self, actor_index: u32, com: Vec3) -> Option<(Vec3, Vec3)> {
356        let mut force = Vec3::ZERO;
357        let mut torque = Vec3::ZERO;
358        let ok = unsafe {
359            ffi::ext_stress_solver_get_excess_forces(
360                self.handle,
361                actor_index,
362                &com,
363                &mut force,
364                &mut torque,
365            )
366        };
367        if ok != 0 {
368            Some((force, torque))
369        } else {
370            None
371        }
372    }
373}
374
375impl Drop for ExtStressSolver {
376    fn drop(&mut self) {
377        unsafe { ffi::ext_stress_solver_destroy(self.handle) }
378    }
379}
380
381fn to_ffi_settings(s: &SolverSettings) -> ffi::FfiExtStressSolverSettingsDesc {
382    ffi::FfiExtStressSolverSettingsDesc {
383        max_solver_iterations_per_frame: s.max_solver_iterations_per_frame,
384        graph_reduction_level: s.graph_reduction_level,
385        compression_elastic_limit: s.compression_elastic_limit,
386        compression_fatal_limit: s.compression_fatal_limit,
387        tension_elastic_limit: s.tension_elastic_limit,
388        tension_fatal_limit: s.tension_fatal_limit,
389        shear_elastic_limit: s.shear_elastic_limit,
390        shear_fatal_limit: s.shear_fatal_limit,
391    }
392}