Skip to main content

oxiphysics_geometry/mesh_processing/
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_2::*;
7use std::collections::HashSet;
8
9#[allow(unused_imports)]
10use super::functions::*;
11use super::functions::{Face, Uv, Vertex};
12
13/// An anisotropic size field: target edge lengths along principal curvature
14/// directions.
15#[derive(Debug, Clone)]
16pub struct AnisotropicSizeField {
17    /// Per-vertex minimum principal curvature direction.
18    pub k_min_dir: Vec<[f64; 3]>,
19    /// Per-vertex maximum principal curvature direction.
20    pub k_max_dir: Vec<[f64; 3]>,
21    /// Target edge length along minimum curvature direction.
22    pub len_min: Vec<f64>,
23    /// Target edge length along maximum curvature direction.
24    pub len_max: Vec<f64>,
25}
26/// Aggregate quality statistics over a whole mesh.
27#[derive(Debug, Clone)]
28pub struct MeshQualityStats {
29    /// Mean aspect ratio across all faces.
30    pub mean_aspect_ratio: f64,
31    /// Maximum aspect ratio (worst triangle).
32    pub max_aspect_ratio: f64,
33    /// Minimum angle (degrees) in the mesh.
34    pub min_angle_deg: f64,
35    /// Mean area quality score (0..1; 1 = equilateral).
36    pub mean_area_quality: f64,
37}
38/// A feature edge defined by its two endpoint vertex indices and the dihedral
39/// angle between the two adjacent faces.
40#[derive(Debug, Clone, Copy)]
41pub struct FeatureEdge {
42    /// First vertex index.
43    pub v0: usize,
44    /// Second vertex index.
45    pub v1: usize,
46    /// Dihedral angle between adjacent faces in radians.
47    pub dihedral_angle: f64,
48}
49/// A symmetric 4×4 quadric error matrix stored in compact upper-triangle form.
50/// Indices: Q\[0\]=q00, Q\[1\]=q01, Q\[2\]=q02, Q\[3\]=q03, Q\[4\]=q11, Q\[5\]=q12,
51/// Q\[6\]=q13, Q\[7\]=q22, Q\[8\]=q23, Q\[9\]=q33.
52#[derive(Debug, Clone, Copy, Default)]
53pub struct Quadric {
54    /// Upper-triangle elements of the 4×4 symmetric matrix.
55    pub q: [f64; 10],
56}
57impl Quadric {
58    /// Create a zero quadric.
59    pub fn zero() -> Self {
60        Self { q: [0.0; 10] }
61    }
62    /// Add another quadric to this one.
63    pub fn add(&self, other: &Quadric) -> Quadric {
64        let mut r = Quadric::zero();
65        for i in 0..10 {
66            r.q[i] = self.q[i] + other.q[i];
67        }
68        r
69    }
70    /// Build a fundamental error quadric for the plane (a,b,c,d) where
71    /// `ax + by + cz + d = 0` and `(a,b,c)` is the unit normal.
72    pub fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Quadric {
73        Quadric {
74            q: [
75                a * a,
76                a * b,
77                a * c,
78                a * d,
79                b * b,
80                b * c,
81                b * d,
82                c * c,
83                c * d,
84                d * d,
85            ],
86        }
87    }
88    /// Evaluate Q(v) = váµ€Qv (cost of placing a vertex at `v`).
89    pub fn evaluate(&self, v: [f64; 3]) -> f64 {
90        let [x, y, z] = v;
91        let w = 1.0_f64;
92        let q = &self.q;
93        q[0] * x * x
94            + 2.0 * q[1] * x * y
95            + 2.0 * q[2] * x * z
96            + 2.0 * q[3] * x * w
97            + q[4] * y * y
98            + 2.0 * q[5] * y * z
99            + 2.0 * q[6] * y * w
100            + q[7] * z * z
101            + 2.0 * q[8] * z * w
102            + q[9] * w * w
103    }
104}
105/// Quality metrics for a single triangle.
106#[derive(Debug, Clone, Copy)]
107pub struct TriangleQuality {
108    /// Aspect ratio (circumradius / 2·inradius); perfect equilateral = 1.
109    pub aspect_ratio: f64,
110    /// Minimum interior angle in radians.
111    pub min_angle: f64,
112    /// Maximum interior angle in radians.
113    pub max_angle: f64,
114    /// Normalised area (0..1 relative to equilateral with same perimeter).
115    pub area_quality: f64,
116}
117/// A triangle mesh used as the primary data structure for all processing.
118#[derive(Debug, Clone)]
119pub struct ProcessMesh {
120    /// Vertex positions.
121    pub verts: Vec<Vertex>,
122    /// Triangular faces (vertex-index triples).
123    pub faces: Vec<Face>,
124    /// Per-vertex normals (optional).
125    pub normals: Option<Vec<Vertex>>,
126    /// Per-vertex UV coordinates (optional).
127    pub uvs: Option<Vec<Uv>>,
128}
129impl ProcessMesh {
130    /// Create a new mesh from vertices and faces.
131    pub fn new(verts: Vec<Vertex>, faces: Vec<Face>) -> Self {
132        Self {
133            verts,
134            faces,
135            normals: None,
136            uvs: None,
137        }
138    }
139    /// Return the number of vertices.
140    pub fn num_verts(&self) -> usize {
141        self.verts.len()
142    }
143    /// Return the number of faces.
144    pub fn num_faces(&self) -> usize {
145        self.faces.len()
146    }
147    /// Compute per-vertex normals by averaging adjacent face normals.
148    pub fn compute_normals(&mut self) {
149        let nv = self.verts.len();
150        let mut normals = vec![[0.0_f64; 3]; nv];
151        for &[a, b, c] in &self.faces {
152            let va = self.verts[a];
153            let vb = self.verts[b];
154            let vc = self.verts[c];
155            let ab = vec3_sub(vb, va);
156            let ac = vec3_sub(vc, va);
157            let n = vec3_cross(ab, ac);
158            for &i in &[a, b, c] {
159                normals[i] = vec3_add(normals[i], n);
160            }
161        }
162        for n in &mut normals {
163            *n = vec3_normalize(*n);
164        }
165        self.normals = Some(normals);
166    }
167    /// Build a map from each vertex to its list of neighbouring vertex indices.
168    pub fn build_adjacency(&self) -> Vec<Vec<usize>> {
169        let nv = self.verts.len();
170        let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); nv];
171        for &[a, b, c] in &self.faces {
172            adj[a].insert(b);
173            adj[a].insert(c);
174            adj[b].insert(a);
175            adj[b].insert(c);
176            adj[c].insert(a);
177            adj[c].insert(b);
178        }
179        adj.into_iter().map(|s| s.into_iter().collect()).collect()
180    }
181}
182/// UV parameterization result with per-vertex texture coordinates.
183#[derive(Debug, Clone)]
184pub struct UvParameterization {
185    /// Mesh after parameterization (same topology).
186    pub mesh: ProcessMesh,
187    /// Per-vertex UV coordinates in \[0,1\]².
188    pub uvs: Vec<Uv>,
189}
190/// A rectangular texture patch cut from the atlas.
191#[derive(Debug, Clone)]
192pub struct AtlasPatch {
193    /// Index of the original face group.
194    pub group_id: usize,
195    /// UV bounding box: \[umin, vmin, umax, vmax\].
196    pub bounds: [f64; 4],
197    /// Vertices (UVs) of the patch in atlas space.
198    pub uvs: Vec<Uv>,
199}
200/// Result of a boolean mesh operation.
201#[derive(Debug, Clone)]
202pub struct BooleanResult {
203    /// The combined / intersected / subtracted mesh.
204    pub mesh: ProcessMesh,
205    /// Whether the operation produced a topologically exact result.
206    pub is_exact: bool,
207}
208
209impl BooleanResult {
210    /// Determine whether the resulting mesh is topologically exact.
211    ///
212    /// Returns `true` when:
213    /// 1. Every edge is shared by exactly two faces (manifold, no boundary).
214    /// 2. No adjacent faces are nearly coplanar (dihedral > 1e-6 rad).
215    /// 3. No degenerate triangles (cross-product length > 1e-12).
216    pub fn is_topologically_exact(&self) -> bool {
217        let mesh = &self.mesh;
218        if mesh.faces.is_empty() {
219            return false;
220        }
221        let mut edge_count: std::collections::HashMap<(usize, usize), usize> =
222            std::collections::HashMap::new();
223        for &[a, b, c] in &mesh.faces {
224            for &(u, v) in &[
225                (a.min(b), a.max(b)),
226                (b.min(c), b.max(c)),
227                (a.min(c), a.max(c)),
228            ] {
229                *edge_count.entry((u, v)).or_insert(0) += 1;
230            }
231        }
232        if edge_count.values().any(|&cnt| cnt != 2) {
233            return false;
234        }
235        let normals: Vec<[f64; 3]> = mesh
236            .faces
237            .iter()
238            .map(|&[a, b, c]| {
239                let va = mesh.verts[a];
240                let vb = mesh.verts[b];
241                let vc = mesh.verts[c];
242                let ab = [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]];
243                let ac = [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]];
244                [
245                    ab[1] * ac[2] - ab[2] * ac[1],
246                    ab[2] * ac[0] - ab[0] * ac[2],
247                    ab[0] * ac[1] - ab[1] * ac[0],
248                ]
249            })
250            .collect();
251        for n in &normals {
252            if (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt() < 1e-12 {
253                return false;
254            }
255        }
256        let mut face_of_edge: std::collections::HashMap<(usize, usize), Vec<usize>> =
257            std::collections::HashMap::new();
258        for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
259            for &(u, v) in &[
260                (a.min(b), a.max(b)),
261                (b.min(c), b.max(c)),
262                (a.min(c), a.max(c)),
263            ] {
264                face_of_edge.entry((u, v)).or_default().push(fi);
265            }
266        }
267        const MIN_ANGLE: f64 = 1e-6;
268        for fs in face_of_edge.values() {
269            if fs.len() != 2 {
270                continue;
271            }
272            let n0 = normals[fs[0]];
273            let n1 = normals[fs[1]];
274            let l0 = (n0[0] * n0[0] + n0[1] * n0[1] + n0[2] * n0[2]).sqrt();
275            let l1 = (n1[0] * n1[0] + n1[1] * n1[1] + n1[2] * n1[2]).sqrt();
276            if l0 < 1e-30 || l1 < 1e-30 {
277                return false;
278            }
279            let dot = (n0[0] * n1[0] + n0[1] * n1[1] + n0[2] * n1[2]) / (l0 * l1);
280            if dot.abs().min(1.0).acos() < MIN_ANGLE {
281                return false;
282            }
283        }
284        true
285    }
286}
287
288#[cfg(test)]
289mod boolean_result_tests {
290    use super::{BooleanResult, ProcessMesh};
291
292    fn make_tetrahedron() -> ProcessMesh {
293        let verts = vec![
294            [0.0f64, 0.0, 0.0],
295            [1.0, 0.0, 0.0],
296            [0.5, 1.0, 0.0],
297            [0.5, 0.333, 0.816],
298        ];
299        ProcessMesh::new(verts, vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]])
300    }
301
302    #[test]
303    fn test_closed_tetrahedron_is_exact() {
304        let r = BooleanResult {
305            mesh: make_tetrahedron(),
306            is_exact: false,
307        };
308        assert!(r.is_topologically_exact());
309    }
310
311    #[test]
312    fn test_coplanar_patch_not_exact() {
313        let verts = vec![
314            [0.0f64, 0.0, 0.0],
315            [1.0, 0.0, 0.0],
316            [0.5, 1.0, 0.0],
317            [0.5, -1.0, 0.0],
318        ];
319        let mesh = ProcessMesh::new(verts, vec![[0, 1, 2], [0, 3, 1]]);
320        let r = BooleanResult {
321            mesh,
322            is_exact: false,
323        };
324        assert!(!r.is_topologically_exact());
325    }
326
327    #[test]
328    fn test_empty_mesh_not_exact() {
329        let r = BooleanResult {
330            mesh: ProcessMesh::new(vec![], vec![]),
331            is_exact: false,
332        };
333        assert!(!r.is_topologically_exact());
334    }
335
336    #[test]
337    fn test_is_exact_flag_wired() {
338        let mut r = BooleanResult {
339            mesh: make_tetrahedron(),
340            is_exact: false,
341        };
342        r.is_exact = r.is_topologically_exact();
343        assert!(r.is_exact);
344    }
345}