Skip to main content

oxiphysics_geometry/
subdivision.rs

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