Skip to main content

rapier3d/dynamics/soft_body/tearing/
tearing_validate.rs

1//! Consistency checks of a soft body topology and its derived tables after a tear.
2
3use crate::alloc_prelude::*;
4#[cfg(feature = "dim3")]
5use crate::math::DIM;
6use alloc::format;
7use parry::utils::hashmap::HashMap;
8
9use super::super::SoftBody;
10use super::super::soft_body::SOFT_BODY_OVERFLOW_COLOR;
11use super::super::soft_body_builder::element_vertices;
12#[cfg(feature = "dim3")]
13use super::tearing::pair;
14
15impl SoftBody {
16    /// Checks the topology and its derived tables (element indices, every particle in a measure
17    /// element (see [`Self::rest_measure`]), surface rings and incidence, owning cells, the 3D edge
18    /// table, the coloring) for consistency; returns the first inconsistency found.
19    pub fn validate_topology(&self) -> Result<(), String> {
20        let n = self.particles.len() as u32;
21        let check = |what: &str, vertices: &[u32]| -> Result<(), String> {
22            for &v in vertices {
23                if v >= n {
24                    return Err(format!("{what} references particle {v} of {n}"));
25                }
26            }
27            Ok(())
28        };
29        for (i, e) in self.edges.iter().enumerate() {
30            check(&format!("edge {i}"), &e.vertices)?;
31            if e.vertices[0] == e.vertices[1] {
32                return Err(format!("edge {i} is degenerate"));
33            }
34        }
35        for (i, c) in self.cells.iter().enumerate() {
36            check(&format!("cell {i}"), &c.vertices)?;
37        }
38        #[cfg(feature = "dim3")]
39        for (i, d) in self.dihedrals.iter().enumerate() {
40            check(&format!("dihedral {i}"), &d.vertices)?;
41        }
42        for (i, s) in self.boundary.iter().enumerate() {
43            check(&format!("surface element {i}"), s)?;
44        }
45        // Every particle belongs to some measure element (a tear never removes one), unless the
46        // body has none.
47        if let Some(kind) = self.measure_kind() {
48            let mut supported = vec![false; n as usize];
49            self.for_each_measure_element(kind, |_, vertices| {
50                for &v in vertices {
51                    if let Some(s) = supported.get_mut(v as usize) {
52                        *s = true;
53                    }
54                }
55            });
56            if let Some(v) = supported.iter().position(|s| !s) {
57                return Err(format!("particle {v} belongs to no measure element"));
58            }
59        }
60        for (mi, mesh) in self.meshes().enumerate() {
61            let nv = mesh.vertex_count();
62            if let super::collision_mesh::SoftMeshMapping::Skinned { bindings } = mesh.binding() {
63                if bindings.len() != nv {
64                    return Err(format!(
65                        "mesh {mi} has {} bindings for {nv} vertices",
66                        bindings.len()
67                    ));
68                }
69                // A body left without any cell has nothing to bind to.
70                let dead = |b: &super::collision_mesh::SoftMeshCellBinding| {
71                    !self.cells.is_empty() && b.cell as usize >= self.cells.len()
72                };
73                if let Some(v) = bindings.iter().position(dead) {
74                    return Err(format!("mesh {mi} vertex {v} is bound to a dead cell"));
75                }
76            }
77            for (i, s) in mesh.indices().iter().enumerate() {
78                for &v in element_vertices(s) {
79                    if v as usize >= nv {
80                        return Err(format!("mesh {mi} element {i} has an out-of-range vertex"));
81                    }
82                }
83            }
84            if mesh.ring_offsets.len() != nv + 1 {
85                return Err(format!("mesh {mi} ring offsets do not cover every vertex"));
86            }
87            if mesh.vertex_elements_offsets.len() != nv + 1 {
88                return Err(format!(
89                    "mesh {mi} vertex-element offsets do not cover every vertex"
90                ));
91            }
92            for (i, s) in mesh.indices().iter().enumerate() {
93                let s = element_vertices(s);
94                for &v in s {
95                    let (start, end) = (
96                        mesh.vertex_elements_offsets[v as usize] as usize,
97                        mesh.vertex_elements_offsets[v as usize + 1] as usize,
98                    );
99                    if !mesh.vertex_elements[start..end].contains(&(i as u32)) {
100                        return Err(format!(
101                            "mesh {mi} element {i} missing from vertex {v}'s list"
102                        ));
103                    }
104                    let (start, end) = (
105                        mesh.ring_offsets[v as usize] as usize,
106                        mesh.ring_offsets[v as usize + 1] as usize,
107                    );
108                    let ring = &mesh.ring[start..end];
109                    for &w in s.iter().filter(|&&w| w != v) {
110                        if ring.binary_search(&w).is_err() {
111                            return Err(format!("mesh {mi}: vertex {w} missing from {v}'s ring"));
112                        }
113                    }
114                }
115            }
116            for (v, window) in mesh.vertex_elements_offsets.windows(2).enumerate() {
117                for &e in &mesh.vertex_elements[window[0] as usize..window[1] as usize] {
118                    let ok = mesh
119                        .indices()
120                        .get(e as usize)
121                        .is_some_and(|s| s.contains(&(v as u32)));
122                    if !ok {
123                        return Err(format!(
124                            "mesh {mi}: vertex {v} lists element {e} it is not in"
125                        ));
126                    }
127                }
128            }
129            #[cfg(feature = "dim3")]
130            {
131                if mesh.element_edges.len() != mesh.indices().len()
132                    || mesh.edge_owners.len() != mesh.edges.len()
133                {
134                    return Err(format!("mesh {mi} edge tables do not cover its elements"));
135                }
136                for (i, s) in mesh.indices().iter().enumerate() {
137                    // A wire segment (padded with `u32::MAX`) is its own single edge.
138                    if element_vertices(s).len() < DIM {
139                        let id = mesh.element_edges[i][0] as usize;
140                        if mesh.edges.get(id) != Some(&pair(s[0], s[1])) {
141                            return Err(format!("mesh {mi} segment {i} edge mismatch"));
142                        }
143                        continue;
144                    }
145                    for k in 0..DIM {
146                        let id = mesh.element_edges[i][k] as usize;
147                        let expected = pair(s[(k + 1) % DIM], s[(k + 2) % DIM]);
148                        if mesh.edges.get(id) != Some(&expected) {
149                            return Err(format!("mesh {mi} element {i} edge {k} mismatch"));
150                        }
151                    }
152                }
153                for (id, &owner) in mesh.edge_owners.iter().enumerate() {
154                    let ok = mesh
155                        .element_edges
156                        .get(owner as usize)
157                        .is_some_and(|edges| edges.contains(&(id as u32)));
158                    if !ok {
159                        return Err(format!("mesh {mi} edge {id} owner mismatch"));
160                    }
161                }
162            }
163        }
164        if !self.cells.is_empty() {
165            if self.boundary_element_cells.len() != self.boundary.len() {
166                return Err("boundary element owners do not cover the boundary".into());
167            }
168            for (i, &c) in self.boundary_element_cells.iter().enumerate() {
169                if c == u32::MAX {
170                    continue;
171                }
172                let ok = self
173                    .cells
174                    .get(c as usize)
175                    .is_some_and(|cell| self.boundary[i].iter().all(|v| cell.vertices.contains(v)));
176                if !ok {
177                    return Err(format!(
178                        "boundary element {i} owned by a cell not containing it"
179                    ));
180                }
181            }
182        }
183        // Coloring: elements of one parallel color share no particle.
184        let mut seen: HashMap<(u8, u32), ()> = HashMap::default();
185        let mut check_color = |color: u8, vertices: &[u32]| -> Result<(), String> {
186            if color == SOFT_BODY_OVERFLOW_COLOR {
187                return Ok(());
188            }
189            if color >= self.num_colors {
190                return Err(format!(
191                    "color {color} beyond num_colors {}",
192                    self.num_colors
193                ));
194            }
195            for &v in vertices {
196                if seen.insert((color, v), ()).is_some() {
197                    return Err(format!(
198                        "particle {v} shared by two elements of color {color}"
199                    ));
200                }
201            }
202            Ok(())
203        };
204        for e in &self.edges {
205            check_color(e.color, &e.vertices)?;
206        }
207        for c in &self.cells {
208            check_color(c.color, &c.vertices)?;
209        }
210        #[cfg(feature = "dim3")]
211        for d in &self.dihedrals {
212            check_color(d.color, &d.vertices)?;
213        }
214        Ok(())
215    }
216}