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