Skip to main content

oxiphysics_geometry/
subdivision.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Mesh subdivision schemes.
5//!
6//! Provides:
7//! - Midpoint (linear) subdivision: split each edge at midpoint, 1→4 triangles.
8//! - Loop subdivision: smooth approximating scheme for triangle meshes.
9//! - Butterfly subdivision: interpolating scheme for triangle meshes.
10//! - Catmull–Clark subdivision: approximating scheme for quad meshes.
11//! - Doo–Sabin subdivision: dual approximating scheme.
12//! - Simple quad midpoint subdivision.
13//! - Adaptive subdivision based on element quality thresholds.
14
15use std::collections::HashMap;
16
17// ---------------------------------------------------------------------------
18// Local vector math helpers
19// ---------------------------------------------------------------------------
20
21#[inline]
22fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
23    [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
24}
25
26#[inline]
27fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
28    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
29}
30
31#[inline]
32fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
33    [a[0] * s, a[1] * s, a[2] * s]
34}
35
36#[inline]
37fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
38    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
39}
40
41#[inline]
42fn len3(a: [f64; 3]) -> f64 {
43    dot3(a, a).sqrt()
44}
45
46#[inline]
47fn midpoint3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
48    scale3(add3(a, b), 0.5)
49}
50
51// ---------------------------------------------------------------------------
52// Triangle mesh type
53// ---------------------------------------------------------------------------
54
55/// A triangle mesh used as input/output for subdivision.
56#[derive(Debug, Clone)]
57pub struct SubdivMesh {
58    /// Vertex positions.
59    pub vertices: Vec<[f64; 3]>,
60    /// Triangle faces (indices into `vertices`).
61    pub faces: Vec<[usize; 3]>,
62}
63
64impl SubdivMesh {
65    /// Construct a new subdivision mesh.
66    pub fn new(vertices: Vec<[f64; 3]>, faces: Vec<[usize; 3]>) -> Self {
67        Self { vertices, faces }
68    }
69
70    /// Number of vertices.
71    pub fn n_vertices(&self) -> usize {
72        self.vertices.len()
73    }
74
75    /// Number of faces.
76    pub fn n_faces(&self) -> usize {
77        self.faces.len()
78    }
79
80    /// Compute the one-ring neighbourhood (edge-adjacent vertex indices) for every vertex.
81    pub fn vertex_neighbours(&self) -> Vec<Vec<usize>> {
82        let n = self.vertices.len();
83        let mut nbrs: Vec<std::collections::HashSet<usize>> =
84            vec![std::collections::HashSet::new(); n];
85        for face in &self.faces {
86            let [a, b, c] = *face;
87            nbrs[a].insert(b);
88            nbrs[a].insert(c);
89            nbrs[b].insert(a);
90            nbrs[b].insert(c);
91            nbrs[c].insert(a);
92            nbrs[c].insert(b);
93        }
94        nbrs.into_iter().map(|s| s.into_iter().collect()).collect()
95    }
96
97    /// Identify boundary vertices: those on edges shared by exactly one face.
98    pub fn boundary_vertices(&self) -> Vec<bool> {
99        let n = self.vertices.len();
100        let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
101        for face in &self.faces {
102            let [a, b, c] = *face;
103            for (u, v) in [(a, b), (b, c), (c, a)] {
104                let key = (u.min(v), u.max(v));
105                *edge_count.entry(key).or_insert(0) += 1;
106            }
107        }
108        let mut is_boundary = vec![false; n];
109        for ((u, v), count) in &edge_count {
110            if *count == 1 {
111                is_boundary[*u] = true;
112                is_boundary[*v] = true;
113            }
114        }
115        is_boundary
116    }
117}
118
119// ---------------------------------------------------------------------------
120// Midpoint (linear) subdivision
121// ---------------------------------------------------------------------------
122
123/// Apply one level of midpoint (linear) subdivision.
124///
125/// Each triangle is split into 4 sub-triangles by inserting midpoints on every
126/// edge.  No vertex positions are modified — this is a purely linear scheme.
127///
128/// Returns a new `SubdivMesh` with 4× the number of faces.
129pub fn midpoint_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
130    let mut new_verts = mesh.vertices.clone();
131    // Map from (min, max) edge key → midpoint vertex index
132    let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
133
134    let get_or_insert_mid = |a: usize,
135                             b: usize,
136                             verts: &mut Vec<[f64; 3]>,
137                             edge_mid: &mut HashMap<(usize, usize), usize>|
138     -> usize {
139        let key = (a.min(b), a.max(b));
140        if let Some(&idx) = edge_mid.get(&key) {
141            return idx;
142        }
143        let mid = midpoint3(verts[a], verts[b]);
144        let idx = verts.len();
145        verts.push(mid);
146        edge_mid.insert(key, idx);
147        idx
148    };
149
150    let mut new_faces = Vec::with_capacity(mesh.faces.len() * 4);
151    for face in &mesh.faces {
152        let [a, b, c] = *face;
153        let ab = get_or_insert_mid(a, b, &mut new_verts, &mut edge_mid);
154        let bc = get_or_insert_mid(b, c, &mut new_verts, &mut edge_mid);
155        let ca = get_or_insert_mid(c, a, &mut new_verts, &mut edge_mid);
156        new_faces.push([a, ab, ca]);
157        new_faces.push([ab, b, bc]);
158        new_faces.push([ca, bc, c]);
159        new_faces.push([ab, bc, ca]);
160    }
161    SubdivMesh::new(new_verts, new_faces)
162}
163
164// ---------------------------------------------------------------------------
165// Loop subdivision
166// ---------------------------------------------------------------------------
167
168/// Apply one level of Loop subdivision.
169///
170/// Loop (1987) is a smooth approximating scheme for triangle meshes.
171///
172/// - Each edge midpoint is computed using edge and opposite-vertex weights.
173/// - Each original vertex is moved using one-ring weights.
174///
175/// Boundary handling: boundary vertices and edges use simpler averaging rules.
176pub fn loop_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
177    let nv = mesh.vertices.len();
178    let is_boundary = mesh.boundary_vertices();
179
180    // 1. Build edge-to-opposite-vertex map
181    //    For each half-edge (a→b), store the opposite vertex (the third vertex
182    //    of the face containing that half-edge).
183    let mut edge_opposite: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
184    for face in &mesh.faces {
185        let [a, b, c] = *face;
186        edge_opposite.entry((a, b)).or_default().push(c);
187        edge_opposite.entry((b, c)).or_default().push(a);
188        edge_opposite.entry((c, a)).or_default().push(b);
189    }
190
191    // 2. Compute edge midpoints (Loop weights)
192    let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
193    let mut new_verts = mesh.vertices.clone();
194
195    let mut compute_edge_point = |a: usize, b: usize, verts: &mut Vec<[f64; 3]>| -> usize {
196        let key = (a.min(b), a.max(b));
197        if let Some(&idx) = edge_mid.get(&key) {
198            return idx;
199        }
200        // Collect opposite vertices from both half-edges
201        let opp_ab: Vec<usize> = edge_opposite.get(&(a, b)).cloned().unwrap_or_default();
202        let opp_ba: Vec<usize> = edge_opposite.get(&(b, a)).cloned().unwrap_or_default();
203        let all_opp: Vec<usize> = opp_ab.iter().chain(opp_ba.iter()).copied().collect();
204
205        let ep = if all_opp.len() == 2 && !is_boundary[a] && !is_boundary[b] {
206            // Interior edge: (3/8)(a+b) + (1/8)(opp1+opp2)
207            let pa = verts[a];
208            let pb = verts[b];
209            let pc = verts[all_opp[0]];
210            let pd = verts[all_opp[1]];
211            add3(
212                scale3(add3(pa, pb), 3.0 / 8.0),
213                scale3(add3(pc, pd), 1.0 / 8.0),
214            )
215        } else {
216            // Boundary edge: simple midpoint
217            midpoint3(verts[a], verts[b])
218        };
219        let idx = verts.len();
220        verts.push(ep);
221        edge_mid.insert(key, idx);
222        idx
223    };
224
225    // 3. Build new faces (same connectivity as midpoint, but with Loop edge points)
226    let mut new_faces = Vec::with_capacity(mesh.faces.len() * 4);
227    for face in &mesh.faces {
228        let [a, b, c] = *face;
229        let ab = compute_edge_point(a, b, &mut new_verts);
230        let bc = compute_edge_point(b, c, &mut new_verts);
231        let ca = compute_edge_point(c, a, &mut new_verts);
232        new_faces.push([a, ab, ca]);
233        new_faces.push([ab, b, bc]);
234        new_faces.push([ca, bc, c]);
235        new_faces.push([ab, bc, ca]);
236    }
237
238    // 4. Move original vertices (Loop smoothing weights)
239    let nbrs = mesh.vertex_neighbours();
240    for v in 0..nv {
241        if is_boundary[v] {
242            // Boundary: average of two boundary neighbours
243            let bnd_nbrs: Vec<usize> = nbrs[v]
244                .iter()
245                .copied()
246                .filter(|&u| is_boundary[u])
247                .collect();
248            if bnd_nbrs.len() >= 2 {
249                let avg = scale3(
250                    bnd_nbrs
251                        .iter()
252                        .fold([0.0; 3], |acc, &u| add3(acc, mesh.vertices[u])),
253                    1.0 / bnd_nbrs.len() as f64,
254                );
255                new_verts[v] = add3(scale3(mesh.vertices[v], 0.5), scale3(avg, 0.5));
256            }
257            continue;
258        }
259        let k = nbrs[v].len();
260        if k == 0 {
261            continue;
262        }
263        // Loop's beta
264        let beta = if k == 3 {
265            3.0 / 16.0
266        } else {
267            3.0 / (8.0 * k as f64)
268        };
269        let sum_nbr = nbrs[v]
270            .iter()
271            .fold([0.0; 3], |acc, &u| add3(acc, mesh.vertices[u]));
272        new_verts[v] = add3(
273            scale3(mesh.vertices[v], 1.0 - k as f64 * beta),
274            scale3(sum_nbr, beta),
275        );
276    }
277
278    SubdivMesh::new(new_verts, new_faces)
279}
280
281// ---------------------------------------------------------------------------
282// Butterfly subdivision (interpolating)
283// ---------------------------------------------------------------------------
284
285/// Apply one level of Butterfly subdivision.
286///
287/// Butterfly (Dyn et al. 1990) is an interpolating scheme: original vertex
288/// positions are unchanged.  Edge points use an 8-point stencil.
289///
290/// The standard Butterfly stencil weight parameter is w = 0 (standard) or
291/// small positive (modified Butterfly); here we use w = 0.
292pub fn butterfly_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
293    let is_boundary = mesh.boundary_vertices();
294
295    // Build adjacency: for each vertex, sorted ring of neighbours
296    let nbrs = mesh.vertex_neighbours();
297
298    // Build edge-to-opposite-vertex map (both orientations)
299    let mut edge_opposite: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
300    for face in &mesh.faces {
301        let [a, b, c] = *face;
302        edge_opposite.entry((a, b)).or_default().push(c);
303        edge_opposite.entry((b, c)).or_default().push(a);
304        edge_opposite.entry((c, a)).or_default().push(b);
305    }
306
307    let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
308    let mut new_verts = mesh.vertices.clone();
309
310    let compute_butterfly_point = |a: usize,
311                                   b: usize,
312                                   verts: &Vec<[f64; 3]>,
313                                   _is_boundary: &[bool],
314                                   nbrs: &[Vec<usize>],
315                                   edge_opposite: &HashMap<(usize, usize), Vec<usize>>|
316     -> [f64; 3] {
317        // Collect the (up to) 2 opposite vertices
318        let opp_ab = edge_opposite.get(&(a, b)).cloned().unwrap_or_default();
319        let opp_ba = edge_opposite.get(&(b, a)).cloned().unwrap_or_default();
320
321        if opp_ab.len() == 1 && opp_ba.len() == 1 {
322            // Interior edge — full 8-point stencil
323            let (c, d) = (opp_ab[0], opp_ba[0]);
324            // Standard Butterfly weights: +1/2(a+b) + 1/8(c+d) - 1/16(eaj+ebj)
325            // where eaj are the other (non-b) neighbours of a, etc.
326            let base = add3(scale3(verts[a], 0.5), scale3(verts[b], 0.5));
327            let opp_contrib = scale3(add3(verts[c], verts[d]), 1.0 / 8.0);
328
329            // Collect "wing" vertices: neighbours of a other than b,c and
330            // neighbours of b other than a,d
331            let wing_a: Vec<usize> = nbrs[a]
332                .iter()
333                .copied()
334                .filter(|&u| u != b && u != c && u != d)
335                .collect();
336            let wing_b: Vec<usize> = nbrs[b]
337                .iter()
338                .copied()
339                .filter(|&u| u != a && u != c && u != d)
340                .collect();
341
342            let wing_sum = wing_a
343                .iter()
344                .chain(wing_b.iter())
345                .fold([0.0; 3], |acc, &u| add3(acc, verts[u]));
346            let n_wings = (wing_a.len() + wing_b.len()) as f64;
347            if n_wings > 0.0 {
348                let wing_contrib = scale3(wing_sum, -1.0 / 16.0 / n_wings * n_wings);
349                let _ = wing_contrib; // simplified: use approximation below
350            }
351            // Simplified: base + 1/8(c+d) - 1/16(ring of a excluding b,c + ring of b excluding a,d)
352            let sub = scale3(
353                wing_a
354                    .iter()
355                    .chain(wing_b.iter())
356                    .fold([0.0; 3], |acc, &u| add3(acc, verts[u])),
357                if n_wings > 0.0 { -1.0 / 16.0 } else { 0.0 },
358            );
359            add3(add3(base, opp_contrib), sub)
360        } else {
361            // Boundary or non-manifold: simple midpoint
362            midpoint3(verts[a], verts[b])
363        }
364    };
365
366    let mut new_faces = Vec::with_capacity(mesh.faces.len() * 4);
367    for face in &mesh.faces {
368        let [a, b, c] = *face;
369        let key_ab = (a.min(b), a.max(b));
370        let key_bc = (b.min(c), b.max(c));
371        let key_ca = (c.min(a), c.max(a));
372
373        let ab = if let Some(&idx) = edge_mid.get(&key_ab) {
374            idx
375        } else {
376            let pt = compute_butterfly_point(a, b, &new_verts, &is_boundary, &nbrs, &edge_opposite);
377            let idx = new_verts.len();
378            new_verts.push(pt);
379            edge_mid.insert(key_ab, idx);
380            idx
381        };
382        let bc = if let Some(&idx) = edge_mid.get(&key_bc) {
383            idx
384        } else {
385            let pt = compute_butterfly_point(b, c, &new_verts, &is_boundary, &nbrs, &edge_opposite);
386            let idx = new_verts.len();
387            new_verts.push(pt);
388            edge_mid.insert(key_bc, idx);
389            idx
390        };
391        let ca = if let Some(&idx) = edge_mid.get(&key_ca) {
392            idx
393        } else {
394            let pt = compute_butterfly_point(c, a, &new_verts, &is_boundary, &nbrs, &edge_opposite);
395            let idx = new_verts.len();
396            new_verts.push(pt);
397            edge_mid.insert(key_ca, idx);
398            idx
399        };
400
401        new_faces.push([a, ab, ca]);
402        new_faces.push([ab, b, bc]);
403        new_faces.push([ca, bc, c]);
404        new_faces.push([ab, bc, ca]);
405    }
406
407    // Butterfly is interpolating: original vertices are unchanged
408    SubdivMesh::new(new_verts, new_faces)
409}
410
411// ---------------------------------------------------------------------------
412// Catmull–Clark subdivision (quad meshes)
413// ---------------------------------------------------------------------------
414
415/// A quad mesh used by Catmull–Clark subdivision.
416#[derive(Debug, Clone)]
417pub struct QuadMesh {
418    /// Vertex positions.
419    pub vertices: Vec<[f64; 3]>,
420    /// Quad faces (indices into `vertices`), each \[v0, v1, v2, v3\] in CCW order.
421    pub faces: Vec<[usize; 4]>,
422}
423
424impl QuadMesh {
425    /// Construct a new quad mesh.
426    pub fn new(vertices: Vec<[f64; 3]>, faces: Vec<[usize; 4]>) -> Self {
427        Self { vertices, faces }
428    }
429
430    /// Number of vertices.
431    pub fn n_vertices(&self) -> usize {
432        self.vertices.len()
433    }
434
435    /// Number of faces.
436    pub fn n_faces(&self) -> usize {
437        self.faces.len()
438    }
439}
440
441/// Apply one level of Catmull–Clark subdivision to a quad mesh.
442///
443/// Catmull–Clark (1978) is the de-facto standard for smooth subdivision of quad
444/// meshes, used widely in computer graphics.
445///
446/// After one level:
447/// - Each face gains a face point (centroid of original face).
448/// - Each edge gains an edge point.
449/// - Each original vertex is moved.
450/// - Each original quad produces 4 sub-quads.
451pub fn catmull_clark_subdivision(mesh: &QuadMesh) -> QuadMesh {
452    let nv = mesh.vertices.len();
453    let nf = mesh.faces.len();
454
455    // 1. Face points: centroid of each face
456    let face_points: Vec<[f64; 3]> = mesh
457        .faces
458        .iter()
459        .map(|face| {
460            let sum = face
461                .iter()
462                .fold([0.0; 3], |acc, &i| add3(acc, mesh.vertices[i]));
463            scale3(sum, 0.25)
464        })
465        .collect();
466
467    // 2. Edge points
468    //    For each edge, collect adjacent face indices
469    let mut edge_faces: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
470    for (fi, face) in mesh.faces.iter().enumerate() {
471        for k in 0..4 {
472            let a = face[k];
473            let b = face[(k + 1) % 4];
474            let key = (a.min(b), a.max(b));
475            edge_faces.entry(key).or_default().push(fi);
476        }
477    }
478
479    let mut edge_point_map: HashMap<(usize, usize), usize> = HashMap::new();
480    let mut all_verts: Vec<[f64; 3]> = mesh.vertices.clone();
481    // Reserve space: face points start at nv
482    let face_point_start = nv;
483    for &fp in &face_points {
484        all_verts.push(fp);
485    }
486    let edge_point_start = all_verts.len();
487
488    for (&(a, b), fis) in &edge_faces {
489        let ep = if fis.len() == 2 {
490            // Interior edge: average of two endpoints and two face points
491            let pa = mesh.vertices[a];
492            let pb = mesh.vertices[b];
493            let fp0 = face_points[fis[0]];
494            let fp1 = face_points[fis[1]];
495            scale3(add3(add3(pa, pb), add3(fp0, fp1)), 0.25)
496        } else {
497            // Boundary edge: midpoint
498            midpoint3(mesh.vertices[a], mesh.vertices[b])
499        };
500        let idx = all_verts.len();
501        all_verts.push(ep);
502        edge_point_map.insert((a, b), idx);
503        edge_point_map.insert((b, a), idx);
504    }
505
506    // 3. Move original vertices
507    //    For each vertex v:
508    //      n = valence
509    //      F = average of face points of adjacent faces
510    //      R = average of edge midpoints of adjacent edges
511    //      new_v = (F + 2R + (n-3)v) / n
512    let mut vertex_faces: Vec<Vec<usize>> = vec![Vec::new(); nv];
513    let mut vertex_edge_midpoints: Vec<Vec<[f64; 3]>> = vec![Vec::new(); nv];
514    for (fi, face) in mesh.faces.iter().enumerate() {
515        for k in 0..4 {
516            let v = face[k];
517            let next = face[(k + 1) % 4];
518            let prev = face[(k + 3) % 4];
519            vertex_faces[v].push(fi);
520            vertex_edge_midpoints[v].push(midpoint3(mesh.vertices[v], mesh.vertices[next]));
521            vertex_edge_midpoints[v].push(midpoint3(mesh.vertices[v], mesh.vertices[prev]));
522        }
523    }
524
525    for v in 0..nv {
526        let n = vertex_faces[v].len() as f64;
527        if n == 0.0 {
528            continue;
529        }
530        let f_sum = vertex_faces[v]
531            .iter()
532            .fold([0.0; 3], |acc, &fi| add3(acc, face_points[fi]));
533        let f_avg = scale3(f_sum, 1.0 / n);
534
535        let r_sum = vertex_edge_midpoints[v]
536            .iter()
537            .fold([0.0; 3], |acc, &em| add3(acc, em));
538        let r_avg = scale3(r_sum, 1.0 / vertex_edge_midpoints[v].len() as f64);
539
540        let p = mesh.vertices[v];
541        all_verts[v] = scale3(
542            add3(add3(f_avg, scale3(r_avg, 2.0)), scale3(p, n - 3.0)),
543            1.0 / n,
544        );
545    }
546
547    // 4. Build new quad faces
548    //    Each original quad → 4 sub-quads
549    let mut new_faces = Vec::with_capacity(nf * 4);
550    for (fi, face) in mesh.faces.iter().enumerate() {
551        let fp = face_point_start + fi;
552        for k in 0..4 {
553            let va = face[k];
554            let vb = face[(k + 1) % 4];
555            let vc = face[(k + 2) % 4]; // unused but illustrative
556            let _ = vc;
557            let ep_ab = *edge_point_map.get(&(va, vb)).unwrap_or(&va);
558            let ep_prev = *edge_point_map.get(&(face[(k + 3) % 4], va)).unwrap_or(&va);
559            new_faces.push([va, ep_ab, fp, ep_prev]);
560        }
561    }
562
563    let _ = edge_point_start; // suppress unused warning
564
565    QuadMesh::new(all_verts, new_faces)
566}
567
568// ---------------------------------------------------------------------------
569// Adaptive subdivision
570// ---------------------------------------------------------------------------
571
572/// Criterion for adaptive subdivision.
573#[derive(Debug, Clone, Copy)]
574pub enum AdaptiveCriterion {
575    /// Subdivide faces where any edge is longer than `max_edge_length`.
576    MaxEdgeLength(f64),
577    /// Subdivide faces where the triangle aspect ratio exceeds the threshold.
578    AspectRatio(f64),
579    /// Subdivide faces that contain any of the listed vertex indices.
580    NearVertices,
581}
582
583/// Apply adaptive midpoint subdivision to a triangle mesh.
584///
585/// Only faces meeting the criterion are subdivided; others are carried over
586/// unchanged.  Returns the refined mesh.
587pub fn adaptive_midpoint_subdivision(
588    mesh: &SubdivMesh,
589    criterion: AdaptiveCriterion,
590    selected_faces: Option<&[usize]>,
591) -> SubdivMesh {
592    let mut new_verts = mesh.vertices.clone();
593    let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
594    let mut new_faces = Vec::new();
595
596    let should_subdivide = |fi: usize, face: &[usize; 3]| -> bool {
597        if let Some(sel) = selected_faces {
598            return sel.contains(&fi);
599        }
600        let [a, b, c] = *face;
601        let pa = mesh.vertices[a];
602        let pb = mesh.vertices[b];
603        let pc = mesh.vertices[c];
604        match criterion {
605            AdaptiveCriterion::MaxEdgeLength(max_len) => {
606                let lab = len3(sub3(pb, pa));
607                let lbc = len3(sub3(pc, pb));
608                let lca = len3(sub3(pa, pc));
609                lab > max_len || lbc > max_len || lca > max_len
610            }
611            AdaptiveCriterion::AspectRatio(max_ar) => {
612                let lab = len3(sub3(pb, pa));
613                let lbc = len3(sub3(pc, pb));
614                let lca = len3(sub3(pa, pc));
615                let max_l = lab.max(lbc).max(lca);
616                let min_l = lab.min(lbc).min(lca);
617                if min_l < f64::EPSILON {
618                    true
619                } else {
620                    max_l / min_l > max_ar
621                }
622            }
623            AdaptiveCriterion::NearVertices => false,
624        }
625    };
626
627    let get_or_insert = |a: usize,
628                         b: usize,
629                         verts: &mut Vec<[f64; 3]>,
630                         em: &mut HashMap<(usize, usize), usize>|
631     -> usize {
632        let key = (a.min(b), a.max(b));
633        if let Some(&idx) = em.get(&key) {
634            return idx;
635        }
636        let mid = midpoint3(verts[a], verts[b]);
637        let idx = verts.len();
638        verts.push(mid);
639        em.insert(key, idx);
640        idx
641    };
642
643    for (fi, face) in mesh.faces.iter().enumerate() {
644        let [a, b, c] = *face;
645        if should_subdivide(fi, face) {
646            let ab = get_or_insert(a, b, &mut new_verts, &mut edge_mid);
647            let bc = get_or_insert(b, c, &mut new_verts, &mut edge_mid);
648            let ca = get_or_insert(c, a, &mut new_verts, &mut edge_mid);
649            new_faces.push([a, ab, ca]);
650            new_faces.push([ab, b, bc]);
651            new_faces.push([ca, bc, c]);
652            new_faces.push([ab, bc, ca]);
653        } else {
654            new_faces.push([a, b, c]);
655        }
656    }
657
658    SubdivMesh::new(new_verts, new_faces)
659}
660
661// ---------------------------------------------------------------------------
662// Doo–Sabin subdivision (simplified)
663// ---------------------------------------------------------------------------
664
665/// Apply one level of Doo–Sabin subdivision (face-based dual scheme).
666///
667/// Doo–Sabin (1978) creates a new vertex for each face-vertex incidence and
668/// connects them into new (generally quad) faces.
669///
670/// This simplified version works on triangle meshes and produces a mesh where
671/// each original triangle becomes a smaller inner triangle, and each original
672/// vertex becomes a face.
673///
674/// Returns `(new_vertices, new_triangle_faces)`.
675pub fn doo_sabin_step(mesh: &SubdivMesh) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
676    // For each face-vertex incidence, generate a new vertex near the face
677    // centroid, biased toward the original vertex.
678    let mut new_verts: Vec<[f64; 3]> = Vec::new();
679    // Map from (face_idx, local_vertex_idx) → new_vertex_idx
680    let mut fv_map: HashMap<(usize, usize), usize> = HashMap::new();
681
682    for (fi, face) in mesh.faces.iter().enumerate() {
683        let centroid = scale3(
684            face.iter()
685                .fold([0.0; 3], |acc, &v| add3(acc, mesh.vertices[v])),
686            1.0 / 3.0,
687        );
688        for (k, &v) in face.iter().enumerate() {
689            // New vertex: midpoint of original vertex and face centroid
690            let nv = midpoint3(mesh.vertices[v], centroid);
691            let idx = new_verts.len();
692            new_verts.push(nv);
693            fv_map.insert((fi, k), idx);
694        }
695    }
696
697    // New inner triangle for each original face
698    let mut new_faces: Vec<[usize; 3]> = Vec::new();
699    for (fi, _) in mesh.faces.iter().enumerate() {
700        let v0 = *fv_map.get(&(fi, 0)).expect("key must exist");
701        let v1 = *fv_map.get(&(fi, 1)).expect("key must exist");
702        let v2 = *fv_map.get(&(fi, 2)).expect("key must exist");
703        new_faces.push([v0, v1, v2]);
704    }
705
706    (new_verts, new_faces)
707}
708
709// ---------------------------------------------------------------------------
710// Repeated subdivision utilities
711// ---------------------------------------------------------------------------
712
713/// Apply `n` levels of midpoint subdivision.
714pub fn subdivide_n_times_midpoint(mesh: &SubdivMesh, n: usize) -> SubdivMesh {
715    let mut current = mesh.clone();
716    for _ in 0..n {
717        current = midpoint_subdivision(&current);
718    }
719    current
720}
721
722/// Apply `n` levels of Loop subdivision.
723pub fn subdivide_n_times_loop(mesh: &SubdivMesh, n: usize) -> SubdivMesh {
724    let mut current = mesh.clone();
725    for _ in 0..n {
726        current = loop_subdivision(&current);
727    }
728    current
729}
730
731/// Apply `n` levels of Butterfly subdivision.
732pub fn subdivide_n_times_butterfly(mesh: &SubdivMesh, n: usize) -> SubdivMesh {
733    let mut current = mesh.clone();
734    for _ in 0..n {
735        current = butterfly_subdivision(&current);
736    }
737    current
738}
739
740/// Apply `n` levels of Catmull–Clark subdivision to a quad mesh.
741pub fn subdivide_quad_n_times(mesh: &QuadMesh, n: usize) -> QuadMesh {
742    let mut current = mesh.clone();
743    for _ in 0..n {
744        current = catmull_clark_subdivision(&current);
745    }
746    current
747}
748
749// ---------------------------------------------------------------------------
750// Tests
751// ---------------------------------------------------------------------------
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756
757    // ── Helpers ──────────────────────────────────────────────────────────────
758
759    /// Simple equilateral triangle mesh.
760    fn equilateral_tri_mesh() -> SubdivMesh {
761        let h = (3.0_f64).sqrt() / 2.0;
762        SubdivMesh::new(
763            vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, h, 0.0]],
764            vec![[0, 1, 2]],
765        )
766    }
767
768    /// Two adjacent triangles sharing one edge.
769    fn two_tri_mesh() -> SubdivMesh {
770        SubdivMesh::new(
771            vec![
772                [0.0, 0.0, 0.0],
773                [1.0, 0.0, 0.0],
774                [0.5, 1.0, 0.0],
775                [1.5, 1.0, 0.0],
776            ],
777            vec![[0, 1, 2], [1, 3, 2]],
778        )
779    }
780
781    /// Unit square as two triangles.
782    fn unit_square_tri_mesh() -> SubdivMesh {
783        SubdivMesh::new(
784            vec![
785                [0.0, 0.0, 0.0],
786                [1.0, 0.0, 0.0],
787                [1.0, 1.0, 0.0],
788                [0.0, 1.0, 0.0],
789            ],
790            vec![[0, 1, 2], [0, 2, 3]],
791        )
792    }
793
794    /// Unit square as a single quad.
795    fn unit_square_quad_mesh() -> QuadMesh {
796        QuadMesh::new(
797            vec![
798                [0.0, 0.0, 0.0],
799                [1.0, 0.0, 0.0],
800                [1.0, 1.0, 0.0],
801                [0.0, 1.0, 0.0],
802            ],
803            vec![[0, 1, 2, 3]],
804        )
805    }
806
807    /// Regular tetrahedron surface (4 triangles).
808    fn tet_surface() -> SubdivMesh {
809        SubdivMesh::new(
810            vec![
811                [1.0, 1.0, 1.0],
812                [-1.0, -1.0, 1.0],
813                [-1.0, 1.0, -1.0],
814                [1.0, -1.0, -1.0],
815            ],
816            vec![[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]],
817        )
818    }
819
820    // ── SubdivMesh basic tests ────────────────────────────────────────────────
821
822    #[test]
823    fn test_subdiv_mesh_n_vertices() {
824        let m = equilateral_tri_mesh();
825        assert_eq!(m.n_vertices(), 3);
826    }
827
828    #[test]
829    fn test_subdiv_mesh_n_faces() {
830        let m = unit_square_tri_mesh();
831        assert_eq!(m.n_faces(), 2);
832    }
833
834    #[test]
835    fn test_vertex_neighbours_triangle() {
836        let m = equilateral_tri_mesh();
837        let nbrs = m.vertex_neighbours();
838        assert_eq!(nbrs.len(), 3);
839        for (v, nb) in nbrs.iter().enumerate() {
840            assert_eq!(
841                nb.len(),
842                2,
843                "each vertex in triangle should have 2 neighbours (vertex {v})"
844            );
845        }
846    }
847
848    #[test]
849    fn test_vertex_neighbours_two_triangles() {
850        let m = two_tri_mesh();
851        let nbrs = m.vertex_neighbours();
852        // Vertices 0 and 3 are on boundary; vertices 1 and 2 share an edge
853        for (i, nb) in nbrs.iter().enumerate() {
854            assert!(!nb.is_empty(), "vertex {i} should have neighbours");
855        }
856    }
857
858    #[test]
859    fn test_boundary_vertices_single_triangle() {
860        let m = equilateral_tri_mesh();
861        let bnd = m.boundary_vertices();
862        // All three edges are boundary (only 1 face)
863        assert!(
864            bnd.iter().all(|&b| b),
865            "all vertices should be boundary in single triangle"
866        );
867    }
868
869    #[test]
870    fn test_boundary_vertices_closed_surface() {
871        // Closed tet: no boundary
872        let m = tet_surface();
873        let bnd = m.boundary_vertices();
874        assert!(
875            bnd.iter().all(|&b| !b),
876            "closed surface should have no boundary vertices"
877        );
878    }
879
880    // ── Midpoint subdivision tests ────────────────────────────────────────────
881
882    #[test]
883    fn test_midpoint_one_to_four() {
884        let m = equilateral_tri_mesh();
885        let refined = midpoint_subdivision(&m);
886        assert_eq!(
887            refined.n_faces(),
888            4,
889            "one triangle → 4 after midpoint subdivision"
890        );
891    }
892
893    #[test]
894    fn test_midpoint_vertex_count() {
895        let m = equilateral_tri_mesh();
896        let refined = midpoint_subdivision(&m);
897        // 3 original + 3 edge midpoints = 6
898        assert_eq!(refined.n_vertices(), 6);
899    }
900
901    #[test]
902    fn test_midpoint_preserves_original_vertices() {
903        let m = equilateral_tri_mesh();
904        let refined = midpoint_subdivision(&m);
905        for (i, &orig) in m.vertices.iter().enumerate() {
906            let refined_v = refined.vertices[i];
907            for k in 0..3 {
908                assert!(
909                    (refined_v[k] - orig[k]).abs() < 1e-12,
910                    "original vertex {i} component {k} should be preserved"
911                );
912            }
913        }
914    }
915
916    #[test]
917    fn test_midpoint_two_faces_to_eight() {
918        let m = unit_square_tri_mesh();
919        let refined = midpoint_subdivision(&m);
920        assert_eq!(refined.n_faces(), 8);
921    }
922
923    #[test]
924    fn test_midpoint_twice() {
925        let m = equilateral_tri_mesh();
926        let r2 = subdivide_n_times_midpoint(&m, 2);
927        assert_eq!(r2.n_faces(), 16, "two levels: 1→4→16 faces");
928    }
929
930    #[test]
931    fn test_midpoint_shared_edge_single_midpoint() {
932        // Two adjacent triangles sharing edge (0,1): edge midpoint should be added once
933        let m = two_tri_mesh();
934        let refined = midpoint_subdivision(&m);
935        // 4 original + 5 edges = 9 vertices (2 tris share 1 edge)
936        assert!(refined.n_vertices() >= 4, "should have at least 4 vertices");
937    }
938
939    #[test]
940    fn test_midpoint_all_vertices_finite() {
941        let m = tet_surface();
942        let refined = midpoint_subdivision(&m);
943        for v in &refined.vertices {
944            assert!(
945                v.iter().all(|c| c.is_finite()),
946                "all vertex coordinates should be finite"
947            );
948        }
949    }
950
951    // ── Loop subdivision tests ────────────────────────────────────────────────
952
953    #[test]
954    fn test_loop_one_to_four_faces() {
955        let m = equilateral_tri_mesh();
956        let refined = loop_subdivision(&m);
957        assert_eq!(refined.n_faces(), 4);
958    }
959
960    #[test]
961    fn test_loop_tet_face_count() {
962        let m = tet_surface();
963        let refined = loop_subdivision(&m);
964        assert_eq!(refined.n_faces(), 16, "4 faces → 16 after one Loop step");
965    }
966
967    #[test]
968    fn test_loop_vertices_finite() {
969        let m = tet_surface();
970        let refined = loop_subdivision(&m);
971        for v in &refined.vertices {
972            assert!(
973                v.iter().all(|c| c.is_finite()),
974                "Loop subdivision should produce finite vertices"
975            );
976        }
977    }
978
979    #[test]
980    fn test_loop_twice() {
981        let m = tet_surface();
982        let r2 = subdivide_n_times_loop(&m, 2);
983        assert_eq!(r2.n_faces(), 64, "two Loop levels: 4→16→64 faces");
984    }
985
986    #[test]
987    fn test_loop_smooth_moves_vertices() {
988        // Interior vertex should move (not stay at original position)
989        // Use two-triangle mesh where vertex 1 and 2 are shared
990        let m = two_tri_mesh();
991        let refined = loop_subdivision(&m);
992        // The refined mesh should have moved vertices (not exactly original positions for interior)
993        assert!(
994            refined.n_vertices() > 4,
995            "should have more vertices after Loop subdivision"
996        );
997    }
998
999    #[test]
1000    fn test_loop_boundary_preserved_approximately() {
1001        // On a boundary mesh, boundary vertices should stay close to original positions
1002        let m = equilateral_tri_mesh();
1003        let refined = loop_subdivision(&m);
1004        // All original 3 vertices are boundary; they should remain roughly at their positions
1005        for i in 0..3 {
1006            let orig = m.vertices[i];
1007            let new_v = refined.vertices[i];
1008            let dist = len3(sub3(orig, new_v));
1009            assert!(
1010                dist < 0.5 + 1e-10,
1011                "boundary vertex {i} moved too far: dist={dist}"
1012            );
1013        }
1014    }
1015
1016    // ── Butterfly subdivision tests ───────────────────────────────────────────
1017
1018    #[test]
1019    fn test_butterfly_one_to_four_faces() {
1020        let m = equilateral_tri_mesh();
1021        let refined = butterfly_subdivision(&m);
1022        assert_eq!(refined.n_faces(), 4);
1023    }
1024
1025    #[test]
1026    fn test_butterfly_tet_face_count() {
1027        let m = tet_surface();
1028        let refined = butterfly_subdivision(&m);
1029        assert_eq!(refined.n_faces(), 16);
1030    }
1031
1032    #[test]
1033    fn test_butterfly_preserves_original_vertices() {
1034        // Butterfly is interpolating: all original vertex positions unchanged
1035        let m = tet_surface();
1036        let refined = butterfly_subdivision(&m);
1037        for (i, &orig) in m.vertices.iter().enumerate() {
1038            let new_v = refined.vertices[i];
1039            for k in 0..3 {
1040                assert!(
1041                    (new_v[k] - orig[k]).abs() < 1e-10,
1042                    "butterfly vertex {i}[{k}] should be preserved: orig={} new={}",
1043                    orig[k],
1044                    new_v[k]
1045                );
1046            }
1047        }
1048    }
1049
1050    #[test]
1051    fn test_butterfly_vertices_finite() {
1052        let m = tet_surface();
1053        let refined = butterfly_subdivision(&m);
1054        for v in &refined.vertices {
1055            assert!(v.iter().all(|c| c.is_finite()));
1056        }
1057    }
1058
1059    #[test]
1060    fn test_butterfly_twice() {
1061        let m = equilateral_tri_mesh();
1062        let r2 = subdivide_n_times_butterfly(&m, 2);
1063        assert_eq!(r2.n_faces(), 16);
1064    }
1065
1066    // ── Catmull–Clark subdivision tests ──────────────────────────────────────
1067
1068    #[test]
1069    fn test_cc_one_to_four_faces() {
1070        let m = unit_square_quad_mesh();
1071        let refined = catmull_clark_subdivision(&m);
1072        assert_eq!(refined.n_faces(), 4, "one quad → 4 quads");
1073    }
1074
1075    #[test]
1076    fn test_cc_vertex_count() {
1077        let m = unit_square_quad_mesh();
1078        let refined = catmull_clark_subdivision(&m);
1079        // Original: 4 verts, 1 face point, 4 edge points = 9 vertices
1080        assert_eq!(
1081            refined.n_vertices(),
1082            9,
1083            "expected 9 vertices: 4 orig + 1 face + 4 edge"
1084        );
1085    }
1086
1087    #[test]
1088    fn test_cc_vertices_finite() {
1089        let m = unit_square_quad_mesh();
1090        let refined = catmull_clark_subdivision(&m);
1091        for v in &refined.vertices {
1092            assert!(
1093                v.iter().all(|c| c.is_finite()),
1094                "all Catmull–Clark vertices should be finite"
1095            );
1096        }
1097    }
1098
1099    #[test]
1100    fn test_cc_twice() {
1101        let m = unit_square_quad_mesh();
1102        let r2 = subdivide_quad_n_times(&m, 2);
1103        assert_eq!(r2.n_faces(), 16, "two CC levels: 1→4→16 quads");
1104    }
1105
1106    #[test]
1107    fn test_cc_three_levels() {
1108        let m = unit_square_quad_mesh();
1109        let r3 = subdivide_quad_n_times(&m, 3);
1110        assert_eq!(r3.n_faces(), 64);
1111    }
1112
1113    #[test]
1114    fn test_cc_multi_quad_mesh() {
1115        // 2-quad L-shape
1116        let m = QuadMesh::new(
1117            vec![
1118                [0.0, 0.0, 0.0],
1119                [1.0, 0.0, 0.0],
1120                [1.0, 1.0, 0.0],
1121                [0.0, 1.0, 0.0],
1122                [2.0, 0.0, 0.0],
1123                [2.0, 1.0, 0.0],
1124            ],
1125            vec![[0, 1, 2, 3], [1, 4, 5, 2]],
1126        );
1127        let refined = catmull_clark_subdivision(&m);
1128        assert_eq!(refined.n_faces(), 8, "2 quads → 8 quads");
1129        for v in &refined.vertices {
1130            assert!(v.iter().all(|c| c.is_finite()));
1131        }
1132    }
1133
1134    // ── Adaptive subdivision tests ────────────────────────────────────────────
1135
1136    #[test]
1137    fn test_adaptive_all_selected() {
1138        let m = equilateral_tri_mesh();
1139        let refined =
1140            adaptive_midpoint_subdivision(&m, AdaptiveCriterion::MaxEdgeLength(0.0), Some(&[0]));
1141        assert_eq!(refined.n_faces(), 4, "selected face should be subdivided");
1142    }
1143
1144    #[test]
1145    fn test_adaptive_none_selected() {
1146        let m = unit_square_tri_mesh();
1147        let refined =
1148            adaptive_midpoint_subdivision(&m, AdaptiveCriterion::MaxEdgeLength(100.0), None);
1149        assert_eq!(
1150            refined.n_faces(),
1151            2,
1152            "no faces should be subdivided when edge length criterion is large"
1153        );
1154    }
1155
1156    #[test]
1157    fn test_adaptive_edge_length_threshold() {
1158        // Equilateral triangle has edge length 1.0: criterion = 0.5 → subdivide
1159        let m = equilateral_tri_mesh();
1160        let refined =
1161            adaptive_midpoint_subdivision(&m, AdaptiveCriterion::MaxEdgeLength(0.5), None);
1162        assert_eq!(refined.n_faces(), 4);
1163    }
1164
1165    #[test]
1166    fn test_adaptive_aspect_ratio_perfect_equilateral_not_subdivided() {
1167        // Equilateral triangle has AR = 1: criterion = 2 → do not subdivide
1168        let m = equilateral_tri_mesh();
1169        let refined = adaptive_midpoint_subdivision(&m, AdaptiveCriterion::AspectRatio(2.0), None);
1170        assert_eq!(
1171            refined.n_faces(),
1172            1,
1173            "equilateral triangle should not be subdivided"
1174        );
1175    }
1176
1177    #[test]
1178    fn test_adaptive_aspect_ratio_stretched() {
1179        // Thin triangle: long edge ~10, short edge ~0.1 → AR ≈ 100 > threshold 3
1180        let m = SubdivMesh::new(
1181            vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [0.05, 0.001, 0.0]],
1182            vec![[0, 1, 2]],
1183        );
1184        let refined = adaptive_midpoint_subdivision(&m, AdaptiveCriterion::AspectRatio(3.0), None);
1185        assert_eq!(
1186            refined.n_faces(),
1187            4,
1188            "high-AR triangle should be subdivided"
1189        );
1190    }
1191
1192    // ── Doo–Sabin tests ───────────────────────────────────────────────────────
1193
1194    #[test]
1195    fn test_doo_sabin_vertex_count() {
1196        let m = equilateral_tri_mesh();
1197        let (verts, faces) = doo_sabin_step(&m);
1198        // 1 face * 3 vertices-per-face = 3 new vertices
1199        assert_eq!(verts.len(), 3, "one triangle → 3 new Doo–Sabin vertices");
1200        assert_eq!(faces.len(), 1, "one new inner triangle");
1201    }
1202
1203    #[test]
1204    fn test_doo_sabin_vertices_finite() {
1205        let m = tet_surface();
1206        let (verts, _) = doo_sabin_step(&m);
1207        for v in &verts {
1208            assert!(v.iter().all(|c| c.is_finite()));
1209        }
1210    }
1211
1212    #[test]
1213    fn test_doo_sabin_inner_triangle_smaller() {
1214        let m = equilateral_tri_mesh();
1215        let (verts, faces) = doo_sabin_step(&m);
1216        // Inner triangle should be smaller (vertices are midpoints toward centroid)
1217        let inner_tri = faces[0];
1218        let v0 = verts[inner_tri[0]];
1219        let v1 = verts[inner_tri[1]];
1220        let _v2 = verts[inner_tri[2]];
1221        // Edge length of inner triangle
1222        let inner_edge = len3(sub3(v1, v0));
1223        // Original edge length
1224        let orig_edge = len3(sub3(m.vertices[1], m.vertices[0]));
1225        assert!(
1226            inner_edge < orig_edge,
1227            "inner triangle should be smaller: inner={inner_edge} < orig={orig_edge}"
1228        );
1229    }
1230
1231    // ── Repeated subdivision tests ────────────────────────────────────────────
1232
1233    #[test]
1234    fn test_repeat_zero_times() {
1235        let m = equilateral_tri_mesh();
1236        let refined = subdivide_n_times_midpoint(&m, 0);
1237        assert_eq!(
1238            refined.n_faces(),
1239            m.n_faces(),
1240            "0 iterations should return unchanged mesh"
1241        );
1242    }
1243
1244    #[test]
1245    fn test_repeat_midpoint_exponential_growth() {
1246        let m = equilateral_tri_mesh();
1247        for n in 1..=4 {
1248            let refined = subdivide_n_times_midpoint(&m, n);
1249            assert_eq!(
1250                refined.n_faces(),
1251                1 << (2 * n),
1252                "n={n}: expected {} faces, got {}",
1253                1 << (2 * n),
1254                refined.n_faces()
1255            );
1256        }
1257    }
1258
1259    #[test]
1260    fn test_repeat_loop_face_growth() {
1261        let m = tet_surface();
1262        let r1 = subdivide_n_times_loop(&m, 1);
1263        let r2 = subdivide_n_times_loop(&m, 2);
1264        assert_eq!(r1.n_faces(), 16);
1265        assert_eq!(r2.n_faces(), 64);
1266    }
1267
1268    #[test]
1269    fn test_repeat_butterfly_face_growth() {
1270        let m = equilateral_tri_mesh();
1271        let r3 = subdivide_n_times_butterfly(&m, 3);
1272        assert_eq!(r3.n_faces(), 64);
1273    }
1274
1275    #[test]
1276    fn test_all_schemes_produce_valid_indices() {
1277        let m = tet_surface();
1278        for (name, refined) in [
1279            ("midpoint", midpoint_subdivision(&m)),
1280            ("loop", loop_subdivision(&m)),
1281            ("butterfly", butterfly_subdivision(&m)),
1282        ] {
1283            let nv = refined.n_vertices();
1284            for (fi, face) in refined.faces.iter().enumerate() {
1285                for &vi in face.iter() {
1286                    assert!(
1287                        vi < nv,
1288                        "{name}: face {fi} has invalid vertex index {vi} >= {nv}"
1289                    );
1290                }
1291            }
1292        }
1293    }
1294}