Skip to main content

oxiphysics_geometry/mesh_repair/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{HashMap, HashSet};
6
7use super::functions::{
8    fill_hole_ear_clipping, find_boundary_loops, fix_non_manifold_edges, merge_duplicate_vertices,
9};
10
11/// A single directed half-edge in a half-edge mesh.
12pub struct HalfEdge {
13    /// Index of the vertex this half-edge points *to*.
14    pub vertex: usize,
15    /// Index of the face this half-edge belongs to.
16    pub face: usize,
17    /// Index of the next half-edge in the same face (CCW).
18    pub next: usize,
19    /// Index of the previous half-edge in the same face.
20    pub prev: usize,
21    /// Index of the opposite (twin) half-edge, if any.
22    pub twin: Option<usize>,
23}
24/// Half-edge mesh data structure for topology queries.
25pub struct HalfEdgeMesh {
26    /// Vertex positions.
27    pub vertices: Vec<[f64; 3]>,
28    /// All half-edges in the mesh.
29    pub half_edges: Vec<HalfEdge>,
30    /// For each face, the index of its first half-edge.
31    pub face_starts: Vec<usize>,
32}
33impl HalfEdgeMesh {
34    /// Build a half-edge mesh from a triangle soup.
35    pub fn from_triangle_soup(vertices: Vec<[f64; 3]>, triangles: Vec<[usize; 3]>) -> Self {
36        let mut half_edges: Vec<HalfEdge> = Vec::new();
37        let mut face_starts: Vec<usize> = Vec::new();
38        let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
39        for (face_idx, tri) in triangles.iter().enumerate() {
40            let base = half_edges.len();
41            face_starts.push(base);
42            for k in 0..3 {
43                let to_v = tri[(k + 1) % 3];
44                half_edges.push(HalfEdge {
45                    vertex: to_v,
46                    face: face_idx,
47                    next: base + (k + 1) % 3,
48                    prev: base + (k + 2) % 3,
49                    twin: None,
50                });
51                edge_map.insert((tri[k], to_v), base + k);
52            }
53        }
54        let n = half_edges.len();
55        for i in 0..n {
56            if half_edges[i].twin.is_none() {
57                let prev_idx = half_edges[i].prev;
58                let from_v = half_edges[prev_idx].vertex;
59                let to_v = half_edges[i].vertex;
60                if let Some(&twin_idx) = edge_map.get(&(to_v, from_v)) {
61                    half_edges[i].twin = Some(twin_idx);
62                    half_edges[twin_idx].twin = Some(i);
63                }
64            }
65        }
66        HalfEdgeMesh {
67            vertices,
68            half_edges,
69            face_starts,
70        }
71    }
72    /// Returns `true` if the mesh is manifold.
73    pub fn is_manifold(&self) -> bool {
74        let mut seen: HashMap<(usize, usize), usize> = HashMap::new();
75        for (i, he) in self.half_edges.iter().enumerate() {
76            let prev = &self.half_edges[he.prev];
77            let from_v = prev.vertex;
78            let to_v = he.vertex;
79            let count = seen.entry((from_v, to_v)).or_insert(0);
80            *count += 1;
81            if *count > 1 {
82                let _ = i;
83                return false;
84            }
85        }
86        true
87    }
88    /// Returns the indices of all boundary half-edges (those with no twin).
89    pub fn boundary_edges(&self) -> Vec<usize> {
90        self.half_edges
91            .iter()
92            .enumerate()
93            .filter_map(|(i, he)| if he.twin.is_none() { Some(i) } else { None })
94            .collect()
95    }
96    /// Returns the valence (number of incident edges) of vertex `v`.
97    pub fn vertex_valence(&self, v: usize) -> usize {
98        self.half_edges.iter().filter(|he| he.vertex == v).count()
99    }
100    /// Returns the number of faces in the mesh.
101    pub fn face_count(&self) -> usize {
102        self.face_starts.len()
103    }
104    /// Returns the number of vertices in the mesh.
105    pub fn vertex_count(&self) -> usize {
106        self.vertices.len()
107    }
108    /// Collect boundary loops: returns a list of loops, each loop is a list
109    /// of vertex indices forming a closed boundary.
110    pub fn boundary_loops(&self) -> Vec<Vec<usize>> {
111        let boundary_hes = self.boundary_edges();
112        if boundary_hes.is_empty() {
113            return Vec::new();
114        }
115        let mut from_to_he: HashMap<usize, usize> = HashMap::new();
116        for &he_idx in &boundary_hes {
117            let prev_idx = self.half_edges[he_idx].prev;
118            let from_v = self.half_edges[prev_idx].vertex;
119            from_to_he.insert(from_v, he_idx);
120        }
121        let mut visited: HashSet<usize> = HashSet::new();
122        let mut loops = Vec::new();
123        for &start_he in &boundary_hes {
124            if visited.contains(&start_he) {
125                continue;
126            }
127            let mut loop_verts = Vec::new();
128            let mut current = start_he;
129            loop {
130                visited.insert(current);
131                let to_v = self.half_edges[current].vertex;
132                loop_verts.push(to_v);
133                match from_to_he.get(&to_v) {
134                    Some(&next_he) if !visited.contains(&next_he) => {
135                        current = next_he;
136                    }
137                    _ => break,
138                }
139            }
140            if loop_verts.len() >= 3 {
141                loops.push(loop_verts);
142            }
143        }
144        loops
145    }
146}
147/// Summary report from a mesh repair pass.
148pub struct MeshRepairReport {
149    /// Number of degenerate triangles removed.
150    pub n_degenerate_removed: usize,
151    /// Number of holes filled.
152    pub n_holes_filled: usize,
153    /// Number of duplicate vertices merged.
154    pub n_duplicates_merged: usize,
155    /// Number of face normals flipped for consistency.
156    pub n_normals_fixed: usize,
157}
158/// A simple triangle mesh with an adjacency structure that supports edge flips.
159///
160/// Useful for converting a mesh towards a Delaunay triangulation by flipping
161/// edges whose opposite angles violate the Delaunay in-circle criterion.
162pub struct FlipMesh {
163    /// Vertex positions.
164    pub vertices: Vec<[f64; 3]>,
165    /// Triangle list; each triangle stores 3 vertex indices.
166    pub triangles: Vec<[usize; 3]>,
167    /// Number of edge-flips performed in the last call to `flip_to_delaunay`.
168    pub n_flips: usize,
169}
170impl FlipMesh {
171    /// Create a new FlipMesh from existing vertices and triangles.
172    pub fn new(vertices: Vec<[f64; 3]>, triangles: Vec<[usize; 3]>) -> Self {
173        Self {
174            vertices,
175            triangles,
176            n_flips: 0,
177        }
178    }
179    /// Check if the shared edge between two triangles satisfies the Delaunay
180    /// in-circle criterion (i.e., neither opposite vertex lies inside the other
181    /// triangle's circumcircle). Returns `true` if the edge is locally Delaunay.
182    ///
183    /// The two triangles share vertices `a` and `b`; the opposite vertices are
184    /// `c` (triangle 0) and `d` (triangle 1).
185    pub fn is_locally_delaunay(va: [f64; 3], vb: [f64; 3], vc: [f64; 3], vd: [f64; 3]) -> bool {
186        let ax = va[0] - vd[0];
187        let ay = va[1] - vd[1];
188        let bx = vb[0] - vd[0];
189        let by = vb[1] - vd[1];
190        let cx = vc[0] - vd[0];
191        let cy = vc[1] - vd[1];
192        let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
193            - ay * (bx * (cx * cx + cy * cy) - cx * (bx * bx + by * by))
194            + (ax * ax + ay * ay) * (bx * cy - by * cx);
195        det <= 0.0
196    }
197    /// Attempt one pass of edge-flipping towards Delaunay. Returns the number of
198    /// edges flipped. Repeat until 0 to fully Delaunay-ize a planar mesh.
199    pub fn flip_pass(&mut self) -> usize {
200        let n = self.triangles.len();
201        let mut edge_map: std::collections::HashMap<(usize, usize), Vec<usize>> =
202            std::collections::HashMap::new();
203        for (ti, tri) in self.triangles.iter().enumerate() {
204            for k in 0..3 {
205                let a = tri[k];
206                let b = tri[(k + 1) % 3];
207                let key = if a < b { (a, b) } else { (b, a) };
208                edge_map.entry(key).or_default().push(ti);
209            }
210        }
211        let mut flips = 0usize;
212        let candidates: Vec<((usize, usize), usize, usize)> = edge_map
213            .iter()
214            .filter_map(|(&key, tris)| {
215                if tris.len() == 2 {
216                    Some((key, tris[0], tris[1]))
217                } else {
218                    None
219                }
220            })
221            .collect();
222        for (edge_key, t0, t1) in candidates {
223            if t0 >= n || t1 >= n {
224                continue;
225            }
226            let tri0 = self.triangles[t0];
227            let tri1 = self.triangles[t1];
228            let (ea, eb) = edge_key;
229            let opp0 = tri0.iter().copied().find(|&v| v != ea && v != eb);
230            let opp1 = tri1.iter().copied().find(|&v| v != ea && v != eb);
231            let (c, d) = match (opp0, opp1) {
232                (Some(c), Some(d)) => (c, d),
233                _ => continue,
234            };
235            let va = self.vertices[ea];
236            let vb = self.vertices[eb];
237            let vc = self.vertices[c];
238            let vd = self.vertices[d];
239            if !Self::is_locally_delaunay(va, vb, vc, vd) {
240                self.triangles[t0] = [ea, c, d];
241                self.triangles[t1] = [eb, d, c];
242                flips += 1;
243            }
244        }
245        self.n_flips += flips;
246        flips
247    }
248    /// Run up to `max_passes` flip passes until no more flips occur.
249    pub fn flip_to_delaunay(&mut self, max_passes: usize) {
250        for _ in 0..max_passes {
251            if self.flip_pass() == 0 {
252                break;
253            }
254        }
255    }
256}
257/// Compute mesh statistics: vertex count, triangle count, edge count,
258/// boundary edge count, non-manifold edge count, genus estimate.
259pub struct MeshStats {
260    /// Number of vertices.
261    pub n_vertices: usize,
262    /// Number of triangles.
263    pub n_triangles: usize,
264    /// Number of unique edges.
265    pub n_edges: usize,
266    /// Number of boundary edges.
267    pub n_boundary_edges: usize,
268    /// Number of non-manifold edges.
269    pub n_non_manifold_edges: usize,
270    /// Euler characteristic (V - E + F).
271    pub euler_characteristic: i64,
272}
273/// A stateful mesh repair helper that owns the vertex and triangle lists.
274///
275/// Provides higher-level repair operations beyond the standalone functions:
276/// - `fill_holes` – fan-triangulate every boundary loop
277/// - `remove_duplicate_vertices` – merge vertices within epsilon
278/// - `fix_non_manifold_edges` – remove excess triangles until manifold
279/// - `compute_euler_characteristic` – V − E + F
280pub struct MeshRepair {
281    /// Vertex positions.
282    pub vertices: Vec<[f64; 3]>,
283    /// Triangle index triples.
284    pub triangles: Vec<[usize; 3]>,
285}
286impl MeshRepair {
287    /// Create a new `MeshRepair` from existing geometry.
288    pub fn new(vertices: Vec<[f64; 3]>, triangles: Vec<[usize; 3]>) -> Self {
289        Self {
290            vertices,
291            triangles,
292        }
293    }
294    /// Fill all open boundary holes using fan triangulation.
295    ///
296    /// For each boundary loop a "fan" of triangles is built from the first
297    /// vertex of the loop to every subsequent edge.  This is faster than
298    /// ear-clipping and always produces valid triangles for convex loops.
299    ///
300    /// Returns the number of holes filled.
301    pub fn fill_holes(&mut self) -> usize {
302        let loops = find_boundary_loops(&self.triangles);
303        let n_holes = loops.len();
304        for lp in &loops {
305            if lp.len() < 3 {
306                continue;
307            }
308            let hub = lp[0];
309            for i in 1..(lp.len() - 1) {
310                self.triangles.push([hub, lp[i], lp[i + 1]]);
311            }
312        }
313        n_holes
314    }
315    /// Fill all open boundary holes using ear-clipping (higher quality).
316    ///
317    /// Returns the number of holes filled.
318    pub fn fill_holes_ear(&mut self) -> usize {
319        let loops = find_boundary_loops(&self.triangles);
320        let n_holes = loops.len();
321        for lp in &loops {
322            let new_tris = fill_hole_ear_clipping(&self.vertices, lp);
323            self.triangles.extend_from_slice(&new_tris);
324        }
325        n_holes
326    }
327    /// Merge all vertex pairs closer than `epsilon` and compact the vertex
328    /// array.  Triangle references are remapped accordingly.
329    ///
330    /// Returns the number of vertices that were merged (eliminated).
331    pub fn remove_duplicate_vertices(&mut self, epsilon: f64) -> usize {
332        let (new_v, new_t, n_merged) =
333            merge_duplicate_vertices(&self.vertices, &self.triangles, epsilon);
334        self.vertices = new_v;
335        self.triangles = new_t;
336        n_merged
337    }
338    /// Remove triangles until every edge is shared by at most 2 faces.
339    ///
340    /// The greedy strategy keeps the first two triangles seen for any edge;
341    /// later ones are discarded.  Returns the number of triangles removed.
342    pub fn fix_non_manifold_edges(&mut self) -> usize {
343        let before = self.triangles.len();
344        self.triangles = fix_non_manifold_edges(&self.triangles);
345        before - self.triangles.len()
346    }
347    /// Compute the Euler characteristic χ = V − E + F.
348    ///
349    /// For a closed orientable surface of genus g: χ = 2 − 2g.
350    /// A sphere has χ = 2; a torus has χ = 0.
351    pub fn compute_euler_characteristic(&self) -> i32 {
352        let v = self.vertices.len();
353        let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
354        for tri in &self.triangles {
355            for k in 0..3 {
356                let a = tri[k];
357                let b = tri[(k + 1) % 3];
358                let key = if a < b { (a, b) } else { (b, a) };
359                edge_set.insert(key);
360            }
361        }
362        let e = edge_set.len();
363        let f = self.triangles.len();
364        v as i32 - e as i32 + f as i32
365    }
366}