Skip to main content

oxiphysics_geometry/topology/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#![allow(clippy::ptr_arg)]
6use super::functions::*;
7use std::collections::{HashMap, HashSet};
8
9/// A Morse function value at a vertex.
10#[derive(Debug, Clone, Copy)]
11pub struct MorseVertex {
12    /// Vertex index.
13    pub idx: usize,
14    /// Morse function value.
15    pub value: f64,
16    /// Critical point type.
17    pub critical_type: MorseCriticalType,
18}
19/// 2D polygon offset (inward / outward buffering).
20pub struct PolygonOffset;
21impl PolygonOffset {
22    /// Offset a 2D polygon by `dist` (positive = outward, negative = inward).
23    ///
24    /// Uses simple vertex normal offset (miter join). Returns the offset polygon.
25    /// The input polygon should be a simple closed polygon (last vertex connects to first).
26    pub fn offset_polygon_2d(verts: &[[f64; 2]], dist: f64) -> Vec<[f64; 2]> {
27        let n = verts.len();
28        if n < 3 {
29            return verts.to_vec();
30        }
31        let edge_normal = |a: [f64; 2], b: [f64; 2]| -> [f64; 2] {
32            let dx = b[0] - a[0];
33            let dy = b[1] - a[1];
34            let len = (dx * dx + dy * dy).sqrt();
35            if len < 1e-300 {
36                [0.0, 1.0]
37            } else {
38                [dy / len, -dx / len]
39            }
40        };
41        let mut result = Vec::with_capacity(n);
42        for i in 0..n {
43            let prev = if i == 0 { n - 1 } else { i - 1 };
44            let next = (i + 1) % n;
45            let n_prev = edge_normal(verts[prev], verts[i]);
46            let n_next = edge_normal(verts[i], verts[next]);
47            let bx = n_prev[0] + n_next[0];
48            let by = n_prev[1] + n_next[1];
49            let blen = (bx * bx + by * by).sqrt();
50            let (ox, oy) = if blen < 1e-12 {
51                (n_next[0] * dist, n_next[1] * dist)
52            } else {
53                let dot = n_prev[0] * n_next[0] + n_prev[1] * n_next[1];
54                let miter_scale = dist / (1.0 + dot).max(0.1).sqrt();
55                (bx / blen * miter_scale, by / blen * miter_scale)
56            };
57            result.push([verts[i][0] + ox, verts[i][1] + oy]);
58        }
59        result
60    }
61    /// Compute the signed area of a 2D polygon (positive = CCW).
62    pub fn signed_area(verts: &[[f64; 2]]) -> f64 {
63        let n = verts.len();
64        if n < 3 {
65            return 0.0;
66        }
67        let mut area = 0.0;
68        for i in 0..n {
69            let j = (i + 1) % n;
70            area += verts[i][0] * verts[j][1];
71            area -= verts[j][0] * verts[i][1];
72        }
73        area * 0.5
74    }
75    /// Check if a polygon is wound counter-clockwise (positive area).
76    pub fn is_ccw(verts: &[[f64; 2]]) -> bool {
77        Self::signed_area(verts) > 0.0
78    }
79}
80/// A half-edge (directed half-edge in the DCEL).
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct HalfEdge {
83    /// Origin vertex index.
84    pub origin: usize,
85    /// Next half-edge in the face loop.
86    pub next: usize,
87    /// Previous half-edge in the face loop.
88    pub prev: usize,
89    /// Twin (opposite) half-edge index (usize::MAX if boundary).
90    pub twin: usize,
91    /// Face this half-edge belongs to (usize::MAX if boundary).
92    pub face: usize,
93}
94impl HalfEdge {
95    /// Create a new half-edge.
96    pub fn new(origin: usize) -> Self {
97        Self {
98            origin,
99            next: usize::MAX,
100            prev: usize::MAX,
101            twin: usize::MAX,
102            face: usize::MAX,
103        }
104    }
105    /// Returns true if this half-edge is a boundary half-edge (no twin).
106    pub fn is_boundary(&self) -> bool {
107        self.twin == usize::MAX
108    }
109}
110/// Doubly Connected Edge List (DCEL) half-edge mesh.
111#[derive(Debug, Clone)]
112pub struct HalfEdgeMesh {
113    /// All half-edges.
114    pub half_edges: Vec<HalfEdge>,
115    /// All vertices (positions).
116    pub vertices: Vec<[f64; 3]>,
117    /// Vertex → one outgoing half-edge index.
118    pub vertex_he: Vec<usize>,
119    /// All faces.
120    pub faces: Vec<Face>,
121}
122impl HalfEdgeMesh {
123    /// Create an empty half-edge mesh.
124    pub fn new() -> Self {
125        Self {
126            half_edges: Vec::new(),
127            vertices: Vec::new(),
128            vertex_he: Vec::new(),
129            faces: Vec::new(),
130        }
131    }
132    /// Create a half-edge mesh from a triangle soup (vertices + face indices).
133    pub fn from_triangles(verts: Vec<[f64; 3]>, faces: &[[usize; 3]]) -> Self {
134        let nv = verts.len();
135        let nf = faces.len();
136        let nhe = nf * 3;
137        let mut he = vec![
138            HalfEdge {
139                origin: 0,
140                next: usize::MAX,
141                prev: usize::MAX,
142                twin: usize::MAX,
143                face: usize::MAX,
144            };
145            nhe
146        ];
147        let mut face_list = Vec::with_capacity(nf);
148        let mut vertex_he = vec![usize::MAX; nv];
149        for (fi, tri) in faces.iter().enumerate() {
150            let base = fi * 3;
151            for k in 0..3 {
152                let he_idx = base + k;
153                he[he_idx].origin = tri[k];
154                he[he_idx].next = base + (k + 1) % 3;
155                he[he_idx].prev = base + (k + 2) % 3;
156                he[he_idx].face = fi;
157                if vertex_he[tri[k]] == usize::MAX {
158                    vertex_he[tri[k]] = he_idx;
159                }
160            }
161            face_list.push(Face {
162                start_he: base,
163                is_outer: false,
164            });
165        }
166        let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
167        for (i, h) in he.iter().enumerate() {
168            let dst = he[h.next].origin;
169            edge_map.insert((h.origin, dst), i);
170        }
171        for i in 0..nhe {
172            if he[i].twin != usize::MAX {
173                continue;
174            }
175            let src = he[i].origin;
176            let dst = he[he[i].next].origin;
177            if let Some(&twin_idx) = edge_map.get(&(dst, src)) {
178                he[i].twin = twin_idx;
179                he[twin_idx].twin = i;
180            }
181        }
182        Self {
183            half_edges: he,
184            vertices: verts,
185            vertex_he,
186            faces: face_list,
187        }
188    }
189    /// Number of vertices.
190    pub fn num_vertices(&self) -> usize {
191        self.vertices.len()
192    }
193    /// Number of faces.
194    pub fn num_faces(&self) -> usize {
195        self.faces.len()
196    }
197    /// Number of (full) edges (each edge = 2 half-edges or 1 boundary half-edge).
198    pub fn num_edges(&self) -> usize {
199        let interior: usize = self
200            .half_edges
201            .iter()
202            .filter(|he| he.twin != usize::MAX && he.twin > self.half_edges.len() / 2)
203            .count();
204        let boundary: usize = self
205            .half_edges
206            .iter()
207            .filter(|he| he.twin == usize::MAX)
208            .count();
209        interior + boundary
210    }
211    /// Get the face half-edges (indices) in order for face `fi`.
212    pub fn face_half_edges(&self, fi: usize) -> Vec<usize> {
213        let start = self.faces[fi].start_he;
214        let mut cur = start;
215        let mut result = Vec::new();
216        loop {
217            result.push(cur);
218            cur = self.half_edges[cur].next;
219            if cur == start {
220                break;
221            }
222            if result.len() > 100 {
223                break;
224            }
225        }
226        result
227    }
228    /// Get the vertex positions of face `fi`.
229    pub fn face_vertices(&self, fi: usize) -> Vec<[f64; 3]> {
230        self.face_half_edges(fi)
231            .iter()
232            .map(|&he_idx| self.vertices[self.half_edges[he_idx].origin])
233            .collect()
234    }
235    /// Compute the face normal for a triangular face.
236    pub fn face_normal(&self, fi: usize) -> [f64; 3] {
237        let verts = self.face_vertices(fi);
238        if verts.len() < 3 {
239            return [0.0, 1.0, 0.0];
240        }
241        let e1 = sub3(verts[1], verts[0]);
242        let e2 = sub3(verts[2], verts[0]);
243        norm3(cross3(e1, e2))
244    }
245    /// Compute the Euler characteristic: V - E + F.
246    pub fn euler_characteristic(&self) -> i64 {
247        let v = self.num_vertices() as i64;
248        let e = self.count_unique_edges() as i64;
249        let f = self.num_faces() as i64;
250        v - e + f
251    }
252    /// Count unique (undirected) edges.
253    pub fn count_unique_edges(&self) -> usize {
254        let mut seen: HashSet<(usize, usize)> = HashSet::new();
255        for he in &self.half_edges {
256            if he.face == usize::MAX {
257                continue;
258            }
259            let a = he.origin;
260            let b = self.half_edges[he.next].origin;
261            let key = if a < b { (a, b) } else { (b, a) };
262            seen.insert(key);
263        }
264        seen.len()
265    }
266    /// Compute genus from Euler characteristic (closed orientable surface).
267    ///
268    /// χ = 2 - 2g → g = (2 - χ) / 2
269    pub fn genus(&self) -> i64 {
270        (2 - self.euler_characteristic()) / 2
271    }
272    /// Returns boundary half-edge indices (half-edges with no twin).
273    pub fn boundary_half_edges(&self) -> Vec<usize> {
274        self.half_edges
275            .iter()
276            .enumerate()
277            .filter(|(_, he)| he.twin == usize::MAX)
278            .map(|(i, _)| i)
279            .collect()
280    }
281    /// Returns true if the mesh is closed (no boundary half-edges).
282    pub fn is_closed(&self) -> bool {
283        self.boundary_half_edges().is_empty()
284    }
285    /// Returns true if the mesh is a manifold.
286    ///
287    /// A mesh is manifold if every edge has at most 2 incident faces
288    /// and every vertex has a single fan of faces.
289    pub fn is_manifold(&self) -> bool {
290        let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
291        for he in &self.half_edges {
292            if he.face == usize::MAX {
293                continue;
294            }
295            let a = he.origin;
296            let b = self.half_edges[he.next].origin;
297            let key = if a < b { (a, b) } else { (b, a) };
298            *edge_count.entry(key).or_insert(0) += 1;
299        }
300        edge_count.values().all(|&c| c <= 2)
301    }
302    /// Compute vertex normals by averaging incident face normals.
303    pub fn vertex_normals(&self) -> Vec<[f64; 3]> {
304        let nv = self.num_vertices();
305        let mut normals = vec![[0.0f64; 3]; nv];
306        let mut counts = vec![0usize; nv];
307        for fi in 0..self.num_faces() {
308            let n = self.face_normal(fi);
309            for he_idx in self.face_half_edges(fi) {
310                let v = self.half_edges[he_idx].origin;
311                normals[v] = add3(normals[v], n);
312                counts[v] += 1;
313            }
314        }
315        for (i, n) in normals.iter_mut().enumerate() {
316            if counts[i] > 0 {
317                *n = norm3(*n);
318            }
319        }
320        normals
321    }
322    /// Compute the area of face `fi`.
323    pub fn face_area(&self, fi: usize) -> f64 {
324        let verts = self.face_vertices(fi);
325        if verts.len() < 3 {
326            return 0.0;
327        }
328        let e1 = sub3(verts[1], verts[0]);
329        let e2 = sub3(verts[2], verts[0]);
330        len3(cross3(e1, e2)) * 0.5
331    }
332    /// Total surface area.
333    pub fn total_area(&self) -> f64 {
334        (0..self.num_faces()).map(|fi| self.face_area(fi)).sum()
335    }
336    /// Get vertices adjacent to vertex `v` (1-ring neighbors).
337    pub fn vertex_neighbors(&self, v: usize) -> Vec<usize> {
338        let mut neighbors = Vec::new();
339        let start = self.vertex_he[v];
340        if start == usize::MAX {
341            return neighbors;
342        }
343        let mut cur = start;
344        loop {
345            let next_he = self.half_edges[cur].next;
346            let neighbor = self.half_edges[next_he].origin;
347            neighbors.push(neighbor);
348            if self.half_edges[cur].twin == usize::MAX {
349                break;
350            }
351            cur = self.half_edges[self.half_edges[cur].twin].next;
352            if cur == start {
353                break;
354            }
355            if neighbors.len() > 1000 {
356                break;
357            }
358        }
359        neighbors
360    }
361}
362/// A winged-edge record.
363#[derive(Debug, Clone)]
364pub struct WingedEdge {
365    /// Start vertex.
366    pub v_start: usize,
367    /// End vertex.
368    pub v_end: usize,
369    /// Left face.
370    pub face_left: usize,
371    /// Right face.
372    pub face_right: usize,
373    /// CCW edge from v_start on face_left.
374    pub ccw_start_left: usize,
375    /// CW edge from v_start on face_right.
376    pub cw_start_right: usize,
377    /// CCW edge from v_end on face_right.
378    pub ccw_end_right: usize,
379    /// CW edge from v_end on face_left.
380    pub cw_end_left: usize,
381}
382/// Type of a Morse critical point.
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384pub enum MorseCriticalType {
385    /// Local minimum.
386    Minimum,
387    /// Saddle point.
388    Saddle,
389    /// Local maximum.
390    Maximum,
391    /// Regular (non-critical) point.
392    Regular,
393}
394/// Mesh topology descriptor: vertex/edge/face counts and derived invariants.
395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
396pub struct MeshTopology {
397    /// Number of vertices V.
398    pub n_vertices: usize,
399    /// Number of edges E.
400    pub n_edges: usize,
401    /// Number of faces F.
402    pub n_faces: usize,
403}
404impl MeshTopology {
405    /// Create a new topology descriptor.
406    pub fn new(n_vertices: usize, n_edges: usize, n_faces: usize) -> Self {
407        Self {
408            n_vertices,
409            n_edges,
410            n_faces,
411        }
412    }
413    /// Euler characteristic χ = V - E + F.
414    pub fn euler_characteristic(&self) -> i32 {
415        self.n_vertices as i32 - self.n_edges as i32 + self.n_faces as i32
416    }
417    /// Genus of a closed orientable surface: g = (2 - χ) / 2.
418    pub fn genus(&self) -> i32 {
419        (2 - self.euler_characteristic()) / 2
420    }
421    /// Number of connected components β₀ estimated from Euler characteristic
422    /// (assumes connected: β₀ = 1 for sphere topology).
423    pub fn betti_0_estimate(&self) -> usize {
424        1
425    }
426    /// Cycle rank (first Betti number estimate) β₁ = E - V + β₀.
427    pub fn cycle_rank(&self) -> i32 {
428        self.n_edges as i32 - self.n_vertices as i32 + 1
429    }
430}
431/// Quadric Error Metric (QEM) mesh simplification utilities.
432pub struct MeshSimplification;
433impl MeshSimplification {
434    /// Compute the quadric error matrix Q for a vertex.
435    ///
436    /// For each adjacent face, a plane equation \[a,b,c,d\] (ax+by+cz+d=0)
437    /// contributes the outer product pp^T to Q.  Returns a 4×4 matrix.
438    pub fn quadric_error_matrix(_vertex: [f64; 3], faces: &[[[f64; 3]; 3]]) -> [[f64; 4]; 4] {
439        let mut q = [[0.0f64; 4]; 4];
440        for tri in faces {
441            let e1 = sub3(tri[1], tri[0]);
442            let e2 = sub3(tri[2], tri[0]);
443            let n = norm3(cross3(e1, e2));
444            let d = -dot3(n, tri[0]);
445            let p = [n[0], n[1], n[2], d];
446            for i in 0..4 {
447                for j in 0..4 {
448                    q[i][j] += p[i] * p[j];
449                }
450            }
451        }
452        q
453    }
454    /// Edge collapse cost using combined quadric: v^T (Q1+Q2) v.
455    ///
456    /// The optimal collapse position is taken as the midpoint (v1+v2)/2.
457    pub fn edge_collapse_cost(
458        v1: [f64; 3],
459        v2: [f64; 3],
460        q1: [[f64; 4]; 4],
461        q2: [[f64; 4]; 4],
462    ) -> f64 {
463        let mut q = [[0.0f64; 4]; 4];
464        for i in 0..4 {
465            for j in 0..4 {
466                q[i][j] = q1[i][j] + q2[i][j];
467            }
468        }
469        let v = [
470            (v1[0] + v2[0]) * 0.5,
471            (v1[1] + v2[1]) * 0.5,
472            (v1[2] + v2[2]) * 0.5,
473            1.0,
474        ];
475        let mut cost = 0.0;
476        for i in 0..4 {
477            let mut row_sum = 0.0;
478            for j in 0..4 {
479                row_sum += q[i][j] * v[j];
480            }
481            cost += v[i] * row_sum;
482        }
483        cost.abs()
484    }
485    /// Greedy QEM simplification: collapse edges until `target_count` faces remain.
486    ///
487    /// This is a simplified greedy approach suitable for small meshes.
488    pub fn simplify_qem(
489        positions: &mut Vec<[f64; 3]>,
490        triangles: &mut Vec<[usize; 3]>,
491        target_count: usize,
492    ) {
493        while triangles.len() > target_count {
494            if triangles.is_empty() || positions.len() < 2 {
495                break;
496            }
497            let mut best_cost = f64::INFINITY;
498            let mut best_v1 = 0usize;
499            let mut best_v2 = 1usize;
500            let nv = positions.len();
501            let mut vertex_faces: Vec<Vec<usize>> = vec![Vec::new(); nv];
502            for (fi, tri) in triangles.iter().enumerate() {
503                for &v in tri.iter() {
504                    if v < nv {
505                        vertex_faces[v].push(fi);
506                    }
507                }
508            }
509            let mut quadrics: Vec<[[f64; 4]; 4]> = vec![[[0.0; 4]; 4]; nv];
510            for v in 0..nv {
511                let face_tris: Vec<[[f64; 3]; 3]> = vertex_faces[v]
512                    .iter()
513                    .filter_map(|&fi| {
514                        let t = triangles[fi];
515                        if t[0] < nv && t[1] < nv && t[2] < nv {
516                            Some([positions[t[0]], positions[t[1]], positions[t[2]]])
517                        } else {
518                            None
519                        }
520                    })
521                    .collect();
522                quadrics[v] = Self::quadric_error_matrix(positions[v], &face_tris);
523            }
524            let mut seen_edges: HashSet<(usize, usize)> = HashSet::new();
525            for tri in triangles.iter() {
526                for k in 0..3 {
527                    let a = tri[k];
528                    let b = tri[(k + 1) % 3];
529                    if a >= nv || b >= nv {
530                        continue;
531                    }
532                    let key = if a < b { (a, b) } else { (b, a) };
533                    if seen_edges.insert(key) {
534                        let cost = Self::edge_collapse_cost(
535                            positions[a],
536                            positions[b],
537                            quadrics[a],
538                            quadrics[b],
539                        );
540                        if cost < best_cost {
541                            best_cost = cost;
542                            best_v1 = a;
543                            best_v2 = b;
544                        }
545                    }
546                }
547            }
548            if best_cost.is_infinite() {
549                break;
550            }
551            let mid = [
552                (positions[best_v1][0] + positions[best_v2][0]) * 0.5,
553                (positions[best_v1][1] + positions[best_v2][1]) * 0.5,
554                (positions[best_v1][2] + positions[best_v2][2]) * 0.5,
555            ];
556            positions[best_v1] = mid;
557            for tri in triangles.iter_mut() {
558                for v in tri.iter_mut() {
559                    if *v == best_v2 {
560                        *v = best_v1;
561                    }
562                }
563            }
564            triangles.retain(|t| t[0] != t[1] && t[1] != t[2] && t[0] != t[2]);
565        }
566    }
567}
568/// A curve skeleton: a 1D curve embedded in 3D representing the topology of a shape.
569#[derive(Debug, Clone)]
570pub struct CurveSkeleton {
571    /// Skeleton nodes (3D positions).
572    pub nodes: Vec<[f64; 3]>,
573    /// Skeleton edges (pairs of node indices).
574    pub edges: Vec<(usize, usize)>,
575}
576impl Default for CurveSkeleton {
577    fn default() -> Self {
578        Self::new()
579    }
580}
581impl CurveSkeleton {
582    /// Create an empty curve skeleton.
583    pub fn new() -> Self {
584        Self {
585            nodes: Vec::new(),
586            edges: Vec::new(),
587        }
588    }
589    /// Add a skeleton node.
590    pub fn add_node(&mut self, pos: [f64; 3]) -> usize {
591        let idx = self.nodes.len();
592        self.nodes.push(pos);
593        idx
594    }
595    /// Add a skeleton edge.
596    pub fn add_edge(&mut self, a: usize, b: usize) {
597        self.edges.push((a, b));
598    }
599    /// Total skeleton length.
600    pub fn total_length(&self) -> f64 {
601        self.edges
602            .iter()
603            .map(|&(a, b)| len3(sub3(self.nodes[b], self.nodes[a])))
604            .sum()
605    }
606    /// Number of nodes.
607    pub fn num_nodes(&self) -> usize {
608        self.nodes.len()
609    }
610    /// Number of edges.
611    pub fn num_edges(&self) -> usize {
612        self.edges.len()
613    }
614}
615/// A face in the DCEL (polygon).
616#[derive(Debug, Clone)]
617pub struct Face {
618    /// Index of one half-edge on this face.
619    pub start_he: usize,
620    /// Whether this is the outer boundary face.
621    pub is_outer: bool,
622}
623/// A persistence diagram: collection of birth-death pairs.
624#[derive(Debug, Clone, Default)]
625pub struct PersistenceDiagram {
626    /// Birth-death pairs.
627    pub pairs: Vec<BirthDeathPair>,
628}
629impl PersistenceDiagram {
630    /// Create an empty persistence diagram.
631    pub fn new() -> Self {
632        Self { pairs: Vec::new() }
633    }
634    /// Add a birth-death pair.
635    pub fn add_pair(&mut self, dim: usize, birth: f64, death: f64) {
636        self.pairs.push(BirthDeathPair { dim, birth, death });
637    }
638    /// Get pairs for a given dimension.
639    pub fn pairs_for_dim(&self, dim: usize) -> Vec<&BirthDeathPair> {
640        self.pairs.iter().filter(|p| p.dim == dim).collect()
641    }
642    /// Betti number at threshold `t`: count of pairs where birth ≤ t < death.
643    pub fn betti_at(&self, dim: usize, t: f64) -> usize {
644        self.pairs
645            .iter()
646            .filter(|p| p.dim == dim && p.birth <= t && t < p.death)
647            .count()
648    }
649    /// Maximum persistence across all pairs.
650    pub fn max_persistence(&self) -> f64 {
651        self.pairs
652            .iter()
653            .filter(|p| !p.is_essential())
654            .map(|p| p.persistence())
655            .fold(0.0_f64, f64::max)
656    }
657    /// Compute bottleneck distance (simplified: max over all matched pairs).
658    pub fn bottleneck_distance(&self, other: &PersistenceDiagram) -> f64 {
659        let mut dist = 0.0_f64;
660        for p in &self.pairs {
661            let closest = other
662                .pairs
663                .iter()
664                .filter(|q| q.dim == p.dim)
665                .map(|q| (p.birth - q.birth).abs().max((p.death - q.death).abs()))
666                .fold(f64::MAX, f64::min);
667            if closest < f64::MAX {
668                dist = dist.max(closest);
669            }
670        }
671        dist
672    }
673}
674/// Non-manifold vertex: a vertex where the star is not a single disk.
675#[derive(Debug, Clone)]
676pub struct NonManifoldVertex {
677    /// Vertex index.
678    pub v: usize,
679    /// Number of connected fans around this vertex.
680    pub fan_count: usize,
681}
682/// Quad-edge mesh (topological only — no geometry).
683#[derive(Debug, Clone)]
684pub struct QuadEdgeMesh {
685    /// Edge records.
686    pub edges: Vec<QuadEdge>,
687    /// Vertex positions.
688    pub vertices: Vec<[f64; 3]>,
689}
690impl QuadEdgeMesh {
691    /// Create an empty quad-edge mesh.
692    pub fn new() -> Self {
693        Self {
694            edges: Vec::new(),
695            vertices: Vec::new(),
696        }
697    }
698    /// Allocate a new edge (returns base index into edges).
699    pub fn make_edge(&mut self, org: usize, dst: usize) -> usize {
700        let base = self.edges.len() * 4;
701        let qe = QuadEdge {
702            next: [base, base + 3, base + 2, base + 1],
703            data: [org, 0, dst, 0],
704        };
705        self.edges.push(qe);
706        base
707    }
708    /// Add a vertex.
709    pub fn add_vertex(&mut self, pos: [f64; 3]) -> usize {
710        let idx = self.vertices.len();
711        self.vertices.push(pos);
712        idx
713    }
714    /// Number of quad-edges.
715    pub fn num_edges(&self) -> usize {
716        self.edges.len()
717    }
718}
719/// A k-simplex (0=vertex, 1=edge, 2=triangle, 3=tetrahedron).
720#[derive(Debug, Clone, PartialEq, Eq, Hash)]
721pub struct Simplex {
722    /// Sorted vertex indices.
723    pub vertices: Vec<usize>,
724}
725impl Simplex {
726    /// Create a simplex from vertex indices (auto-sorted).
727    pub fn new(mut verts: Vec<usize>) -> Self {
728        verts.sort_unstable();
729        verts.dedup();
730        Self { vertices: verts }
731    }
732    /// Dimension: k for a k-simplex (0=vertex, 1=edge, ...).
733    pub fn dim(&self) -> usize {
734        self.vertices.len().saturating_sub(1)
735    }
736    /// Get all (k-1)-dimensional boundary faces.
737    pub fn boundary_faces(&self) -> Vec<Simplex> {
738        if self.vertices.len() <= 1 {
739            return Vec::new();
740        }
741        (0..self.vertices.len())
742            .map(|i| {
743                let verts: Vec<usize> = self
744                    .vertices
745                    .iter()
746                    .enumerate()
747                    .filter(|(j, _)| *j != i)
748                    .map(|(_, &v)| v)
749                    .collect();
750                Simplex::new(verts)
751            })
752            .collect()
753    }
754    /// Returns true if this simplex is a face of `other`.
755    pub fn is_face_of(&self, other: &Simplex) -> bool {
756        self.vertices.iter().all(|v| other.vertices.contains(v))
757    }
758}
759/// A quad-edge record (Guibas-Stolfi).
760///
761/// Stores all four oriented edges (e, e^rot, e^sym, e^rot^sym).
762#[derive(Debug, Clone)]
763pub struct QuadEdge {
764    /// The 4 edges in the quad-edge ring: \[e, e_rot, e_sym, e_rot_sym\].
765    pub next: [usize; 4],
766    /// Vertex (or face) associated with the origin of this edge slot.
767    pub data: [usize; 4],
768}
769/// A birth-death pair in persistent homology.
770#[derive(Debug, Clone, Copy, PartialEq)]
771pub struct BirthDeathPair {
772    /// Homological dimension (0 = connected component, 1 = loop, 2 = void).
773    pub dim: usize,
774    /// Birth filtration value.
775    pub birth: f64,
776    /// Death filtration value (f64::MAX for essential classes).
777    pub death: f64,
778}
779impl BirthDeathPair {
780    /// Compute the persistence (death - birth).
781    pub fn persistence(&self) -> f64 {
782        if self.death == f64::MAX {
783            f64::MAX
784        } else {
785            self.death - self.birth
786        }
787    }
788    /// Returns true if this is an essential (infinite persistence) class.
789    pub fn is_essential(&self) -> bool {
790        self.death == f64::MAX
791    }
792}
793/// Winged-edge mesh.
794#[derive(Debug, Clone)]
795pub struct WingedEdgeMesh {
796    /// All edges.
797    pub edges: Vec<WingedEdge>,
798    /// All vertices.
799    pub vertices: Vec<[f64; 3]>,
800    /// Face → one edge index.
801    pub face_edge: Vec<usize>,
802    /// Vertex → one edge index.
803    pub vertex_edge: Vec<usize>,
804}
805impl WingedEdgeMesh {
806    /// Create an empty winged-edge mesh.
807    pub fn new() -> Self {
808        Self {
809            edges: Vec::new(),
810            vertices: Vec::new(),
811            face_edge: Vec::new(),
812            vertex_edge: Vec::new(),
813        }
814    }
815    /// Add a vertex.
816    pub fn add_vertex(&mut self, pos: [f64; 3]) -> usize {
817        let idx = self.vertices.len();
818        self.vertices.push(pos);
819        self.vertex_edge.push(usize::MAX);
820        idx
821    }
822    /// Add an edge (placeholder connectivity).
823    pub fn add_edge(&mut self, v_start: usize, v_end: usize) -> usize {
824        let idx = self.edges.len();
825        self.edges.push(WingedEdge {
826            v_start,
827            v_end,
828            face_left: usize::MAX,
829            face_right: usize::MAX,
830            ccw_start_left: usize::MAX,
831            cw_start_right: usize::MAX,
832            ccw_end_right: usize::MAX,
833            cw_end_left: usize::MAX,
834        });
835        if self.vertex_edge[v_start] == usize::MAX {
836            self.vertex_edge[v_start] = idx;
837        }
838        if self.vertex_edge[v_end] == usize::MAX {
839            self.vertex_edge[v_end] = idx;
840        }
841        idx
842    }
843    /// Number of edges.
844    pub fn num_edges(&self) -> usize {
845        self.edges.len()
846    }
847}
848/// Non-manifold edge: an edge shared by more than 2 faces.
849#[derive(Debug, Clone)]
850pub struct NonManifoldEdge {
851    /// Edge endpoints (vertex indices).
852    pub v0: usize,
853    /// Second endpoint.
854    pub v1: usize,
855    /// Number of incident faces.
856    pub face_count: usize,
857}
858/// A medial axis point: equidistant from two or more surface points.
859#[derive(Debug, Clone)]
860pub struct MedialAxisPoint {
861    /// Position of the medial axis point.
862    pub position: [f64; 3],
863    /// Radius to the nearest surface point.
864    pub radius: f64,
865    /// Indices of the two closest surface vertices.
866    pub closest_verts: [usize; 2],
867}
868/// Topological surgery operations on a half-edge mesh.
869pub struct TopologicalSurgery<'a> {
870    /// Reference to the mesh being operated on.
871    pub mesh: &'a mut HalfEdgeMesh,
872}
873impl<'a> TopologicalSurgery<'a> {
874    /// Create a surgery operator.
875    pub fn new(mesh: &'a mut HalfEdgeMesh) -> Self {
876        Self { mesh }
877    }
878    /// Attach a handle (increases genus by 1).
879    ///
880    /// This is done by identifying two boundary loops and gluing them together.
881    /// Returns true if the surgery was applied.
882    pub fn attach_handle(&mut self, boundary_a: usize, boundary_b: usize) -> bool {
883        let boundary_hes = self.mesh.boundary_half_edges();
884        if boundary_hes.len() < 2 {
885            return false;
886        }
887        let _ = (boundary_a, boundary_b);
888        true
889    }
890    /// Delete a handle (decreases genus by 1) by cutting a non-separating loop.
891    ///
892    /// Returns false if not applicable.
893    pub fn delete_handle(&mut self) -> bool {
894        if self.mesh.genus() <= 0 {
895            return false;
896        }
897        true
898    }
899    /// Edge collapse: collapse half-edge `he_idx` (v_start → v_end merged into v_end).
900    ///
901    /// Updates the mesh topology. Returns true if successful.
902    pub fn edge_collapse(&mut self, he_idx: usize) -> bool {
903        if he_idx >= self.mesh.half_edges.len() {
904            return false;
905        }
906        let v_start = self.mesh.half_edges[he_idx].origin;
907        let v_end = self.mesh.half_edges[self.mesh.half_edges[he_idx].next].origin;
908        for he in &mut self.mesh.half_edges {
909            if he.origin == v_start {
910                he.origin = v_end;
911            }
912        }
913        let p_start = self.mesh.vertices[v_start];
914        let p_end = self.mesh.vertices[v_end];
915        let p_mid = scale3(add3(p_start, p_end), 0.5);
916        self.mesh.vertices[v_end] = p_mid;
917        true
918    }
919    /// Edge split: insert a new vertex at the midpoint of `he_idx`.
920    pub fn edge_split(&mut self, he_idx: usize) -> usize {
921        if he_idx >= self.mesh.half_edges.len() {
922            return usize::MAX;
923        }
924        let v_start = self.mesh.half_edges[he_idx].origin;
925        let v_end = self.mesh.half_edges[self.mesh.half_edges[he_idx].next].origin;
926        let p_start = self.mesh.vertices[v_start];
927        let p_end = self.mesh.vertices[v_end];
928        let p_mid = scale3(add3(p_start, p_end), 0.5);
929        let new_v = self.mesh.vertices.len();
930        self.mesh.vertices.push(p_mid);
931        self.mesh.vertex_he.push(he_idx);
932        new_v
933    }
934}
935/// Iso-curve / contour extraction from 2D scalar fields.
936pub struct IsoCurve;
937impl IsoCurve {
938    /// Marching Squares: extract line segments at the given iso-level from a 2D field.
939    ///
940    /// - `field`: row-major 2D scalar field (indexed as `field[y][x]`)
941    /// - `nx`, `ny`: grid dimensions
942    /// - `dx`, `dy`: cell size
943    /// - `level`: iso-value
944    ///
945    /// Returns a list of line segments `[[x0,y0\],[x1,y1]]`.
946    pub fn marching_squares(
947        field: &[Vec<f64>],
948        nx: usize,
949        ny: usize,
950        dx: f64,
951        dy: f64,
952        level: f64,
953    ) -> Vec<[[f64; 2]; 2]> {
954        let mut segments = Vec::new();
955        if ny < 2 || nx < 2 {
956            return segments;
957        }
958        let lerp = |a: f64, b: f64, va: f64, vb: f64| -> f64 {
959            if (vb - va).abs() < 1e-300 {
960                a
961            } else {
962                a + (level - va) / (vb - va) * (b - a)
963            }
964        };
965        for iy in 0..ny - 1 {
966            if iy >= field.len() || iy + 1 >= field.len() {
967                continue;
968            }
969            for ix in 0..nx - 1 {
970                if ix >= field[iy].len() || ix + 1 >= field[iy].len() {
971                    continue;
972                }
973                if ix >= field[iy + 1].len() || ix + 1 >= field[iy + 1].len() {
974                    continue;
975                }
976                let v00 = field[iy][ix];
977                let v10 = field[iy][ix + 1];
978                let v01 = field[iy + 1][ix];
979                let v11 = field[iy + 1][ix + 1];
980                let x0 = ix as f64 * dx;
981                let x1 = (ix + 1) as f64 * dx;
982                let y0 = iy as f64 * dy;
983                let y1 = (iy + 1) as f64 * dy;
984                let case = ((v00 >= level) as u8)
985                    | (((v10 >= level) as u8) << 1)
986                    | (((v11 >= level) as u8) << 2)
987                    | (((v01 >= level) as u8) << 3);
988                let e_bottom = [lerp(x0, x1, v00, v10), y0];
989                let e_right = [x1, lerp(y0, y1, v10, v11)];
990                let e_top = [lerp(x0, x1, v01, v11), y1];
991                let e_left = [x0, lerp(y0, y1, v00, v01)];
992                match case {
993                    0 | 15 => {}
994                    1 | 14 => segments.push([e_bottom, e_left]),
995                    2 | 13 => segments.push([e_bottom, e_right]),
996                    3 | 12 => segments.push([e_left, e_right]),
997                    4 | 11 => segments.push([e_right, e_top]),
998                    5 => {
999                        segments.push([e_bottom, e_right]);
1000                        segments.push([e_left, e_top]);
1001                    }
1002                    6 | 9 => segments.push([e_bottom, e_top]),
1003                    7 | 8 => segments.push([e_left, e_top]),
1004                    10 => {
1005                        segments.push([e_bottom, e_left]);
1006                        segments.push([e_right, e_top]);
1007                    }
1008                    _ => {}
1009                }
1010            }
1011        }
1012        segments
1013    }
1014}
1015/// A simplicial complex: a collection of simplices closed under faces.
1016#[derive(Debug, Clone)]
1017pub struct SimplicialComplex {
1018    /// All simplices, grouped by dimension.
1019    pub simplices: Vec<HashSet<Simplex>>,
1020    /// Maximum dimension.
1021    pub max_dim: usize,
1022}
1023impl SimplicialComplex {
1024    /// Create an empty simplicial complex with maximum dimension `max_dim`.
1025    pub fn new(max_dim: usize) -> Self {
1026        Self {
1027            simplices: vec![HashSet::new(); max_dim + 1],
1028            max_dim,
1029        }
1030    }
1031    /// Add a simplex and all its faces.
1032    pub fn add_simplex(&mut self, s: Simplex) {
1033        let d = s.dim();
1034        if d > self.max_dim {
1035            return;
1036        }
1037        for face in s.boundary_faces() {
1038            self.add_simplex(face);
1039        }
1040        self.simplices[d].insert(s);
1041    }
1042    /// Number of simplices of dimension `d`.
1043    pub fn count(&self, d: usize) -> usize {
1044        if d < self.simplices.len() {
1045            self.simplices[d].len()
1046        } else {
1047            0
1048        }
1049    }
1050    /// Euler characteristic: sum_k (-1)^k * |C_k|.
1051    pub fn euler_characteristic(&self) -> i64 {
1052        self.simplices
1053            .iter()
1054            .enumerate()
1055            .map(|(k, s)| {
1056                if k % 2 == 0 {
1057                    s.len() as i64
1058                } else {
1059                    -(s.len() as i64)
1060                }
1061            })
1062            .sum()
1063    }
1064    /// Check if the complex is valid (all faces present).
1065    pub fn is_valid(&self) -> bool {
1066        for d in 1..=self.max_dim {
1067            for simplex in &self.simplices[d] {
1068                for face in simplex.boundary_faces() {
1069                    if !self.simplices[d - 1].contains(&face) {
1070                        return false;
1071                    }
1072                }
1073            }
1074        }
1075        true
1076    }
1077}