1use std::collections::HashMap;
11
12use crate::triangle_mesh::TriangleMesh;
13use oxiphysics_core::math::Vec3;
14
15#[inline]
20fn vec3_to_arr(v: Vec3) -> [f64; 3] {
21 [v.x, v.y, v.z]
22}
23
24#[inline]
25fn arr_to_vec3(a: [f64; 3]) -> Vec3 {
26 Vec3::new(a[0], a[1], a[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 add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
36 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
37}
38
39#[inline]
40fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
41 [a[0] * s, a[1] * s, a[2] * s]
42}
43
44#[inline]
45fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
46 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
47}
48
49#[inline]
50fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
51 [
52 a[1] * b[2] - a[2] * b[1],
53 a[2] * b[0] - a[0] * b[2],
54 a[0] * b[1] - a[1] * b[0],
55 ]
56}
57
58#[inline]
59fn len3(a: [f64; 3]) -> f64 {
60 dot3(a, a).sqrt()
61}
62
63#[inline]
64fn normalize3(a: [f64; 3]) -> [f64; 3] {
65 let l = len3(a);
66 if l < f64::EPSILON {
67 [0.0, 0.0, 0.0]
68 } else {
69 [a[0] / l, a[1] / l, a[2] / l]
70 }
71}
72
73#[inline]
74fn dist3(a: [f64; 3], b: [f64; 3]) -> f64 {
75 len3(sub3(a, b))
76}
77
78#[inline]
79fn midpoint(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
80 scale3(add3(a, b), 0.5)
81}
82
83pub struct UniformRemesher {
89 pub target_edge_length: f64,
91}
92
93impl UniformRemesher {
94 pub fn new(target_edge_length: f64) -> Self {
96 Self { target_edge_length }
97 }
98
99 pub fn remesh(&self, mesh: &TriangleMesh, iterations: usize) -> TriangleMesh {
108 isotropic_remesh(mesh, self.target_edge_length, iterations)
109 }
110}
111
112pub fn isotropic_remesh(mesh: &TriangleMesh, target_len: f64, iterations: usize) -> TriangleMesh {
120 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
122 let mut tris: Vec<[usize; 3]> = mesh.indices.clone();
123
124 let orig_verts = verts.clone();
126 let orig_tris = tris.clone();
127
128 let high = target_len * 4.0 / 3.0;
129 let low = target_len * 4.0 / 5.0;
130
131 for _ in 0..iterations {
132 split_long_edges(&mut verts, &mut tris, high);
134
135 collapse_short_edges(&mut verts, &mut tris, low);
137
138 flip_for_valence(&mut tris, verts.len());
140
141 laplacian_smooth_surface(&mut verts, &tris);
143
144 project_to_surface(&mut verts, &orig_verts, &orig_tris);
146 }
147
148 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
149 TriangleMesh::new(vertices, tris)
150}
151
152fn split_long_edges(verts: &mut Vec<[f64; 3]>, tris: &mut Vec<[usize; 3]>, max_len: f64) {
157 let mut edge_midpoints: HashMap<(usize, usize), usize> = HashMap::new();
159 let mut new_tris: Vec<[usize; 3]> = Vec::new();
160 let mut keep = vec![true; tris.len()];
161
162 for (ti, tri) in tris.iter().enumerate() {
163 let [a, b, c] = *tri;
164 let pa = verts[a];
165 let pb = verts[b];
166 let pc = verts[c];
167
168 let lab = dist3(pa, pb);
169 let lbc = dist3(pb, pc);
170 let lca = dist3(pc, pa);
171
172 let (longest, va, vb, vopp) = if lab >= lbc && lab >= lca {
173 (lab, a, b, c)
174 } else if lbc >= lab && lbc >= lca {
175 (lbc, b, c, a)
176 } else {
177 (lca, c, a, b)
178 };
179
180 if longest > max_len {
181 let key = (va.min(vb), va.max(vb));
183 let mid = if let Some(&existing) = edge_midpoints.get(&key) {
184 existing
185 } else {
186 let idx = verts.len();
187 verts.push(midpoint(verts[va], verts[vb]));
188 edge_midpoints.insert(key, idx);
189 idx
190 };
191 new_tris.push([va, mid, vopp]);
192 new_tris.push([mid, vb, vopp]);
193 keep[ti] = false;
194 }
195 }
196
197 let orig: Vec<[usize; 3]> = tris
198 .iter()
199 .enumerate()
200 .filter_map(|(i, &t)| if keep[i] { Some(t) } else { None })
201 .collect();
202
203 *tris = orig;
204 tris.extend(new_tris);
205}
206
207fn collapse_short_edges(verts: &mut [[f64; 3]], tris: &mut Vec<[usize; 3]>, min_len: f64) {
212 let n = verts.len();
214 let mut remap: Vec<usize> = (0..n).collect();
215
216 let mut edge_set: HashMap<(usize, usize), bool> = HashMap::new();
218 for tri in tris.iter() {
219 for k in 0..3 {
220 let a = tri[k];
221 let b = tri[(k + 1) % 3];
222 let key = (a.min(b), a.max(b));
223 edge_set.insert(key, true);
224 }
225 }
226
227 for (a, b) in edge_set.keys() {
228 let ca = remap[*a];
229 let cb = remap[*b];
230 if ca == cb {
231 continue;
232 }
233 let d = dist3(verts[ca], verts[cb]);
234 if d < min_len {
235 let mp = midpoint(verts[ca], verts[cb]);
237 verts[ca] = mp;
238 for r in remap.iter_mut() {
240 if *r == cb {
241 *r = ca;
242 }
243 }
244 }
245 }
246
247 let new_tris: Vec<[usize; 3]> = tris
249 .iter()
250 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
251 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
252 .collect();
253
254 *tris = new_tris;
255}
256
257fn flip_for_valence(tris: &mut [[usize; 3]], n_verts: usize) {
262 let mut valence = vec![0usize; n_verts];
264 for tri in tris.iter() {
265 for &vi in tri.iter() {
266 if vi < n_verts {
267 valence[vi] += 1;
268 }
269 }
270 }
271
272 let mut edge_map: HashMap<(usize, usize), Vec<(usize, usize)>> = HashMap::new();
274 for (ti, tri) in tris.iter().enumerate() {
275 for k in 0..3 {
276 let a = tri[k];
277 let b = tri[(k + 1) % 3];
278 let opp = tri[(k + 2) % 3];
279 let key = (a.min(b), a.max(b));
280 edge_map.entry(key).or_default().push((ti, opp));
281 }
282 }
283
284 for ((ea, eb), entries) in &edge_map {
285 if entries.len() != 2 {
286 continue;
287 }
288 let (ti, oc) = entries[0];
289 let (tj, od) = entries[1];
290
291 let dev_before = valence_deviation(valence[*ea], 6)
293 + valence_deviation(valence[*eb], 6)
294 + valence_deviation(valence[oc], 6)
295 + valence_deviation(valence[od], 6);
296
297 let dev_after = valence_deviation(valence[*ea] - 1, 6)
299 + valence_deviation(valence[*eb] - 1, 6)
300 + valence_deviation(valence[oc] + 1, 6)
301 + valence_deviation(valence[od] + 1, 6);
302
303 if dev_after < dev_before {
304 tris[ti] = [*ea, oc, od];
305 tris[tj] = [*eb, od, oc];
306 valence[*ea] -= 1;
308 valence[*eb] -= 1;
309 valence[oc] += 1;
310 valence[od] += 1;
311 }
312 }
313}
314
315fn valence_deviation(v: usize, ideal: usize) -> i64 {
316 let d = v as i64 - ideal as i64;
317 d * d
318}
319
320fn laplacian_smooth_surface(verts: &mut [[f64; 3]], tris: &[[usize; 3]]) {
325 let n = verts.len();
326 let mut sums = vec![[0.0_f64; 3]; n];
327 let mut counts = vec![0usize; n];
328
329 for tri in tris {
330 for k in 0..3 {
331 let vi = tri[k];
332 for (j, &vj) in tri.iter().enumerate() {
333 if j != k {
334 sums[vi] = add3(sums[vi], verts[vj]);
335 counts[vi] += 1;
336 }
337 }
338 }
339 }
340
341 for i in 0..n {
342 if counts[i] > 0 {
343 let avg = scale3(sums[i], 1.0 / counts[i] as f64);
344 let delta = sub3(avg, verts[i]);
346 verts[i] = add3(verts[i], scale3(delta, 0.5));
347 }
348 }
349}
350
351fn project_to_surface(verts: &mut [[f64; 3]], orig_verts: &[[f64; 3]], orig_tris: &[[usize; 3]]) {
356 for v in verts.iter_mut() {
357 let mut best_dist = f64::INFINITY;
359 let mut best_point = *v;
360
361 for tri in orig_tris {
362 let a = orig_verts[tri[0]];
363 let b = orig_verts[tri[1]];
364 let c = orig_verts[tri[2]];
365 let cp = closest_point_on_triangle(*v, a, b, c);
366 let d = dist3(*v, cp);
367 if d < best_dist {
368 best_dist = d;
369 best_point = cp;
370 }
371 }
372
373 *v = best_point;
374 }
375}
376
377fn closest_point_on_triangle(p: [f64; 3], a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [f64; 3] {
379 let ab = sub3(b, a);
380 let ac = sub3(c, a);
381 let ap = sub3(p, a);
382
383 let d1 = dot3(ab, ap);
384 let d2 = dot3(ac, ap);
385 if d1 <= 0.0 && d2 <= 0.0 {
386 return a;
387 }
388
389 let bp = sub3(p, b);
390 let d3 = dot3(ab, bp);
391 let d4 = dot3(ac, bp);
392 if d3 >= 0.0 && d4 <= d3 {
393 return b;
394 }
395
396 let vc = d1 * d4 - d3 * d2;
397 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
398 let v = d1 / (d1 - d3);
399 return add3(a, scale3(ab, v));
400 }
401
402 let cp = sub3(p, c);
403 let d5 = dot3(ab, cp);
404 let d6 = dot3(ac, cp);
405 if d6 >= 0.0 && d5 <= d6 {
406 return c;
407 }
408
409 let vb = d5 * d2 - d1 * d6;
410 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
411 let w = d2 / (d2 - d6);
412 return add3(a, scale3(ac, w));
413 }
414
415 let va = d3 * d6 - d5 * d4;
416 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
417 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
418 return add3(b, scale3(sub3(c, b), w));
419 }
420
421 let denom = 1.0 / (va + vb + vc);
422 let v = vb * denom;
423 let w = vc * denom;
424 add3(add3(a, scale3(ab, v)), scale3(ac, w))
425}
426
427pub struct LoopSubdivision;
433
434impl LoopSubdivision {
435 pub fn subdivide(mesh: &TriangleMesh) -> TriangleMesh {
440 let n_orig = mesh.vertices.len();
441 let mut new_verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
442
443 let mut edge_midpoints: HashMap<(usize, usize), usize> = HashMap::new();
445
446 let get_or_create_midpoint = |a: usize,
447 b: usize,
448 verts: &mut Vec<[f64; 3]>,
449 map: &mut HashMap<(usize, usize), usize>|
450 -> usize {
451 let key = (a.min(b), a.max(b));
452 if let Some(&idx) = map.get(&key) {
453 return idx;
454 }
455 let mid = midpoint(verts[a], verts[b]);
456 let idx = verts.len();
457 verts.push(mid);
458 map.insert(key, idx);
459 idx
460 };
461
462 let mut new_tris: Vec<[usize; 3]> = Vec::with_capacity(mesh.indices.len() * 4);
464
465 for tri in &mesh.indices {
466 let [a, b, c] = *tri;
467 let mab = get_or_create_midpoint(a, b, &mut new_verts, &mut edge_midpoints);
468 let mbc = get_or_create_midpoint(b, c, &mut new_verts, &mut edge_midpoints);
469 let mca = get_or_create_midpoint(c, a, &mut new_verts, &mut edge_midpoints);
470
471 new_tris.push([a, mab, mca]);
472 new_tris.push([mab, b, mbc]);
473 new_tris.push([mca, mbc, c]);
474 new_tris.push([mab, mbc, mca]);
475 }
476
477 let mut neighbours: Vec<Vec<usize>> = vec![Vec::new(); n_orig];
480 for tri in &mesh.indices {
481 let [a, b, c] = *tri;
482 if !neighbours[a].contains(&b) {
483 neighbours[a].push(b);
484 }
485 if !neighbours[a].contains(&c) {
486 neighbours[a].push(c);
487 }
488 if !neighbours[b].contains(&a) {
489 neighbours[b].push(a);
490 }
491 if !neighbours[b].contains(&c) {
492 neighbours[b].push(c);
493 }
494 if !neighbours[c].contains(&a) {
495 neighbours[c].push(a);
496 }
497 if !neighbours[c].contains(&b) {
498 neighbours[c].push(b);
499 }
500 }
501
502 for i in 0..n_orig {
503 let k = neighbours[i].len();
504 if k == 0 {
505 continue;
506 }
507 let beta = if k == 3 {
509 3.0 / 16.0
510 } else {
511 3.0 / (8.0 * k as f64)
512 };
513 let self_weight = 1.0 - k as f64 * beta;
514
515 let mut new_pos = scale3(new_verts[i], self_weight);
516 for &nb in &neighbours[i] {
517 new_pos = add3(new_pos, scale3(new_verts[nb], beta));
518 }
519 new_verts[i] = new_pos;
520 }
521
522 let vertices: Vec<Vec3> = new_verts.iter().map(|&a| arr_to_vec3(a)).collect();
523 TriangleMesh::new(vertices, new_tris)
524 }
525}
526
527pub struct CatmullClark;
533
534impl CatmullClark {
535 pub fn subdivide_quad_mesh(
545 verts: &[[f64; 3]],
546 quads: &[[usize; 4]],
547 ) -> (Vec<[f64; 3]>, Vec<[usize; 4]>) {
548 let n_orig = verts.len();
549 let mut new_verts: Vec<[f64; 3]> = verts.to_vec();
550
551 let face_point_start = new_verts.len();
553 let mut face_points: Vec<usize> = Vec::with_capacity(quads.len());
554 for quad in quads {
555 let fp = scale3(
556 quad.iter()
557 .fold([0.0_f64; 3], |acc, &vi| add3(acc, verts[vi])),
558 0.25,
559 );
560 face_points.push(new_verts.len());
561 new_verts.push(fp);
562 }
563
564 let mut edge_map: HashMap<(usize, usize), Vec<usize>> = HashMap::new(); for (qi, quad) in quads.iter().enumerate() {
569 for k in 0..4 {
570 let a = quad[k];
571 let b = quad[(k + 1) % 4];
572 let key = (a.min(b), a.max(b));
573 edge_map.entry(key).or_default().push(qi);
574 }
575 }
576
577 let edge_point_start = new_verts.len();
578 let mut edge_to_idx: HashMap<(usize, usize), usize> = HashMap::new();
579 for (&(ea, eb), face_idxs) in &edge_map {
580 let mut ep = scale3(add3(verts[ea], verts[eb]), 0.5);
581 if face_idxs.len() == 2 {
582 let fp0 = new_verts[face_point_start + face_idxs[0]];
583 let fp1 = new_verts[face_point_start + face_idxs[1]];
584 ep = scale3(add3(add3(verts[ea], verts[eb]), add3(fp0, fp1)), 0.25);
585 }
586 edge_to_idx.insert((ea, eb), new_verts.len());
587 edge_to_idx.insert((eb, ea), new_verts.len());
588 new_verts.push(ep);
589 }
590 let _ = edge_point_start;
591
592 let mut vertex_face_points: Vec<Vec<[f64; 3]>> = vec![Vec::new(); n_orig];
596 let mut vertex_edge_mids: Vec<Vec<[f64; 3]>> = vec![Vec::new(); n_orig];
597
598 for (qi, quad) in quads.iter().enumerate() {
599 let fp = new_verts[face_point_start + qi];
600 for k in 0..4 {
601 let vi = quad[k];
602 vertex_face_points[vi].push(fp);
603 let nb = quad[(k + 1) % 4];
604 let em = scale3(add3(verts[vi], verts[nb]), 0.5);
605 vertex_edge_mids[vi].push(em);
606 let nb2 = quad[(k + 3) % 4];
607 let em2 = scale3(add3(verts[vi], verts[nb2]), 0.5);
608 vertex_edge_mids[vi].push(em2);
609 }
610 }
611
612 for i in 0..n_orig {
613 let n = vertex_face_points[i].len();
614 if n == 0 {
615 continue;
616 }
617 let n_f = n as f64;
618 let f_avg = scale3(
619 vertex_face_points[i]
620 .iter()
621 .fold([0.0_f64; 3], |acc, &x| add3(acc, x)),
622 1.0 / n_f,
623 );
624 let e_count = vertex_edge_mids[i].len();
625 let e_avg = if e_count > 0 {
626 scale3(
627 vertex_edge_mids[i]
628 .iter()
629 .fold([0.0_f64; 3], |acc, &x| add3(acc, x)),
630 1.0 / e_count as f64,
631 )
632 } else {
633 verts[i]
634 };
635 let v_new = scale3(
637 add3(
638 add3(f_avg, scale3(e_avg, 2.0)),
639 scale3(verts[i], (n_f - 3.0).max(0.0)),
640 ),
641 1.0 / n_f,
642 );
643 new_verts[i] = v_new;
644 }
645
646 let mut new_quads: Vec<[usize; 4]> = Vec::with_capacity(quads.len() * 4);
649 for (qi, quad) in quads.iter().enumerate() {
650 let fp_idx = face_point_start + qi;
651 for k in 0..4 {
652 let vi = quad[k];
653 let ea = quad[k];
654 let eb = quad[(k + 1) % 4];
655 let ea2 = quad[(k + 3) % 4];
656 let eb2 = quad[k];
657
658 let ep1 = *edge_to_idx.get(&(ea.min(eb), ea.max(eb))).unwrap_or(&vi);
659 let ep2 = *edge_to_idx
660 .get(&(ea2.min(eb2), ea2.max(eb2)))
661 .unwrap_or(&vi);
662
663 new_quads.push([vi, ep1, fp_idx, ep2]);
664 }
665 }
666
667 (new_verts, new_quads)
668 }
669}
670
671pub fn feature_preserving_remesh(
680 mesh: &TriangleMesh,
681 target_len: f64,
682 iterations: usize,
683 feature_angle_rad: f64,
684) -> TriangleMesh {
685 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
686 let mut tris: Vec<[usize; 3]> = mesh.indices.clone();
687
688 let feature_edges = detect_feature_edges(&verts, &tris, feature_angle_rad);
690
691 let orig_verts = verts.clone();
692 let orig_tris = tris.clone();
693
694 let high = target_len * 4.0 / 3.0;
695 let low = target_len * 4.0 / 5.0;
696
697 for _ in 0..iterations {
698 split_long_edges(&mut verts, &mut tris, high);
699 collapse_short_edges_preserving(&mut verts, &mut tris, low, &feature_edges);
700 flip_for_valence(&mut tris, verts.len());
701 laplacian_smooth_surface(&mut verts, &tris);
702 project_to_surface(&mut verts, &orig_verts, &orig_tris);
703 }
704
705 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
706 TriangleMesh::new(vertices, tris)
707}
708
709fn detect_feature_edges(
711 verts: &[[f64; 3]],
712 tris: &[[usize; 3]],
713 threshold_rad: f64,
714) -> HashMap<(usize, usize), bool> {
715 let mut edge_faces: HashMap<(usize, usize), Vec<[f64; 3]>> = HashMap::new();
717 for tri in tris {
718 let a = verts[tri[0]];
719 let b = verts[tri[1]];
720 let c = verts[tri[2]];
721 let e1 = sub3(b, a);
722 let e2 = sub3(c, a);
723 let n = normalize3(cross3(e1, e2));
724 for k in 0..3 {
725 let va = tri[k];
726 let vb = tri[(k + 1) % 3];
727 let key = (va.min(vb), va.max(vb));
728 edge_faces.entry(key).or_default().push(n);
729 }
730 }
731
732 let mut features = HashMap::new();
733 let cos_thresh = threshold_rad.cos();
734 for ((ea, eb), normals) in &edge_faces {
735 let is_feature = if normals.len() == 2 {
736 let d = dot3(normals[0], normals[1]);
737 d < cos_thresh
738 } else {
739 true };
741 features.insert((*ea, *eb), is_feature);
742 }
743 features
744}
745
746fn collapse_short_edges_preserving(
748 verts: &mut [[f64; 3]],
749 tris: &mut Vec<[usize; 3]>,
750 min_len: f64,
751 feature_edges: &HashMap<(usize, usize), bool>,
752) {
753 let n = verts.len();
754 let mut remap: Vec<usize> = (0..n).collect();
755
756 let mut edge_set: HashMap<(usize, usize), bool> = HashMap::new();
757 for tri in tris.iter() {
758 for k in 0..3 {
759 let a = tri[k];
760 let b = tri[(k + 1) % 3];
761 let key = (a.min(b), a.max(b));
762 edge_set.insert(key, true);
763 }
764 }
765
766 for (a, b) in edge_set.keys() {
767 let ca = remap[*a];
768 let cb = remap[*b];
769 if ca == cb {
770 continue;
771 }
772 if feature_edges
774 .get(&(*a.min(b), *a.max(b)))
775 .copied()
776 .unwrap_or(false)
777 {
778 continue;
779 }
780 let d = dist3(verts[ca], verts[cb]);
781 if d < min_len {
782 let mp = midpoint(verts[ca], verts[cb]);
783 verts[ca] = mp;
784 for r in remap.iter_mut() {
785 if *r == cb {
786 *r = ca;
787 }
788 }
789 }
790 }
791
792 let new_tris: Vec<[usize; 3]> = tris
793 .iter()
794 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
795 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
796 .collect();
797 *tris = new_tris;
798}
799
800pub fn flip_edges_for_quality(tris: &mut [[usize; 3]], verts: &[[f64; 3]]) {
809 let n_verts = verts.len();
810
811 let mut edge_map: HashMap<(usize, usize), Vec<(usize, usize)>> = HashMap::new();
813 for (ti, tri) in tris.iter().enumerate() {
814 for k in 0..3 {
815 let a = tri[k];
816 let b = tri[(k + 1) % 3];
817 let opp = tri[(k + 2) % 3];
818 let key = (a.min(b), a.max(b));
819 edge_map.entry(key).or_default().push((ti, opp));
820 }
821 }
822
823 for ((ea, eb), entries) in &edge_map {
824 if entries.len() != 2 {
825 continue;
826 }
827 let (ti, oc) = entries[0];
828 let (tj, od) = entries[1];
829
830 if *ea >= n_verts || *eb >= n_verts || oc >= n_verts || od >= n_verts {
831 continue;
832 }
833
834 let before_min = min_triangle_angle(verts[*ea], verts[*eb], verts[oc])
836 .min(min_triangle_angle(verts[*ea], verts[*eb], verts[od]));
837 let after_min = min_triangle_angle(verts[oc], verts[od], verts[*ea])
838 .min(min_triangle_angle(verts[oc], verts[od], verts[*eb]));
839
840 if after_min > before_min + 1e-6 {
841 tris[ti] = [*ea, oc, od];
843 tris[tj] = [*eb, od, oc];
844 }
845 }
846}
847
848fn min_triangle_angle(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
850 let ab = sub3(b, a);
851 let ac = sub3(c, a);
852 let bc = sub3(c, b);
853 let lab = len3(ab);
854 let lac = len3(ac);
855 let lbc = len3(bc);
856
857 if lab < 1e-12 || lac < 1e-12 || lbc < 1e-12 {
858 return 0.0;
859 }
860
861 let cos_a = (dot3(ab, ac) / (lab * lac)).clamp(-1.0, 1.0);
862 let cos_b = (dot3(scale3(ab, -1.0), bc) / (lab * lbc)).clamp(-1.0, 1.0);
863 let cos_c = (dot3(scale3(ac, -1.0), scale3(bc, -1.0)) / (lac * lbc)).clamp(-1.0, 1.0);
864
865 cos_a.acos().min(cos_b.acos()).min(cos_c.acos())
866}
867
868pub fn mesh_quality_min_angle(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> f64 {
876 if tris.is_empty() {
877 return 0.0;
878 }
879 let ideal = std::f64::consts::PI / 3.0;
880 let min_q = tris
881 .iter()
882 .map(|tri| {
883 let a = verts[tri[0]];
884 let b = verts[tri[1]];
885 let c = verts[tri[2]];
886 let min_ang = min_triangle_angle(a, b, c);
887 min_ang / ideal
888 })
889 .fold(f64::INFINITY, f64::min);
890 min_q.min(1.0)
891}
892
893pub fn mesh_aspect_ratio_avg(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> f64 {
898 if tris.is_empty() {
899 return 0.0;
900 }
901 let ratios: Vec<f64> = tris
902 .iter()
903 .map(|tri| {
904 let a = verts[tri[0]];
905 let b = verts[tri[1]];
906 let c = verts[tri[2]];
907 let lab = len3(sub3(b, a));
908 let lbc = len3(sub3(c, b));
909 let lca = len3(sub3(a, c));
910 let longest = lab.max(lbc).max(lca);
911 let s = (lab + lbc + lca) * 0.5; let area_sq = s * (s - lab) * (s - lbc) * (s - lca);
913 if area_sq <= 0.0 || s < 1e-12 {
914 return f64::INFINITY;
915 }
916 let area = area_sq.sqrt();
917 let inradius = area / s;
918 longest / (2.0 * inradius)
919 })
920 .collect();
921
922 let finite: Vec<f64> = ratios.into_iter().filter(|x| x.is_finite()).collect();
923 if finite.is_empty() {
924 0.0
925 } else {
926 finite.iter().sum::<f64>() / finite.len() as f64
927 }
928}
929
930pub fn tangent_laplacian_smooth(mesh: &TriangleMesh, iterations: usize) -> TriangleMesh {
937 let orig_verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
938 let tris = &mesh.indices;
939 let mut verts = orig_verts.clone();
940
941 for _ in 0..iterations {
942 laplacian_smooth_surface(&mut verts, tris);
943 project_to_surface(&mut verts, &orig_verts, tris);
944 }
945
946 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
947 TriangleMesh::new(vertices, tris.clone())
948}
949
950pub fn collapse_edge(verts: &mut [[f64; 3]], tris: &mut Vec<[usize; 3]>, v0: usize, v1: usize) {
959 let mid = midpoint(verts[v0], verts[v1]);
960 verts[v0] = mid;
961
962 let new_tris: Vec<[usize; 3]> = tris
964 .iter()
965 .map(|tri| {
966 [
967 if tri[0] == v1 { v0 } else { tri[0] },
968 if tri[1] == v1 { v0 } else { tri[1] },
969 if tri[2] == v1 { v0 } else { tri[2] },
970 ]
971 })
972 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
973 .collect();
974 *tris = new_tris;
975}
976
977pub fn laplacian_smooth(mesh: &TriangleMesh, iterations: usize, lambda: f64) -> TriangleMesh {
986 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
987 let tris = &mesh.indices;
988
989 for _ in 0..iterations {
990 let n = verts.len();
991 let mut sums = vec![[0.0_f64; 3]; n];
992 let mut counts = vec![0usize; n];
993
994 for tri in tris.iter() {
995 for k in 0..3 {
996 let vi = tri[k];
997 for (j, &vj) in tri.iter().enumerate() {
998 if j != k {
999 sums[vi] = add3(sums[vi], verts[vj]);
1000 counts[vi] += 1;
1001 }
1002 }
1003 }
1004 }
1005
1006 for i in 0..n {
1007 if counts[i] > 0 {
1008 let avg = scale3(sums[i], 1.0 / counts[i] as f64);
1009 let delta = sub3(avg, verts[i]);
1010 verts[i] = add3(verts[i], scale3(delta, lambda));
1011 }
1012 }
1013 }
1014
1015 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
1016 TriangleMesh::new(vertices, tris.clone())
1017}
1018
1019pub fn delaunay_edge_flip(tris: &mut [[usize; 3]], verts: &[[f64; 3]]) {
1028 let mut changed = true;
1030 let max_passes = 32;
1031 let mut pass = 0;
1032
1033 while changed && pass < max_passes {
1034 changed = false;
1035 pass += 1;
1036
1037 let mut edge_map: HashMap<(usize, usize), Vec<(usize, usize)>> = HashMap::new();
1038 for (ti, tri) in tris.iter().enumerate() {
1039 for k in 0..3 {
1040 let a = tri[k];
1041 let b = tri[(k + 1) % 3];
1042 let opp = tri[(k + 2) % 3];
1043 let key = (a.min(b), a.max(b));
1044 edge_map.entry(key).or_default().push((ti, opp));
1045 }
1046 }
1047
1048 for ((ea, eb), entries) in &edge_map {
1049 if entries.len() != 2 {
1050 continue;
1051 }
1052 let (ti, oc) = entries[0];
1053 let (tj, od) = entries[1];
1054
1055 if *ea >= verts.len() || *eb >= verts.len() || oc >= verts.len() || od >= verts.len() {
1056 continue;
1057 }
1058
1059 if point_in_circumcircle(verts[*ea], verts[*eb], verts[oc], verts[od]) {
1061 tris[ti] = [*ea, oc, od];
1063 tris[tj] = [*eb, od, oc];
1064 changed = true;
1065 }
1066 }
1067 }
1068}
1069
1070fn point_in_circumcircle(a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3]) -> bool {
1073 let ax = a[0] - d[0];
1075 let az = a[2] - d[2];
1076 let bx = b[0] - d[0];
1077 let bz = b[2] - d[2];
1078 let cx = c[0] - d[0];
1079 let cz = c[2] - d[2];
1080
1081 let det = ax * (bz * (cx * cx + cz * cz) - cz * (bx * bx + bz * bz))
1082 - az * (bx * (cx * cx + cz * cz) - cx * (bx * bx + bz * bz))
1083 + (ax * ax + az * az) * (bx * cz - bz * cx);
1084
1085 det > 0.0
1086}
1087
1088pub fn coarsen_mesh(mesh: &TriangleMesh, target_len: f64) -> TriangleMesh {
1096 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1097 let mut tris: Vec<[usize; 3]> = mesh.indices.clone();
1098
1099 for _ in 0..10 {
1101 let before = tris.len();
1102 collapse_short_edges(&mut verts, &mut tris, target_len);
1103 if tris.len() == before {
1104 break;
1105 }
1106 }
1107
1108 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
1109 TriangleMesh::new(vertices, tris)
1110}
1111
1112pub fn adaptive_refine_by_curvature(mesh: &TriangleMesh, threshold: f64) -> TriangleMesh {
1121 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1122 let mut tris: Vec<[usize; 3]> = mesh.indices.clone();
1123
1124 let face_normals: Vec<[f64; 3]> = tris
1126 .iter()
1127 .map(|tri| {
1128 let a = verts[tri[0]];
1129 let b = verts[tri[1]];
1130 let c = verts[tri[2]];
1131 normalize3(cross3(sub3(b, a), sub3(c, a)))
1132 })
1133 .collect();
1134
1135 let n = verts.len();
1137 let mut vertex_faces: Vec<Vec<usize>> = vec![Vec::new(); n];
1138 for (ti, tri) in tris.iter().enumerate() {
1139 for &vi in tri.iter() {
1140 vertex_faces[vi].push(ti);
1141 }
1142 }
1143
1144 let vertex_curvature: Vec<f64> = (0..n)
1145 .map(|vi| {
1146 let faces = &vertex_faces[vi];
1147 if faces.len() < 2 {
1148 return 0.0;
1149 }
1150 let mut max_angle: f64 = 0.0;
1151 for i in 0..faces.len() {
1152 for j in (i + 1)..faces.len() {
1153 let d = dot3(face_normals[faces[i]], face_normals[faces[j]]).clamp(-1.0, 1.0);
1154 let angle = d.acos();
1155 if angle > max_angle {
1156 max_angle = angle;
1157 }
1158 }
1159 }
1160 max_angle
1161 })
1162 .collect();
1163
1164 let mut new_tris: Vec<[usize; 3]> = Vec::new();
1166 let mut kept_tris: Vec<[usize; 3]> = Vec::new();
1167
1168 for tri in &tris {
1169 let max_curv = vertex_curvature[tri[0]]
1170 .max(vertex_curvature[tri[1]])
1171 .max(vertex_curvature[tri[2]]);
1172
1173 if max_curv > threshold {
1174 let a = tri[0];
1176 let b = tri[1];
1177 let c = tri[2];
1178 let lab = dist3(verts[a], verts[b]);
1179 let lbc = dist3(verts[b], verts[c]);
1180 let lca = dist3(verts[c], verts[a]);
1181
1182 let (va, vb, vc) = if lab >= lbc && lab >= lca {
1183 (a, b, c)
1184 } else if lbc >= lab && lbc >= lca {
1185 (b, c, a)
1186 } else {
1187 (c, a, b)
1188 };
1189
1190 let mid_idx = verts.len();
1191 verts.push(midpoint(verts[va], verts[vb]));
1192 new_tris.push([va, mid_idx, vc]);
1193 new_tris.push([mid_idx, vb, vc]);
1194 } else {
1195 kept_tris.push(*tri);
1196 }
1197 }
1198
1199 tris = kept_tris;
1200 tris.extend(new_tris);
1201
1202 let vertices: Vec<Vec3> = verts.iter().map(|&a| arr_to_vec3(a)).collect();
1203 TriangleMesh::new(vertices, tris)
1204}
1205
1206pub fn vertex_clustering_decimate(mesh: &TriangleMesh, cells_per_axis: usize) -> TriangleMesh {
1216 if cells_per_axis == 0 || mesh.vertices.is_empty() {
1217 return mesh.clone();
1218 }
1219
1220 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1221
1222 let mut bbmin = verts[0];
1224 let mut bbmax = verts[0];
1225 for &v in &verts {
1226 for k in 0..3 {
1227 if v[k] < bbmin[k] {
1228 bbmin[k] = v[k];
1229 }
1230 if v[k] > bbmax[k] {
1231 bbmax[k] = v[k];
1232 }
1233 }
1234 }
1235 let extent = [
1236 bbmax[0] - bbmin[0] + 1e-8,
1237 bbmax[1] - bbmin[1] + 1e-8,
1238 bbmax[2] - bbmin[2] + 1e-8,
1239 ];
1240 let c = cells_per_axis as f64;
1241
1242 let cell_of = |v: [f64; 3]| -> usize {
1244 let ix = ((v[0] - bbmin[0]) / extent[0] * c).floor() as usize;
1245 let iy = ((v[1] - bbmin[1]) / extent[1] * c).floor() as usize;
1246 let iz = ((v[2] - bbmin[2]) / extent[2] * c).floor() as usize;
1247 let ix = ix.min(cells_per_axis - 1);
1248 let iy = iy.min(cells_per_axis - 1);
1249 let iz = iz.min(cells_per_axis - 1);
1250 ix + cells_per_axis * (iy + cells_per_axis * iz)
1251 };
1252
1253 let n_cells = cells_per_axis * cells_per_axis * cells_per_axis;
1254 let mut cell_sums: Vec<[f64; 3]> = vec![[0.0; 3]; n_cells];
1255 let mut cell_counts: Vec<usize> = vec![0; n_cells];
1256 let mut vertex_cell: Vec<usize> = vec![0; verts.len()];
1257
1258 for (i, &v) in verts.iter().enumerate() {
1259 let cell = cell_of(v);
1260 vertex_cell[i] = cell;
1261 for k in 0..3 {
1262 cell_sums[cell][k] += v[k];
1263 }
1264 cell_counts[cell] += 1;
1265 }
1266
1267 let mut cell_to_vert: Vec<Option<usize>> = vec![None; n_cells];
1269 let mut new_verts: Vec<[f64; 3]> = Vec::new();
1270 for cell in 0..n_cells {
1271 if cell_counts[cell] > 0 {
1272 cell_to_vert[cell] = Some(new_verts.len());
1273 let cnt = cell_counts[cell] as f64;
1274 new_verts.push([
1275 cell_sums[cell][0] / cnt,
1276 cell_sums[cell][1] / cnt,
1277 cell_sums[cell][2] / cnt,
1278 ]);
1279 }
1280 }
1281
1282 let new_tris: Vec<[usize; 3]> = mesh
1284 .indices
1285 .iter()
1286 .filter_map(|tri| {
1287 let a = cell_to_vert[vertex_cell[tri[0]]]?;
1288 let b = cell_to_vert[vertex_cell[tri[1]]]?;
1289 let c = cell_to_vert[vertex_cell[tri[2]]]?;
1290 if a == b || b == c || a == c {
1291 return None;
1292 }
1293 Some([a, b, c])
1294 })
1295 .collect();
1296
1297 let vertices: Vec<Vec3> = new_verts.iter().map(|&a| arr_to_vec3(a)).collect();
1298 TriangleMesh::new(vertices, new_tris)
1299}
1300
1301pub fn saliency_weighted_remesh(
1311 mesh: &TriangleMesh,
1312 saliency: &[f64],
1313 iterations: usize,
1314) -> TriangleMesh {
1315 if mesh.vertices.is_empty() {
1316 return mesh.clone();
1317 }
1318
1319 let tri_saliency: Vec<f64> = mesh
1321 .indices
1322 .iter()
1323 .map(|tri| {
1324 let s = [
1325 if tri[0] < saliency.len() {
1326 saliency[tri[0]]
1327 } else {
1328 0.5
1329 },
1330 if tri[1] < saliency.len() {
1331 saliency[tri[1]]
1332 } else {
1333 0.5
1334 },
1335 if tri[2] < saliency.len() {
1336 saliency[tri[2]]
1337 } else {
1338 0.5
1339 },
1340 ];
1341 (s[0] + s[1] + s[2]) / 3.0
1342 })
1343 .collect();
1344
1345 let avg_sal = if tri_saliency.is_empty() {
1347 0.5
1348 } else {
1349 tri_saliency.iter().sum::<f64>() / tri_saliency.len() as f64
1350 };
1351
1352 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1354 let avg_edge: f64 = {
1355 let total: f64 = mesh
1356 .indices
1357 .iter()
1358 .map(|tri| {
1359 let a = verts[tri[0]];
1360 let b = verts[tri[1]];
1361 let c = verts[tri[2]];
1362 (dist3(a, b) + dist3(b, c) + dist3(c, a)) / 3.0
1363 })
1364 .sum();
1365 if mesh.indices.is_empty() {
1366 1.0
1367 } else {
1368 total / mesh.indices.len() as f64
1369 }
1370 };
1371
1372 let target = avg_edge * (1.5 - avg_sal.clamp(0.0, 1.0));
1374
1375 isotropic_remesh(mesh, target.max(1e-6), iterations)
1376}
1377
1378#[cfg(test)]
1383mod tests {
1384 use super::*;
1385
1386 fn simple_quad_mesh() -> TriangleMesh {
1387 let verts = vec![
1389 Vec3::new(0.0, 0.0, 0.0),
1390 Vec3::new(1.0, 0.0, 0.0),
1391 Vec3::new(1.0, 1.0, 0.0),
1392 Vec3::new(0.0, 1.0, 0.0),
1393 ];
1394 let tris = vec![[0usize, 1, 2], [0, 2, 3]];
1395 TriangleMesh::new(verts, tris)
1396 }
1397
1398 #[test]
1399 fn test_loop_subdivision_face_count() {
1400 let mesh = simple_quad_mesh();
1401 let sub = LoopSubdivision::subdivide(&mesh);
1402 assert_eq!(
1404 sub.indices.len(),
1405 mesh.indices.len() * 4,
1406 "Loop subdivision should quadruple the face count"
1407 );
1408 }
1409
1410 #[test]
1411 fn test_loop_subdivision_vertex_count_increases() {
1412 let mesh = simple_quad_mesh();
1413 let sub = LoopSubdivision::subdivide(&mesh);
1414 assert!(
1415 sub.vertices.len() > mesh.vertices.len(),
1416 "Loop subdivision should add vertices"
1417 );
1418 }
1419
1420 #[test]
1421 fn test_catmull_clark_face_count() {
1422 let verts: Vec<[f64; 3]> = vec![
1423 [0.0, 0.0, 0.0],
1424 [1.0, 0.0, 0.0],
1425 [1.0, 1.0, 0.0],
1426 [0.0, 1.0, 0.0],
1427 ];
1428 let quads: Vec<[usize; 4]> = vec![[0, 1, 2, 3]];
1429 let (_, new_quads) = CatmullClark::subdivide_quad_mesh(&verts, &quads);
1430 assert_eq!(
1432 new_quads.len(),
1433 quads.len() * 4,
1434 "Catmull-Clark should quadruple the quad count"
1435 );
1436 }
1437
1438 #[test]
1439 fn test_catmull_clark_two_quads() {
1440 let verts: Vec<[f64; 3]> = vec![
1441 [0.0, 0.0, 0.0],
1442 [1.0, 0.0, 0.0],
1443 [2.0, 0.0, 0.0],
1444 [0.0, 1.0, 0.0],
1445 [1.0, 1.0, 0.0],
1446 [2.0, 1.0, 0.0],
1447 ];
1448 let quads: Vec<[usize; 4]> = vec![[0, 1, 4, 3], [1, 2, 5, 4]];
1449 let (_, new_quads) = CatmullClark::subdivide_quad_mesh(&verts, &quads);
1450 assert_eq!(new_quads.len(), quads.len() * 4);
1451 }
1452
1453 #[test]
1454 fn test_isotropic_remesh_returns_mesh() {
1455 let mesh = simple_quad_mesh();
1456 let result = isotropic_remesh(&mesh, 0.5, 2);
1457 assert!(
1459 !result.indices.is_empty(),
1460 "remeshed mesh should have faces"
1461 );
1462 assert!(
1463 !result.vertices.is_empty(),
1464 "remeshed mesh should have vertices"
1465 );
1466 }
1467
1468 #[test]
1469 fn test_uniform_remesher() {
1470 let mesh = simple_quad_mesh();
1471 let remesher = UniformRemesher::new(0.4);
1472 let result = remesher.remesh(&mesh, 1);
1473 assert!(!result.vertices.is_empty());
1474 }
1475
1476 #[test]
1477 fn test_loop_subdivision_twice() {
1478 let mesh = simple_quad_mesh();
1479 let sub1 = LoopSubdivision::subdivide(&mesh);
1480 let sub2 = LoopSubdivision::subdivide(&sub1);
1481 assert_eq!(sub2.indices.len(), mesh.indices.len() * 16);
1482 }
1483
1484 #[test]
1485 fn test_closest_point_on_triangle() {
1486 let a = [0.0_f64, 0.0, 0.0];
1487 let b = [1.0, 0.0, 0.0];
1488 let c = [0.0, 1.0, 0.0];
1489 let p = [0.25, 0.25, 1.0];
1491 let cp = closest_point_on_triangle(p, a, b, c);
1492 assert!((cp[0] - 0.25).abs() < 1e-10);
1494 assert!((cp[1] - 0.25).abs() < 1e-10);
1495 assert!(cp[2].abs() < 1e-10);
1496 }
1497
1498 #[test]
1501 fn test_feature_preserving_remesh_returns_mesh() {
1502 let mesh = simple_quad_mesh();
1503 let result = feature_preserving_remesh(&mesh, 0.4, 1, 1.0_f64.to_radians());
1506 assert!(!result.vertices.is_empty());
1507 assert!(!result.indices.is_empty());
1508 }
1509
1510 #[test]
1511 fn test_feature_preserving_remesh_no_degenerate_faces() {
1512 let mesh = simple_quad_mesh();
1513 let result = feature_preserving_remesh(&mesh, 0.3, 2, 45.0_f64.to_radians());
1514 let verts: Vec<[f64; 3]> = result.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1515 for tri in &result.indices {
1516 let a = verts[tri[0]];
1517 let b = verts[tri[1]];
1518 let c = verts[tri[2]];
1519 let e1 = sub3(b, a);
1520 let e2 = sub3(c, a);
1521 let cross = cross3(e1, e2);
1522 let area = len3(cross) * 0.5;
1523 assert!(area >= 0.0, "negative area: {}", area);
1524 }
1525 }
1526
1527 #[test]
1530 fn test_edge_flip_preserves_vertex_count() {
1531 let mesh = simple_quad_mesh();
1532 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1533 let mut tris = mesh.indices.clone();
1534 let n_verts_before = verts.len();
1535 flip_edges_for_quality(&mut tris, &verts);
1536 assert_eq!(verts.len(), n_verts_before);
1537 let _ = verts; }
1539
1540 #[test]
1541 fn test_edge_flip_preserves_face_count() {
1542 let mesh = simple_quad_mesh();
1543 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1544 let mut tris = mesh.indices.clone();
1545 let n_tris_before = tris.len();
1546 flip_edges_for_quality(&mut tris, &verts);
1547 assert_eq!(tris.len(), n_tris_before);
1548 }
1549
1550 #[test]
1553 fn test_mesh_quality_equilateral() {
1554 let s = 1.0_f64;
1556 let h = s * (3.0_f64.sqrt()) / 2.0;
1557 let verts = vec![[0.0_f64, 0.0, 0.0], [s, 0.0, 0.0], [s / 2.0, h, 0.0]];
1558 let tris = vec![[0usize, 1, 2]];
1559 let quality = mesh_quality_min_angle(&verts, &tris);
1560 assert!(quality > 0.9, "equilateral triangle quality={}", quality);
1562 }
1563
1564 #[test]
1565 fn test_mesh_quality_degenerate() {
1566 let verts = vec![
1568 [0.0_f64, 0.0, 0.0],
1569 [1.0, 0.0, 0.0],
1570 [100.0, 0.0, 0.0], ];
1572 let tris = vec![[0usize, 1, 2]];
1573 let quality = mesh_quality_min_angle(&verts, &tris);
1574 assert!(
1575 quality < 0.5,
1576 "degenerate triangle quality should be low, got {}",
1577 quality
1578 );
1579 }
1580
1581 #[test]
1584 fn test_tangent_smooth_returns_mesh() {
1585 let mesh = simple_quad_mesh();
1586 let result = tangent_laplacian_smooth(&mesh, 3);
1587 assert_eq!(result.vertices.len(), mesh.vertices.len());
1588 assert_eq!(result.indices.len(), mesh.indices.len());
1589 }
1590
1591 #[test]
1594 fn test_catmull_clark_double_subdivision() {
1595 let verts: Vec<[f64; 3]> = vec![
1596 [0.0, 0.0, 0.0],
1597 [1.0, 0.0, 0.0],
1598 [1.0, 1.0, 0.0],
1599 [0.0, 1.0, 0.0],
1600 ];
1601 let quads: Vec<[usize; 4]> = vec![[0, 1, 2, 3]];
1602 let (v1, q1) = CatmullClark::subdivide_quad_mesh(&verts, &quads);
1603 let (_, q2) = CatmullClark::subdivide_quad_mesh(&v1, &q1);
1604 assert_eq!(q2.len(), quads.len() * 16); }
1606
1607 #[test]
1610 fn test_laplacian_smooth_moves_vertices() {
1611 let mesh = simple_quad_mesh();
1612 let result = laplacian_smooth(&mesh, 3, 0.5);
1613 let orig_sum: f64 = mesh.vertices.iter().map(|v| v.norm()).sum();
1615 let new_sum: f64 = result.vertices.iter().map(|v| v.norm()).sum();
1616 let _ = (orig_sum, new_sum);
1618 assert_eq!(result.vertices.len(), mesh.vertices.len());
1619 }
1620
1621 #[test]
1622 fn test_laplacian_smooth_zero_iterations_unchanged() {
1623 let mesh = simple_quad_mesh();
1624 let result = laplacian_smooth(&mesh, 0, 0.5);
1625 for (a, b) in mesh.vertices.iter().zip(result.vertices.iter()) {
1626 assert!((a.x - b.x).abs() < 1e-12);
1627 assert!((a.y - b.y).abs() < 1e-12);
1628 assert!((a.z - b.z).abs() < 1e-12);
1629 }
1630 }
1631
1632 #[test]
1633 fn test_laplacian_smooth_preserves_face_count() {
1634 let mesh = simple_quad_mesh();
1635 let result = laplacian_smooth(&mesh, 5, 0.3);
1636 assert_eq!(result.indices.len(), mesh.indices.len());
1637 }
1638
1639 #[test]
1642 fn test_delaunay_flip_preserves_vertices() {
1643 let mesh = simple_quad_mesh();
1644 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1645 let mut tris = mesh.indices.clone();
1646 let before = verts.len();
1647 delaunay_edge_flip(&mut tris, &verts);
1648 assert_eq!(verts.len(), before);
1649 }
1650
1651 #[test]
1652 fn test_delaunay_flip_preserves_face_count() {
1653 let mesh = simple_quad_mesh();
1654 let verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1655 let mut tris = mesh.indices.clone();
1656 let before = tris.len();
1657 delaunay_edge_flip(&mut tris, &verts);
1658 assert_eq!(tris.len(), before);
1659 }
1660
1661 #[test]
1664 fn test_coarsen_reduces_vertex_count() {
1665 let mesh = simple_quad_mesh();
1666 let result = coarsen_mesh(&mesh, 0.8); assert!(result.vertices.len() <= mesh.vertices.len() + 1); }
1670
1671 #[test]
1672 fn test_coarsen_no_degenerate_faces() {
1673 let mesh = simple_quad_mesh();
1674 let result = coarsen_mesh(&mesh, 0.5);
1675 let verts: Vec<[f64; 3]> = result.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1676 for tri in &result.indices {
1677 assert!(
1678 tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2],
1679 "degenerate face {:?}",
1680 tri
1681 );
1682 assert!(tri[0] < verts.len() && tri[1] < verts.len() && tri[2] < verts.len());
1684 }
1685 }
1686
1687 #[test]
1690 fn test_adaptive_refine_increases_faces_for_curved_mesh() {
1691 let mesh = simple_quad_mesh();
1693 let result = adaptive_refine_by_curvature(&mesh, 0.01); assert!(result.indices.len() >= mesh.indices.len());
1696 }
1697
1698 #[test]
1699 fn test_adaptive_refine_high_threshold_no_change() {
1700 let mesh = simple_quad_mesh();
1701 let result = adaptive_refine_by_curvature(&mesh, 1000.0); assert_eq!(result.indices.len(), mesh.indices.len());
1704 }
1705
1706 #[test]
1709 fn test_vertex_clustering_reduces_count() {
1710 let mesh = simple_quad_mesh();
1712 let sub = LoopSubdivision::subdivide(&mesh);
1713 let coarse = vertex_clustering_decimate(&sub, 2); assert!(
1715 coarse.vertices.len() < sub.vertices.len(),
1716 "decimation should reduce vertex count"
1717 );
1718 }
1719
1720 #[test]
1721 fn test_vertex_clustering_single_cell_merges_all() {
1722 let mesh = simple_quad_mesh();
1723 let result = vertex_clustering_decimate(&mesh, 1); assert!(result.vertices.len() <= mesh.vertices.len());
1726 }
1727
1728 #[test]
1731 fn test_saliency_remesh_returns_valid_mesh() {
1732 let mesh = simple_quad_mesh();
1734 let sub = LoopSubdivision::subdivide(&mesh);
1735 let saliency = vec![0.5_f64; sub.vertices.len()];
1736 let result = saliency_weighted_remesh(&sub, &saliency, 1);
1737 assert!(!result.vertices.is_empty());
1738 let _ = result.indices.len();
1740 }
1741
1742 #[test]
1743 fn test_saliency_remesh_preserves_approximate_vertex_count() {
1744 let mesh = simple_quad_mesh();
1745 let saliency = vec![0.5_f64; mesh.vertices.len()];
1746 let result = saliency_weighted_remesh(&mesh, &saliency, 1);
1747 assert!(result.vertices.len() <= mesh.vertices.len() * 10);
1749 }
1750
1751 #[test]
1754 fn test_mesh_quality_after_laplacian_smooth() {
1755 let mesh = simple_quad_mesh();
1756 let smoothed = laplacian_smooth(&mesh, 5, 0.5);
1757 let verts: Vec<[f64; 3]> = smoothed.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1758 let q = mesh_quality_min_angle(&verts, &smoothed.indices);
1759 assert!((0.0..=1.0 + 1e-6).contains(&q), "quality out of range: {q}");
1760 }
1761
1762 #[test]
1763 fn test_aspect_ratio_equilateral() {
1764 let s = 1.0_f64;
1765 let h = s * 3.0_f64.sqrt() / 2.0;
1766 let verts = vec![[0.0_f64, 0.0, 0.0], [s, 0.0, 0.0], [s / 2.0, h, 0.0]];
1767 let tris = vec![[0usize, 1, 2]];
1768 let ar = mesh_aspect_ratio_avg(&verts, &tris);
1769 assert!(
1773 (ar - 3.0_f64.sqrt()).abs() < 0.01,
1774 "equilateral ar should be sqrt(3)≈1.732, got {ar}"
1775 );
1776 }
1777
1778 #[test]
1779 fn test_collapse_edge_reduces_vertex_usage() {
1780 let mesh = simple_quad_mesh();
1781 let mut verts: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1782 let mut tris = mesh.indices.clone();
1783 let before_faces = tris.len();
1784 collapse_edge(&mut verts, &mut tris, 0, 1);
1785 assert!(tris.len() <= before_faces);
1787 }
1788
1789 #[test]
1790 fn test_loop_subdivision_vertex_positions_reasonable() {
1791 let mesh = simple_quad_mesh();
1792 let sub = LoopSubdivision::subdivide(&mesh);
1793 for v in &sub.vertices {
1795 assert!(v.x.is_finite() && v.y.is_finite() && v.z.is_finite());
1796 }
1797 }
1798
1799 #[test]
1800 fn test_isotropic_remesh_zero_iterations_same() {
1801 let mesh = simple_quad_mesh();
1802 let result = isotropic_remesh(&mesh, 0.5, 0);
1803 assert_eq!(result.vertices.len(), mesh.vertices.len());
1805 assert_eq!(result.indices.len(), mesh.indices.len());
1806 }
1807
1808 #[test]
1809 fn test_delaunay_flip_on_subdivided_mesh() {
1810 let mesh = simple_quad_mesh();
1811 let sub = LoopSubdivision::subdivide(&mesh);
1812 let verts: Vec<[f64; 3]> = sub.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1813 let mut tris = sub.indices.clone();
1814 delaunay_edge_flip(&mut tris, &verts);
1815 for tri in &tris {
1817 assert!(tri[0] < verts.len() && tri[1] < verts.len() && tri[2] < verts.len());
1818 }
1819 }
1820
1821 #[test]
1822 fn test_coarsen_mesh_returns_valid_faces() {
1823 let mesh = simple_quad_mesh();
1824 let result = coarsen_mesh(&mesh, 1.5);
1825 let verts: Vec<[f64; 3]> = result.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1826 for tri in &result.indices {
1827 assert!(
1828 tri[0] < verts.len() && tri[1] < verts.len() && tri[2] < verts.len(),
1829 "face {:?} has out-of-bounds index (n_verts={})",
1830 tri,
1831 verts.len()
1832 );
1833 }
1834 }
1835
1836 #[test]
1837 fn test_adaptive_refine_no_degenerate_faces() {
1838 let mesh = simple_quad_mesh();
1839 let result = adaptive_refine_by_curvature(&mesh, 0.1);
1840 let verts: Vec<[f64; 3]> = result.vertices.iter().map(|v| vec3_to_arr(*v)).collect();
1841 for tri in &result.indices {
1842 assert!(tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2]);
1843 assert!(tri[0] < verts.len() && tri[1] < verts.len() && tri[2] < verts.len());
1844 }
1845 }
1846}