Skip to main content

oxiphysics_geometry/mesh_simplification/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::{HashMap, HashSet};
7
8/// Half-edge mesh topology.
9#[derive(Debug, Clone)]
10pub struct HalfEdgeMesh {
11    /// Vertex positions.
12    pub vertices: Vec<[f64; 3]>,
13    /// Half-edge array.
14    pub half_edges: Vec<HalfEdge>,
15    /// For each vertex, one outgoing half-edge index.
16    pub vertex_half_edge: Vec<usize>,
17    /// For each face, one half-edge index.
18    pub face_half_edge: Vec<usize>,
19    /// Marks deleted vertices.
20    pub vertex_deleted: Vec<bool>,
21    /// Marks deleted faces.
22    pub face_deleted: Vec<bool>,
23    /// Marks deleted half-edges.
24    pub he_deleted: Vec<bool>,
25}
26impl HalfEdgeMesh {
27    /// Build a half-edge mesh from vertices and triangle indices.
28    pub fn from_triangles(vertices: Vec<[f64; 3]>, triangles: &[[usize; 3]]) -> Self {
29        let n_verts = vertices.len();
30        let n_faces = triangles.len();
31        let n_he = n_faces * 3;
32        let mut half_edges = Vec::with_capacity(n_he);
33        let mut vertex_half_edge = vec![usize::MAX; n_verts];
34        let mut face_half_edge = Vec::with_capacity(n_faces);
35        let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
36        for (fi, tri) in triangles.iter().enumerate() {
37            let base = half_edges.len();
38            face_half_edge.push(base);
39            for k in 0..3 {
40                let v_from = tri[k];
41                let v_to = tri[(k + 1) % 3];
42                let he_idx = base + k;
43                half_edges.push(HalfEdge {
44                    vertex: v_to,
45                    face: fi,
46                    twin: usize::MAX,
47                    next: base + (k + 1) % 3,
48                    prev: base + (k + 2) % 3,
49                });
50                if vertex_half_edge[v_from] == usize::MAX {
51                    vertex_half_edge[v_from] = he_idx;
52                }
53                edge_map.insert((v_from, v_to), he_idx);
54            }
55        }
56        let keys: Vec<(usize, usize)> = edge_map.keys().copied().collect();
57        for (v_from, v_to) in keys {
58            if let Some(&twin_idx) = edge_map.get(&(v_to, v_from)) {
59                let he_idx = edge_map[&(v_from, v_to)];
60                half_edges[he_idx].twin = twin_idx;
61                half_edges[twin_idx].twin = he_idx;
62            }
63        }
64        let vertex_deleted = vec![false; n_verts];
65        let face_deleted = vec![false; n_faces];
66        let he_deleted = vec![false; n_he];
67        Self {
68            vertices,
69            half_edges,
70            vertex_half_edge,
71            face_half_edge,
72            vertex_deleted,
73            face_deleted,
74            he_deleted,
75        }
76    }
77    /// Number of active (non-deleted) vertices.
78    pub fn active_vertex_count(&self) -> usize {
79        self.vertex_deleted.iter().filter(|&&d| !d).count()
80    }
81    /// Number of active faces.
82    pub fn active_face_count(&self) -> usize {
83        self.face_deleted.iter().filter(|&&d| !d).count()
84    }
85    /// Check if a half-edge is on the boundary (no twin).
86    pub fn is_boundary_half_edge(&self, he_idx: usize) -> bool {
87        self.half_edges[he_idx].twin == usize::MAX
88    }
89    /// Check if a vertex is on the boundary.
90    pub fn is_boundary_vertex(&self, v: usize) -> bool {
91        let start = self.vertex_half_edge[v];
92        if start == usize::MAX {
93            return false;
94        }
95        let mut he = start;
96        loop {
97            if self.is_boundary_half_edge(he) {
98                return true;
99            }
100            let twin = self.half_edges[he].twin;
101            if twin == usize::MAX {
102                return true;
103            }
104            he = self.half_edges[twin].next;
105            if he == start {
106                break;
107            }
108        }
109        false
110    }
111    /// Get all faces adjacent to a vertex.
112    pub fn vertex_faces(&self, v: usize) -> Vec<usize> {
113        let mut faces = Vec::new();
114        let start = self.vertex_half_edge[v];
115        if start == usize::MAX {
116            return faces;
117        }
118        let mut he = start;
119        loop {
120            let f = self.half_edges[he].face;
121            if f != usize::MAX && !self.face_deleted[f] {
122                faces.push(f);
123            }
124            let twin = self.half_edges[he].twin;
125            if twin == usize::MAX {
126                break;
127            }
128            he = self.half_edges[twin].next;
129            if he == start {
130                break;
131            }
132        }
133        faces
134    }
135    /// Get one-ring neighbor vertices of a vertex.
136    pub fn vertex_neighbors(&self, v: usize) -> Vec<usize> {
137        let mut neighbors = Vec::new();
138        let start = self.vertex_half_edge[v];
139        if start == usize::MAX {
140            return neighbors;
141        }
142        let mut he = start;
143        loop {
144            let target = self.half_edges[he].vertex;
145            if !self.vertex_deleted[target] {
146                neighbors.push(target);
147            }
148            let twin = self.half_edges[he].twin;
149            if twin == usize::MAX {
150                break;
151            }
152            he = self.half_edges[twin].next;
153            if he == start {
154                break;
155            }
156        }
157        neighbors
158    }
159    /// Compute the face normal for a given face index.
160    pub fn face_normal(&self, fi: usize) -> [f64; 3] {
161        let he0 = self.face_half_edge[fi];
162        let he1 = self.half_edges[he0].next;
163        let he2 = self.half_edges[he1].next;
164        let v0_idx = self.half_edges[self.half_edges[he0].prev].vertex;
165        let v1_idx = self.half_edges[he0].vertex;
166        let v2_idx = self.half_edges[he1].vertex;
167        let _ = v2_idx;
168        let _ = he2;
169        let va = self.vertices[v0_idx];
170        let vb = self.vertices[v1_idx];
171        let vc = self.vertices[self.half_edges[he1].vertex];
172        let ab = sub3(vb, va);
173        let ac = sub3(vc, va);
174        normalize3(cross3(ab, ac))
175    }
176    /// Compute the area of a face.
177    pub fn face_area(&self, fi: usize) -> f64 {
178        let he0 = self.face_half_edge[fi];
179        let he1 = self.half_edges[he0].next;
180        let v0_idx = self.half_edges[self.half_edges[he0].prev].vertex;
181        let v1_idx = self.half_edges[he0].vertex;
182        let v2_idx = self.half_edges[he1].vertex;
183        let va = self.vertices[v0_idx];
184        let vb = self.vertices[v1_idx];
185        let vc = self.vertices[v2_idx];
186        let ab = sub3(vb, va);
187        let ac = sub3(vc, va);
188        0.5 * len3(cross3(ab, ac))
189    }
190    /// Extract active triangles as index triples.
191    pub fn extract_triangles(&self) -> Vec<[usize; 3]> {
192        let mut tris = Vec::new();
193        for (fi, &he0) in self.face_half_edge.iter().enumerate() {
194            if self.face_deleted[fi] {
195                continue;
196            }
197            let he1 = self.half_edges[he0].next;
198            let he2 = self.half_edges[he1].next;
199            let v0 = self.half_edges[he2].vertex;
200            let v1 = self.half_edges[he0].vertex;
201            let v2 = self.half_edges[he1].vertex;
202            tris.push([v0, v1, v2]);
203        }
204        tris
205    }
206}
207/// Mesh simplification statistics.
208#[derive(Debug, Clone)]
209pub struct MeshStats {
210    /// Number of vertices.
211    pub n_vertices: usize,
212    /// Number of triangles.
213    pub n_triangles: usize,
214    /// Number of edges.
215    pub n_edges: usize,
216    /// Average edge length.
217    pub avg_edge_length: f64,
218    /// Minimum edge length.
219    pub min_edge_length: f64,
220    /// Maximum edge length.
221    pub max_edge_length: f64,
222    /// Average triangle quality.
223    pub avg_quality: f64,
224    /// Minimum triangle quality.
225    pub min_quality: f64,
226}
227/// Configuration for QEM mesh simplification.
228#[derive(Debug, Clone)]
229pub struct QemConfig {
230    /// Target number of triangles (0 = use error threshold).
231    pub target_triangles: usize,
232    /// Maximum allowed error (0.0 = use triangle target).
233    pub max_error: f64,
234    /// Whether to preserve boundary edges.
235    pub preserve_boundary: bool,
236    /// Maximum allowed normal deviation (radians, 0.0 = no constraint).
237    pub max_normal_deviation: f64,
238    /// Whether to preserve texture seams.
239    pub preserve_texture_seams: bool,
240    /// Boundary penalty weight.
241    pub boundary_weight: f64,
242}
243impl QemConfig {
244    /// Default configuration targeting 50% reduction.
245    pub fn default_config(current_tris: usize) -> Self {
246        Self {
247            target_triangles: current_tris / 2,
248            max_error: 0.0,
249            preserve_boundary: true,
250            max_normal_deviation: std::f64::consts::FRAC_PI_4,
251            preserve_texture_seams: false,
252            boundary_weight: 100.0,
253        }
254    }
255    /// Configuration with error threshold stopping criterion.
256    pub fn with_error_threshold(max_error: f64) -> Self {
257        Self {
258            target_triangles: 0,
259            max_error,
260            preserve_boundary: true,
261            max_normal_deviation: 0.0,
262            preserve_texture_seams: false,
263            boundary_weight: 100.0,
264        }
265    }
266}
267/// Record of a single edge collapse for progressive mesh.
268#[derive(Debug, Clone)]
269pub struct CollapseRecord {
270    /// Vertex that was removed (collapsed into `kept`).
271    pub removed: usize,
272    /// Vertex that was kept.
273    pub kept: usize,
274    /// Original position of the removed vertex.
275    pub removed_pos: [f64; 3],
276    /// Original position of the kept vertex.
277    pub kept_pos: [f64; 3],
278    /// Position after collapse.
279    pub collapsed_pos: [f64; 3],
280    /// Triangles removed by this collapse.
281    pub removed_triangles: Vec<[usize; 3]>,
282    /// Triangles modified by this collapse (new versions).
283    pub modified_triangles: Vec<[usize; 3]>,
284}
285/// Progressive mesh: stores a sequence of collapses for LOD.
286#[derive(Debug, Clone)]
287pub struct ProgressiveMesh {
288    /// Base (most simplified) vertices.
289    pub base_vertices: Vec<[f64; 3]>,
290    /// Base (most simplified) triangles.
291    pub base_triangles: Vec<[usize; 3]>,
292    /// Collapse records in order of execution (last = first to undo).
293    pub collapses: Vec<CollapseRecord>,
294}
295impl ProgressiveMesh {
296    /// Create a progressive mesh by simplifying with QEM and recording collapses.
297    pub fn build(vertices: &[[f64; 3]], triangles: &[[usize; 3]], target_triangles: usize) -> Self {
298        let config = QemConfig {
299            target_triangles,
300            max_error: 0.0,
301            preserve_boundary: true,
302            max_normal_deviation: 0.0,
303            preserve_texture_seams: false,
304            boundary_weight: 100.0,
305        };
306        let (base_verts, base_tris) = qem_simplify(vertices, triangles, &config);
307        Self {
308            base_vertices: base_verts,
309            base_triangles: base_tris,
310            collapses: Vec::new(),
311        }
312    }
313    /// Get the mesh at the base (lowest) level of detail.
314    pub fn base_mesh(&self) -> (&[[f64; 3]], &[[usize; 3]]) {
315        (&self.base_vertices, &self.base_triangles)
316    }
317    /// Number of collapse records stored.
318    pub fn num_collapses(&self) -> usize {
319        self.collapses.len()
320    }
321}
322/// A symmetric 4×4 matrix stored as 10 unique elements (upper triangle).
323///
324/// Layout: a00 a01 a02 a03
325///              a11 a12 a13
326///                   a22 a23
327///                        a33
328#[derive(Debug, Clone, Copy)]
329pub struct SymMat4 {
330    /// Elements stored in row-major upper-triangle order.
331    pub m: [f64; 10],
332}
333impl SymMat4 {
334    /// Zero matrix.
335    pub fn zero() -> Self {
336        Self { m: [0.0; 10] }
337    }
338    /// Build quadric matrix from plane equation ax + by + cz + d = 0.
339    pub fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Self {
340        Self {
341            m: [
342                a * a,
343                a * b,
344                a * c,
345                a * d,
346                b * b,
347                b * c,
348                b * d,
349                c * c,
350                c * d,
351                d * d,
352            ],
353        }
354    }
355    /// Add two symmetric matrices.
356    pub fn add(&self, other: &Self) -> Self {
357        let mut result = Self::zero();
358        for i in 0..10 {
359            result.m[i] = self.m[i] + other.m[i];
360        }
361        result
362    }
363    /// Scale by a scalar.
364    pub fn scale(&self, s: f64) -> Self {
365        let mut result = *self;
366        for v in &mut result.m {
367            *v *= s;
368        }
369        result
370    }
371    /// Evaluate the quadric error v^T Q v for a 3D point (homogeneous w=1).
372    pub fn evaluate(&self, v: [f64; 3]) -> f64 {
373        let x = v[0];
374        let y = v[1];
375        let z = v[2];
376        self.m[0] * x * x
377            + 2.0 * self.m[1] * x * y
378            + 2.0 * self.m[2] * x * z
379            + 2.0 * self.m[3] * x
380            + self.m[4] * y * y
381            + 2.0 * self.m[5] * y * z
382            + 2.0 * self.m[6] * y
383            + self.m[7] * z * z
384            + 2.0 * self.m[8] * z
385            + self.m[9]
386    }
387    /// Try to find the optimal point that minimizes the quadric error.
388    ///
389    /// Solves the 3×3 linear system from ∂(v^T Q v)/∂v = 0.
390    /// Returns `None` if the system is singular.
391    pub fn optimal_point(&self) -> Option<[f64; 3]> {
392        let a = [
393            [self.m[0], self.m[1], self.m[2]],
394            [self.m[1], self.m[4], self.m[5]],
395            [self.m[2], self.m[5], self.m[7]],
396        ];
397        let b = [-self.m[3], -self.m[6], -self.m[8]];
398        solve_3x3(a, b)
399    }
400}
401/// A half-edge in the mesh topology.
402#[derive(Debug, Clone)]
403pub struct HalfEdge {
404    /// Index of the vertex this half-edge points to.
405    pub vertex: usize,
406    /// Index of the face this half-edge belongs to (usize::MAX if boundary).
407    pub face: usize,
408    /// Index of the twin (opposite) half-edge (usize::MAX if boundary).
409    pub twin: usize,
410    /// Index of the next half-edge in the face loop.
411    pub next: usize,
412    /// Index of the previous half-edge in the face loop.
413    pub prev: usize,
414}
415/// Flags for texture seam edges.
416#[derive(Debug, Clone)]
417pub struct TextureSeamFlags {
418    /// Set of edges (v0, v1) with v0 < v1 that lie on texture seams.
419    pub seam_edges: HashSet<(usize, usize)>,
420}
421impl TextureSeamFlags {
422    /// Create empty seam flags.
423    pub fn new() -> Self {
424        Self {
425            seam_edges: HashSet::new(),
426        }
427    }
428    /// Mark an edge as a texture seam.
429    pub fn mark_seam(&mut self, v0: usize, v1: usize) {
430        let (lo, hi) = if v0 < v1 { (v0, v1) } else { (v1, v0) };
431        self.seam_edges.insert((lo, hi));
432    }
433    /// Check if an edge is on a texture seam.
434    pub fn is_seam(&self, v0: usize, v1: usize) -> bool {
435        let (lo, hi) = if v0 < v1 { (v0, v1) } else { (v1, v0) };
436        self.seam_edges.contains(&(lo, hi))
437    }
438    /// Number of seam edges.
439    pub fn count(&self) -> usize {
440        self.seam_edges.len()
441    }
442}
443/// Result of topology validation.
444#[derive(Debug, Clone)]
445pub struct TopologyValidation {
446    /// Whether the mesh is valid.
447    pub is_valid: bool,
448    /// Number of non-manifold edges found.
449    pub non_manifold_edges: usize,
450    /// Number of isolated vertices found.
451    pub isolated_vertices: usize,
452    /// Number of degenerate triangles found.
453    pub degenerate_triangles: usize,
454}
455/// An edge collapse candidate with its error cost.
456#[derive(Debug, Clone)]
457pub(super) struct CollapseCandidate {
458    /// Vertex at one end.
459    pub(super) v0: usize,
460    /// Vertex at the other end.
461    pub(super) v1: usize,
462    /// Optimal position for the merged vertex.
463    pub(super) optimal_pos: [f64; 3],
464    /// Quadric error cost.
465    pub(super) cost: f64,
466}