1use std::collections::HashMap;
16
17#[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#[derive(Debug, Clone)]
57pub struct SubdivMesh {
58 pub vertices: Vec<[f64; 3]>,
60 pub faces: Vec<[usize; 3]>,
62}
63
64impl SubdivMesh {
65 pub fn new(vertices: Vec<[f64; 3]>, faces: Vec<[usize; 3]>) -> Self {
67 Self { vertices, faces }
68 }
69
70 pub fn n_vertices(&self) -> usize {
72 self.vertices.len()
73 }
74
75 pub fn n_faces(&self) -> usize {
77 self.faces.len()
78 }
79
80 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 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
119pub fn midpoint_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
130 let mut new_verts = mesh.vertices.clone();
131 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
164pub fn loop_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
177 let nv = mesh.vertices.len();
178 let is_boundary = mesh.boundary_vertices();
179
180 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 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 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 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 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 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 let nbrs = mesh.vertex_neighbours();
240 for v in 0..nv {
241 if is_boundary[v] {
242 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 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
281pub fn butterfly_subdivision(mesh: &SubdivMesh) -> SubdivMesh {
293 let is_boundary = mesh.boundary_vertices();
294
295 let nbrs = mesh.vertex_neighbours();
297
298 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 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 let (c, d) = (opp_ab[0], opp_ba[0]);
324 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 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; }
351 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 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 SubdivMesh::new(new_verts, new_faces)
409}
410
411#[derive(Debug, Clone)]
417pub struct QuadMesh {
418 pub vertices: Vec<[f64; 3]>,
420 pub faces: Vec<[usize; 4]>,
422}
423
424impl QuadMesh {
425 pub fn new(vertices: Vec<[f64; 3]>, faces: Vec<[usize; 4]>) -> Self {
427 Self { vertices, faces }
428 }
429
430 pub fn n_vertices(&self) -> usize {
432 self.vertices.len()
433 }
434
435 pub fn n_faces(&self) -> usize {
437 self.faces.len()
438 }
439}
440
441pub fn catmull_clark_subdivision(mesh: &QuadMesh) -> QuadMesh {
452 let nv = mesh.vertices.len();
453 let nf = mesh.faces.len();
454
455 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 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 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 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 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 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 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]; 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; QuadMesh::new(all_verts, new_faces)
566}
567
568#[derive(Debug, Clone, Copy)]
574pub enum AdaptiveCriterion {
575 MaxEdgeLength(f64),
577 AspectRatio(f64),
579 NearVertices,
581}
582
583pub 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
661pub fn doo_sabin_step(mesh: &SubdivMesh) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
676 let mut new_verts: Vec<[f64; 3]> = Vec::new();
679 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 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 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
709pub 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(¤t);
718 }
719 current
720}
721
722pub 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(¤t);
727 }
728 current
729}
730
731pub 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(¤t);
736 }
737 current
738}
739
740pub 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(¤t);
745 }
746 current
747}
748
749#[cfg(test)]
754mod tests {
755 use super::*;
756
757 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 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 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 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 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 #[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 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 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 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 #[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 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 let m = two_tri_mesh();
934 let refined = midpoint_subdivision(&m);
935 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 #[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 let m = two_tri_mesh();
991 let refined = loop_subdivision(&m);
992 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 let m = equilateral_tri_mesh();
1003 let refined = loop_subdivision(&m);
1004 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 #[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 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 #[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 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 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 #[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 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 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 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 #[test]
1195 fn test_doo_sabin_vertex_count() {
1196 let m = equilateral_tri_mesh();
1197 let (verts, faces) = doo_sabin_step(&m);
1198 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 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 let inner_edge = len3(sub3(v1, v0));
1223 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 #[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}