1use oxiphysics_core::math::Vec3;
7use std::collections::HashMap;
8
9pub type Triangle = [usize; 3];
11
12#[derive(Debug, Clone)]
14pub struct TriMesh {
15 pub vertices: Vec<Vec3>,
17 pub triangles: Vec<Triangle>,
19}
20
21impl TriMesh {
22 pub fn new(vertices: Vec<Vec3>, triangles: Vec<Triangle>) -> Self {
24 Self {
25 vertices,
26 triangles,
27 }
28 }
29
30 pub fn face_normal(&self, tri_idx: usize) -> Vec3 {
34 let tri = &self.triangles[tri_idx];
35 let v0 = self.vertices[tri[0]];
36 let v1 = self.vertices[tri[1]];
37 let v2 = self.vertices[tri[2]];
38 let e1 = v1 - v0;
39 let e2 = v2 - v0;
40 e1.cross(&e2).normalize()
41 }
42
43 pub fn signed_volume(&self) -> f64 {
48 let mut sum = 0.0_f64;
49 for tri in &self.triangles {
50 let v0 = self.vertices[tri[0]];
51 let v1 = self.vertices[tri[1]];
52 let v2 = self.vertices[tri[2]];
53 sum += v0.dot(&v1.cross(&v2));
54 }
55 sum / 6.0
56 }
57
58 pub fn surface_area(&self) -> f64 {
62 let mut area = 0.0_f64;
63 for tri in &self.triangles {
64 let v0 = self.vertices[tri[0]];
65 let v1 = self.vertices[tri[1]];
66 let v2 = self.vertices[tri[2]];
67 let e1 = v1 - v0;
68 let e2 = v2 - v0;
69 area += 0.5 * e1.cross(&e2).norm();
70 }
71 area
72 }
73
74 pub fn vertex_normals(&self) -> Vec<Vec3> {
76 let n = self.vertices.len();
77 let mut normals = vec![Vec3::zeros(); n];
78 for tri in &self.triangles {
79 let v0 = self.vertices[tri[0]];
80 let v1 = self.vertices[tri[1]];
81 let v2 = self.vertices[tri[2]];
82 let e1 = v1 - v0;
83 let e2 = v2 - v0;
84 let face_n = e1.cross(&e2); normals[tri[0]] += face_n;
86 normals[tri[1]] += face_n;
87 normals[tri[2]] += face_n;
88 }
89 normals
90 .into_iter()
91 .map(|n| {
92 let len = n.norm();
93 if len < 1e-10 {
94 Vec3::new(0.0, 1.0, 0.0)
95 } else {
96 n / len
97 }
98 })
99 .collect()
100 }
101
102 pub fn contains_point(&self, point: Vec3) -> bool {
106 let mut count = 0usize;
107 for tri in &self.triangles {
108 let v0 = self.vertices[tri[0]];
109 let v1 = self.vertices[tri[1]];
110 let v2 = self.vertices[tri[2]];
111 if ray_intersects_triangle_z(point, v0, v1, v2) {
112 count += 1;
113 }
114 }
115 count % 2 == 1
116 }
117
118 pub fn uv_sphere(radius: f64, rings: u32, sectors: u32) -> Self {
123 use std::f64::consts::PI;
124 let mut vertices = Vec::new();
125 let mut triangles = Vec::new();
126
127 vertices.push(Vec3::new(0.0, radius, 0.0));
129
130 for ring in 1..=rings {
132 let phi = PI * ring as f64 / (rings + 1) as f64; let y = radius * phi.cos();
134 let r_xz = radius * phi.sin();
135 for sector in 0..sectors {
136 let theta = 2.0 * PI * sector as f64 / sectors as f64;
137 let x = r_xz * theta.cos();
138 let z = r_xz * theta.sin();
139 vertices.push(Vec3::new(x, y, z));
140 }
141 }
142
143 vertices.push(Vec3::new(0.0, -radius, 0.0));
145
146 let top_idx = 0usize;
147 let bottom_idx = vertices.len() - 1;
148
149 for s in 0..sectors as usize {
153 let next_s = (s + 1) % sectors as usize;
154 let a = 1 + s;
155 let b = 1 + next_s;
156 triangles.push([top_idx, b, a]);
157 }
158
159 for ring in 0..(rings as usize - 1) {
163 let row_start = 1 + ring * sectors as usize;
164 let next_row_start = row_start + sectors as usize;
165 for s in 0..sectors as usize {
166 let next_s = (s + 1) % sectors as usize;
167 let a = row_start + s; let b = row_start + next_s; let c = next_row_start + s; let d = next_row_start + next_s; triangles.push([a, b, c]);
173 triangles.push([b, d, c]);
174 }
175 }
176
177 let last_ring_start = 1 + (rings as usize - 1) * sectors as usize;
179 for s in 0..sectors as usize {
180 let next_s = (s + 1) % sectors as usize;
181 let a = last_ring_start + s;
182 let b = last_ring_start + next_s;
183 triangles.push([a, b, bottom_idx]);
184 }
185
186 Self {
187 vertices,
188 triangles,
189 }
190 }
191
192 pub fn bounding_box(&self) -> (Vec3, Vec3) {
196 let mut min = self.vertices[0];
197 let mut max = self.vertices[0];
198 for v in &self.vertices {
199 min.x = min.x.min(v.x);
200 min.y = min.y.min(v.y);
201 min.z = min.z.min(v.z);
202 max.x = max.x.max(v.x);
203 max.y = max.y.max(v.y);
204 max.z = max.z.max(v.z);
205 }
206 (min, max)
207 }
208}
209
210#[inline]
213fn mo_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
214 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
215}
216
217#[inline]
218fn mo_add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
219 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
220}
221
222#[inline]
223fn mo_scale(a: [f64; 3], s: f64) -> [f64; 3] {
224 [a[0] * s, a[1] * s, a[2] * s]
225}
226
227#[inline]
228fn mo_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
229 [
230 a[1] * b[2] - a[2] * b[1],
231 a[2] * b[0] - a[0] * b[2],
232 a[0] * b[1] - a[1] * b[0],
233 ]
234}
235
236#[inline]
237fn mo_norm(a: [f64; 3]) -> f64 {
238 (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
239}
240
241#[inline]
242fn mo_normalize(a: [f64; 3]) -> [f64; 3] {
243 let n = mo_norm(a);
244 if n < 1e-12 {
245 [0.0, 1.0, 0.0]
246 } else {
247 mo_scale(a, 1.0 / n)
248 }
249}
250
251pub fn compute_face_normals(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> Vec<[f64; 3]> {
258 tris.iter()
259 .map(|tri| {
260 let v0 = verts[tri[0]];
261 let v1 = verts[tri[1]];
262 let v2 = verts[tri[2]];
263 let e1 = mo_sub(v1, v0);
264 let e2 = mo_sub(v2, v0);
265 let c = mo_cross(e1, e2);
266 let len = mo_norm(c);
267 if len < 1e-14 {
268 [0.0; 3]
269 } else {
270 mo_scale(c, 1.0 / len)
271 }
272 })
273 .collect()
274}
275
276pub fn compute_vertex_normals(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> Vec<[f64; 3]> {
283 let n = verts.len();
284 let mut acc: Vec<[f64; 3]> = vec![[0.0; 3]; n];
285 for tri in tris {
286 let v0 = verts[tri[0]];
287 let v1 = verts[tri[1]];
288 let v2 = verts[tri[2]];
289 let e1 = mo_sub(v1, v0);
290 let e2 = mo_sub(v2, v0);
291 let fn_ = mo_cross(e1, e2); for &vi in tri {
293 acc[vi] = mo_add(acc[vi], fn_);
294 }
295 }
296 acc.into_iter().map(mo_normalize).collect()
297}
298
299pub fn weld_vertices(
306 verts: &[[f64; 3]],
307 tris: &[[usize; 3]],
308 tol: f64,
309) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
310 let tol2 = tol * tol;
311 let mut new_verts: Vec<[f64; 3]> = Vec::with_capacity(verts.len());
312 let mut remap: Vec<usize> = vec![0; verts.len()];
313
314 for (i, &v) in verts.iter().enumerate() {
315 let mut found = None;
317 for (j, &w) in new_verts.iter().enumerate() {
318 let d = mo_sub(v, w);
319 if d[0] * d[0] + d[1] * d[1] + d[2] * d[2] <= tol2 {
320 found = Some(j);
321 break;
322 }
323 }
324 remap[i] = match found {
325 Some(j) => j,
326 None => {
327 let idx = new_verts.len();
328 new_verts.push(v);
329 idx
330 }
331 };
332 }
333
334 let new_tris: Vec<[usize; 3]> = tris
335 .iter()
336 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
337 .collect();
338
339 (new_verts, new_tris)
340}
341
342pub fn compute_adjacency(tris: &[[usize; 3]], n_verts: usize) -> Vec<Vec<usize>> {
349 let mut adj: Vec<std::collections::BTreeSet<usize>> =
350 vec![std::collections::BTreeSet::new(); n_verts];
351 for tri in tris {
352 let [a, b, c] = *tri;
353 adj[a].insert(b);
354 adj[a].insert(c);
355 adj[b].insert(a);
356 adj[b].insert(c);
357 adj[c].insert(a);
358 adj[c].insert(b);
359 }
360 adj.into_iter().map(|s| s.into_iter().collect()).collect()
361}
362
363pub fn is_watertight(tris: &[[usize; 3]]) -> bool {
372 use std::collections::HashMap;
373 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
374 for tri in tris {
375 let [a, b, c] = *tri;
376 let edges = [
377 (a.min(b), a.max(b)),
378 (b.min(c), b.max(c)),
379 (a.min(c), a.max(c)),
380 ];
381 for e in edges {
382 *edge_count.entry(e).or_insert(0) += 1;
383 }
384 }
385 edge_count.values().all(|&cnt| cnt == 2)
386}
387
388pub fn compute_genus(n_verts: usize, tris: &[[usize; 3]]) -> i32 {
395 use std::collections::HashSet;
396 let v = n_verts as i32;
397 let f = tris.len() as i32;
398 let mut edges: HashSet<(usize, usize)> = HashSet::new();
399 for tri in tris {
400 let [a, b, c] = *tri;
401 edges.insert((a.min(b), a.max(b)));
402 edges.insert((b.min(c), b.max(c)));
403 edges.insert((a.min(c), a.max(c)));
404 }
405 let e = edges.len() as i32;
406 let chi = v - e + f; (2 - chi) / 2
408}
409
410pub fn triangulate_polygon(polygon: &[[f64; 3]]) -> Vec<[usize; 3]> {
420 let n = polygon.len();
421 assert!(n >= 3, "triangulate_polygon needs at least 3 vertices");
422 if n == 3 {
423 return vec![[0, 1, 2]];
424 }
425
426 let mut remaining: Vec<usize> = (0..n).collect();
428 let mut result = Vec::with_capacity(n - 2);
429
430 let mut poly_normal = [0.0f64; 3];
432 for i in 0..n {
433 let j = (i + 1) % n;
434 let p = polygon[i];
435 let q = polygon[j];
436 poly_normal[0] += (p[1] - q[1]) * (p[2] + q[2]);
437 poly_normal[1] += (p[2] - q[2]) * (p[0] + q[0]);
438 poly_normal[2] += (p[0] - q[0]) * (p[1] + q[1]);
439 }
440 let poly_normal = mo_normalize(poly_normal);
441
442 let mut iterations = 0;
443 while remaining.len() > 3 {
444 let m = remaining.len();
445 let mut ear_found = false;
446
447 for i in 0..m {
448 let prev = remaining[(i + m - 1) % m];
449 let curr = remaining[i];
450 let next = remaining[(i + 1) % m];
451
452 let a = polygon[prev];
453 let b = polygon[curr];
454 let c = polygon[next];
455
456 let ab = mo_sub(b, a);
458 let ac = mo_sub(c, a);
459 let cross = mo_cross(ab, ac);
460 let dot_with_normal =
461 cross[0] * poly_normal[0] + cross[1] * poly_normal[1] + cross[2] * poly_normal[2];
462 if dot_with_normal < 0.0 {
463 continue; }
465
466 let is_ear = remaining.iter().all(|&vi| {
468 if vi == prev || vi == curr || vi == next {
469 return true;
470 }
471 let p = polygon[vi];
472 !point_in_triangle_3d(p, a, b, c, poly_normal)
473 });
474
475 if is_ear {
476 result.push([prev, curr, next]);
477 remaining.remove(i);
478 ear_found = true;
479 break;
480 }
481 }
482
483 if !ear_found {
484 if remaining.len() >= 3 {
486 result.push([remaining[0], remaining[1], remaining[2]]);
487 remaining.remove(1);
488 } else {
489 break;
490 }
491 }
492
493 iterations += 1;
494 if iterations > n * n {
495 break; }
497 }
498
499 if remaining.len() == 3 {
500 result.push([remaining[0], remaining[1], remaining[2]]);
501 }
502
503 result
504}
505
506fn point_in_triangle_3d(
509 p: [f64; 3],
510 a: [f64; 3],
511 b: [f64; 3],
512 c: [f64; 3],
513 normal: [f64; 3],
514) -> bool {
515 let ab = mo_sub(b, a);
517 let bc = mo_sub(c, b);
518 let ca = mo_sub(a, c);
519 let ap = mo_sub(p, a);
520 let bp = mo_sub(p, b);
521 let cp = mo_sub(p, c);
522
523 let n0 = mo_cross(ab, ap);
524 let n1 = mo_cross(bc, bp);
525 let n2 = mo_cross(ca, cp);
526
527 let dot_n = |v: [f64; 3]| v[0] * normal[0] + v[1] * normal[1] + v[2] * normal[2];
528
529 let d0 = dot_n(n0);
530 let d1 = dot_n(n1);
531 let d2 = dot_n(n2);
532
533 d0 >= 0.0 && d1 >= 0.0 && d2 >= 0.0
534}
535
536fn ray_intersects_triangle_z(origin: Vec3, v0: Vec3, v1: Vec3, v2: Vec3) -> bool {
540 let dir = Vec3::new(0.0, 0.0, 1.0);
542 let edge1 = v1 - v0;
543 let edge2 = v2 - v0;
544 let h = dir.cross(&edge2);
545 let det = edge1.dot(&h);
546 if det.abs() < 1e-10 {
547 return false;
548 }
549 let inv_det = 1.0 / det;
550 let s = origin - v0;
551 let u = inv_det * s.dot(&h);
552 if !(0.0..=1.0).contains(&u) {
553 return false;
554 }
555 let q = s.cross(&edge1);
556 let v = inv_det * dir.dot(&q);
557 if v < 0.0 || u + v > 1.0 {
558 return false;
559 }
560 let t = inv_det * edge2.dot(&q);
561 t > 0.0
562}
563
564pub fn quadric_decimate(
571 verts: &[[f64; 3]],
572 tris: &[[usize; 3]],
573 n_collapses: usize,
574) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
575 let n = verts.len();
576 #[allow(non_snake_case)]
580 let mut q_mat: Vec<[f64; 10]> = vec![[0.0; 10]; n];
581
582 for tri in tris {
583 let a = verts[tri[0]];
584 let b = verts[tri[1]];
585 let c = verts[tri[2]];
586 let e1 = mo_sub(b, a);
587 let e2 = mo_sub(c, a);
588 let nn = mo_cross(e1, e2);
589 let len = mo_norm(nn);
590 if len < 1e-14 {
591 continue;
592 }
593 let n4 = [
594 nn[0] / len,
595 nn[1] / len,
596 nn[2] / len,
597 -(nn[0] * a[0] + nn[1] * a[1] + nn[2] * a[2]) / len,
598 ];
599 for &vi in tri {
601 let q = &mut q_mat[vi];
602 q[0] += n4[0] * n4[0];
603 q[1] += n4[0] * n4[1];
604 q[2] += n4[0] * n4[2];
605 q[3] += n4[0] * n4[3];
606 q[4] += n4[1] * n4[1];
607 q[5] += n4[1] * n4[2];
608 q[6] += n4[1] * n4[3];
609 q[7] += n4[2] * n4[2];
610 q[8] += n4[2] * n4[3];
611 q[9] += n4[3] * n4[3];
612 }
613 }
614
615 let mut cur_verts: Vec<[f64; 3]> = verts.to_vec();
616 let mut cur_tris: Vec<[usize; 3]> = tris.to_vec();
617 let mut remap: Vec<usize> = (0..n).collect();
618
619 let add_q = |qa: &[f64; 10], qb: &[f64; 10]| -> [f64; 10] {
620 let mut r = [0.0f64; 10];
621 for i in 0..10 {
622 r[i] = qa[i] + qb[i];
623 }
624 r
625 };
626
627 let eval_q = |q: &[f64; 10], v: [f64; 3]| -> f64 {
628 let p = [v[0], v[1], v[2], 1.0];
629 let mut result = 0.0;
631 let idx = |r: usize, c: usize| -> usize {
632 if r <= c {
633 r * 4 - r * (r - 1) / 2 + c - r
634 } else {
635 c * 4 - c * (c - 1) / 2 + r - c
636 }
637 };
638 result += q[0] * p[0] * p[0]
640 + 2.0 * q[1] * p[0] * p[1]
641 + 2.0 * q[2] * p[0] * p[2]
642 + 2.0 * q[3] * p[0] * p[3];
643 result += q[4] * p[1] * p[1] + 2.0 * q[5] * p[1] * p[2] + 2.0 * q[6] * p[1] * p[3];
644 result += q[7] * p[2] * p[2] + 2.0 * q[8] * p[2] * p[3];
645 result += q[9] * p[3] * p[3];
646 let _ = idx;
647 result
648 };
649
650 for _ in 0..n_collapses {
651 let mut best_cost = f64::INFINITY;
653 let mut best_edge = (usize::MAX, usize::MAX);
654 let mut best_pos = [0.0f64; 3];
655
656 let mut edge_set = std::collections::BTreeSet::new();
658 for tri in &cur_tris {
659 for k in 0..3 {
660 let a = remap[tri[k]];
661 let b = remap[tri[(k + 1) % 3]];
662 let key = (a.min(b), a.max(b));
663 edge_set.insert(key);
664 }
665 }
666
667 for (ea, eb) in &edge_set {
668 let qa = &q_mat[*ea];
669 let qb = &q_mat[*eb];
670 let qsum = add_q(qa, qb);
671 let mid = [
673 (cur_verts[*ea][0] + cur_verts[*eb][0]) * 0.5,
674 (cur_verts[*ea][1] + cur_verts[*eb][1]) * 0.5,
675 (cur_verts[*ea][2] + cur_verts[*eb][2]) * 0.5,
676 ];
677 let cost = eval_q(&qsum, mid);
678 if cost < best_cost {
679 best_cost = cost;
680 best_edge = (*ea, *eb);
681 best_pos = mid;
682 }
683 }
684
685 if best_edge.0 == usize::MAX {
686 break;
687 }
688
689 let (ea, eb) = best_edge;
690 cur_verts[ea] = best_pos;
692 let qa = q_mat[ea];
693 let qb = q_mat[eb];
694 q_mat[ea] = add_q(&qa, &qb);
695
696 for r in remap.iter_mut() {
698 if *r == eb {
699 *r = ea;
700 }
701 }
702
703 cur_tris = cur_tris
705 .iter()
706 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
707 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
708 .collect();
709 }
710
711 (cur_verts, cur_tris)
712}
713
714pub fn laplacian_smooth(
721 verts: &[[f64; 3]],
722 tris: &[[usize; 3]],
723 iterations: usize,
724 lambda: f64,
725) -> Vec<[f64; 3]> {
726 let mut v = verts.to_vec();
727 let adj = compute_adjacency(tris, verts.len());
728 for _ in 0..iterations {
729 let prev = v.clone();
730 for i in 0..v.len() {
731 if adj[i].is_empty() {
732 continue;
733 }
734 let mut avg = [0.0f64; 3];
735 for &nb in &adj[i] {
736 avg[0] += prev[nb][0];
737 avg[1] += prev[nb][1];
738 avg[2] += prev[nb][2];
739 }
740 let k = adj[i].len() as f64;
741 avg[0] /= k;
742 avg[1] /= k;
743 avg[2] /= k;
744 v[i][0] += lambda * (avg[0] - prev[i][0]);
745 v[i][1] += lambda * (avg[1] - prev[i][1]);
746 v[i][2] += lambda * (avg[2] - prev[i][2]);
747 }
748 }
749 v
750}
751
752pub fn merge_meshes(
757 verts1: &[[f64; 3]],
758 tris1: &[[usize; 3]],
759 verts2: &[[f64; 3]],
760 tris2: &[[usize; 3]],
761) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
762 let offset = verts1.len();
763 let mut verts = verts1.to_vec();
764 verts.extend_from_slice(verts2);
765 let mut tris = tris1.to_vec();
766 for tri in tris2 {
767 tris.push([tri[0] + offset, tri[1] + offset, tri[2] + offset]);
768 }
769 (verts, tris)
770}
771
772pub fn split_edge(verts: &mut Vec<[f64; 3]>, tris: &mut Vec<[usize; 3]>, v0: usize, v1: usize) {
778 let mid_idx = verts.len();
779 let mid = [
780 (verts[v0][0] + verts[v1][0]) * 0.5,
781 (verts[v0][1] + verts[v1][1]) * 0.5,
782 (verts[v0][2] + verts[v1][2]) * 0.5,
783 ];
784 verts.push(mid);
785
786 let mut new_tris: Vec<[usize; 3]> = Vec::new();
787 let mut keep = vec![true; tris.len()];
788
789 for (ti, tri) in tris.iter().enumerate() {
790 let has_edge = (tri[0] == v0 || tri[1] == v0 || tri[2] == v0)
791 && (tri[0] == v1 || tri[1] == v1 || tri[2] == v1);
792 if has_edge {
793 keep[ti] = false;
794 let opp = tri
796 .iter()
797 .copied()
798 .find(|&v| v != v0 && v != v1)
799 .unwrap_or(v0);
800 new_tris.push([v0, mid_idx, opp]);
801 new_tris.push([mid_idx, v1, opp]);
802 }
803 }
804
805 let kept: Vec<[usize; 3]> = tris
806 .iter()
807 .enumerate()
808 .filter_map(|(i, &t)| if keep[i] { Some(t) } else { None })
809 .collect();
810 *tris = kept;
811 tris.extend(new_tris);
812}
813
814pub fn find_boundary_edges(tris: &[[usize; 3]]) -> Vec<(usize, usize)> {
820 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
821 for tri in tris {
822 let edges = [
823 (tri[0].min(tri[1]), tri[0].max(tri[1])),
824 (tri[1].min(tri[2]), tri[1].max(tri[2])),
825 (tri[0].min(tri[2]), tri[0].max(tri[2])),
826 ];
827 for e in edges {
828 *edge_count.entry(e).or_insert(0) += 1;
829 }
830 }
831 edge_count
832 .into_iter()
833 .filter(|(_, cnt)| *cnt == 1)
834 .map(|(e, _)| e)
835 .collect()
836}
837
838pub fn vertex_cluster_simplify(
845 verts: &[[f64; 3]],
846 tris: &[[usize; 3]],
847 grid_res: usize,
848) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
849 if verts.is_empty() || grid_res == 0 {
850 return (verts.to_vec(), tris.to_vec());
851 }
852
853 let mut bb_min = verts[0];
855 let mut bb_max = verts[0];
856 for v in verts {
857 for k in 0..3 {
858 if v[k] < bb_min[k] {
859 bb_min[k] = v[k];
860 }
861 if v[k] > bb_max[k] {
862 bb_max[k] = v[k];
863 }
864 }
865 }
866 let extent = [
867 (bb_max[0] - bb_min[0]).max(1e-12),
868 (bb_max[1] - bb_min[1]).max(1e-12),
869 (bb_max[2] - bb_min[2]).max(1e-12),
870 ];
871
872 let cell = |v: &[f64; 3]| -> (usize, usize, usize) {
873 let cx = ((v[0] - bb_min[0]) / extent[0] * grid_res as f64) as usize;
874 let cy = ((v[1] - bb_min[1]) / extent[1] * grid_res as f64) as usize;
875 let cz = ((v[2] - bb_min[2]) / extent[2] * grid_res as f64) as usize;
876 (
877 cx.min(grid_res - 1),
878 cy.min(grid_res - 1),
879 cz.min(grid_res - 1),
880 )
881 };
882
883 let mut cell_to_idx: HashMap<(usize, usize, usize), usize> = HashMap::new();
884 let mut cell_sums: Vec<[f64; 3]> = Vec::new();
885 let mut cell_counts: Vec<usize> = Vec::new();
886 let mut remap: Vec<usize> = vec![0; verts.len()];
887
888 for (i, v) in verts.iter().enumerate() {
889 let c = cell(v);
890 let idx = *cell_to_idx.entry(c).or_insert_with(|| {
891 let idx = cell_sums.len();
892 cell_sums.push([0.0; 3]);
893 cell_counts.push(0);
894 idx
895 });
896 cell_sums[idx][0] += v[0];
897 cell_sums[idx][1] += v[1];
898 cell_sums[idx][2] += v[2];
899 cell_counts[idx] += 1;
900 remap[i] = idx;
901 }
902
903 let new_verts: Vec<[f64; 3]> = cell_sums
904 .iter()
905 .zip(cell_counts.iter())
906 .map(|(s, &c)| {
907 let cf = c as f64;
908 [s[0] / cf, s[1] / cf, s[2] / cf]
909 })
910 .collect();
911
912 let new_tris: Vec<[usize; 3]> = tris
913 .iter()
914 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
915 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
916 .collect();
917
918 (new_verts, new_tris)
919}
920
921pub fn edge_length_stats(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> (f64, f64, f64) {
925 let mut min_len = f64::INFINITY;
926 let mut max_len = 0.0f64;
927 let mut total_len = 0.0f64;
928 let mut count = 0usize;
929
930 use std::collections::BTreeSet;
931 let mut seen: BTreeSet<(usize, usize)> = BTreeSet::new();
932 for tri in tris {
933 for k in 0..3 {
934 let a = tri[k];
935 let b = tri[(k + 1) % 3];
936 let key = (a.min(b), a.max(b));
937 if seen.insert(key) {
938 let d = mo_norm(mo_sub(verts[b], verts[a]));
939 min_len = min_len.min(d);
940 max_len = max_len.max(d);
941 total_len += d;
942 count += 1;
943 }
944 }
945 }
946 let avg = if count > 0 {
947 total_len / count as f64
948 } else {
949 0.0
950 };
951 (min_len, max_len, avg)
952}
953
954#[cfg(test)]
955mod tests {
956 use super::*;
957 use std::f64::consts::PI;
958
959 #[test]
960 fn test_trimesh_face_normal() {
961 let verts = vec![
963 Vec3::new(0.0, 0.0, 0.0),
964 Vec3::new(1.0, 0.0, 0.0),
965 Vec3::new(0.0, 1.0, 0.0),
966 ];
967 let tris = vec![[0, 1, 2]];
968 let mesh = TriMesh::new(verts, tris);
969 let n = mesh.face_normal(0);
970 assert!((n.z - 1.0).abs() < 1e-10, "normal.z={}, expected 1.0", n.z);
971 assert!(n.x.abs() < 1e-10);
972 assert!(n.y.abs() < 1e-10);
973 }
974
975 #[test]
976 fn test_trimesh_volume_sphere() {
977 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
978 let vol = mesh.signed_volume().abs();
979 let expected = (4.0 / 3.0) * PI; let rel_err = (vol - expected).abs() / expected;
981 assert!(
982 rel_err < 0.01,
983 "sphere volume: got={}, expected≈{}, rel_err={}",
984 vol,
985 expected,
986 rel_err
987 );
988 }
989
990 #[test]
991 fn test_trimesh_surface_area_sphere() {
992 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
993 let sa = mesh.surface_area();
994 let expected = 4.0 * PI; let rel_err = (sa - expected).abs() / expected;
996 assert!(
997 rel_err < 0.01,
998 "sphere surface area: got={}, expected≈{}, rel_err={}",
999 sa,
1000 expected,
1001 rel_err
1002 );
1003 }
1004
1005 #[test]
1006 fn test_trimesh_vertex_normals() {
1007 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
1010 let vnormals = mesh.vertex_normals();
1011 for (i, (v, n)) in mesh.vertices.iter().zip(vnormals.iter()).enumerate() {
1012 let v_len = v.norm();
1013 if v_len < 1e-6 {
1014 continue; }
1016 let v_norm = v / v_len;
1017 let dot = v_norm.dot(n);
1018 assert!(
1019 dot > 0.0,
1020 "vertex {} normal does not point outward: dot={}",
1021 i,
1022 dot
1023 );
1024 }
1025 }
1026
1027 #[test]
1028 fn test_trimesh_contains_point() {
1029 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
1030 let inside = mesh.contains_point(Vec3::new(0.0, 0.0, 0.0));
1032 assert!(inside, "center of sphere should be inside");
1033 let outside = mesh.contains_point(Vec3::new(10.0, 0.0, 0.0));
1035 assert!(!outside, "far point should be outside");
1036 }
1037
1038 #[test]
1039 fn test_trimesh_bounding_box() {
1040 let r = 1.5;
1041 let mesh = TriMesh::uv_sphere(r, 20, 40);
1042 let (min, max) = mesh.bounding_box();
1043 assert!(min.x >= -r - 1e-10, "min.x={} < -r={}", min.x, -r);
1045 assert!(min.y >= -r - 1e-10, "min.y={} < -r={}", min.y, -r);
1046 assert!(min.z >= -r - 1e-10, "min.z={} < -r={}", min.z, -r);
1047 assert!(max.x <= r + 1e-10, "max.x={} > r={}", max.x, r);
1048 assert!(max.y <= r + 1e-10, "max.y={} > r={}", max.y, r);
1049 assert!(max.z <= r + 1e-10, "max.z={} > r={}", max.z, r);
1050 assert!(max.x > r * 0.9, "max.x={} too small", max.x);
1052 assert!(max.y > r * 0.9, "max.y={} too small", max.y);
1053 assert!(max.z > r * 0.9, "max.z={} too small", max.z);
1054 }
1055
1056 fn unit_tetrahedron() -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
1060 let verts = vec![
1061 [0.0f64, 0.0, 0.0],
1062 [1.0, 0.0, 0.0],
1063 [0.5, 1.0, 0.0],
1064 [0.5, 0.5, 1.0],
1065 ];
1066 let tris = vec![[0usize, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]];
1067 (verts, tris)
1068 }
1069
1070 #[test]
1071 fn test_compute_face_normals_unit_length() {
1072 let (verts, tris) = unit_tetrahedron();
1073 let normals = compute_face_normals(&verts, &tris);
1074 assert_eq!(normals.len(), tris.len());
1075 for (i, n) in normals.iter().enumerate() {
1076 let len = (n[0].powi(2) + n[1].powi(2) + n[2].powi(2)).sqrt();
1077 assert!(
1078 (len - 1.0).abs() < 1e-10,
1079 "face {i} normal not unit: len={len}"
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn test_compute_face_normals_xy_plane() {
1086 let verts = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1088 let tris = vec![[0usize, 1, 2]];
1089 let normals = compute_face_normals(&verts, &tris);
1090 assert!(
1091 (normals[0][2].abs() - 1.0).abs() < 1e-10,
1092 "nz={}",
1093 normals[0][2]
1094 );
1095 }
1096
1097 #[test]
1098 fn test_compute_vertex_normals_count() {
1099 let (verts, tris) = unit_tetrahedron();
1100 let vnormals = compute_vertex_normals(&verts, &tris);
1101 assert_eq!(vnormals.len(), verts.len());
1102 }
1103
1104 #[test]
1105 fn test_compute_vertex_normals_unit_length() {
1106 let (verts, tris) = unit_tetrahedron();
1107 let vnormals = compute_vertex_normals(&verts, &tris);
1108 for (i, n) in vnormals.iter().enumerate() {
1109 let len = (n[0].powi(2) + n[1].powi(2) + n[2].powi(2)).sqrt();
1110 assert!(
1111 (len - 1.0).abs() < 1e-10,
1112 "vertex {i} normal not unit: {len}"
1113 );
1114 }
1115 }
1116
1117 #[test]
1118 fn test_weld_vertices_basic() {
1119 let verts = vec![
1121 [0.0f64, 0.0, 0.0],
1122 [0.0, 0.0, 1e-10], [1.0, 0.0, 0.0],
1124 ];
1125 let tris = vec![[0usize, 1, 2]];
1126 let (new_verts, _new_tris) = weld_vertices(&verts, &tris, 1e-6);
1127 assert_eq!(new_verts.len(), 2, "expected 2 unique verts after weld");
1128 }
1129
1130 #[test]
1131 fn test_weld_vertices_no_merge() {
1132 let verts = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1134 let tris = vec![[0usize, 1, 2]];
1135 let (new_verts, _) = weld_vertices(&verts, &tris, 1e-6);
1136 assert_eq!(new_verts.len(), 3);
1137 }
1138
1139 #[test]
1140 fn test_compute_adjacency_tetrahedron() {
1141 let (verts, tris) = unit_tetrahedron();
1142 let adj = compute_adjacency(&tris, verts.len());
1143 assert_eq!(adj.len(), 4);
1145 for (i, neighbors) in adj.iter().enumerate() {
1146 assert_eq!(neighbors.len(), 3, "vertex {i} should have 3 neighbors");
1147 }
1148 }
1149
1150 #[test]
1151 fn test_is_watertight_tetrahedron() {
1152 let (_verts, tris) = unit_tetrahedron();
1153 assert!(is_watertight(&tris), "tetrahedron should be watertight");
1154 }
1155
1156 #[test]
1157 fn test_is_watertight_open_mesh() {
1158 let tris = vec![[0usize, 1, 2]];
1160 assert!(
1161 !is_watertight(&tris),
1162 "single triangle should not be watertight"
1163 );
1164 }
1165
1166 #[test]
1167 fn test_compute_genus_tetrahedron() {
1168 let (verts, tris) = unit_tetrahedron();
1170 let g = compute_genus(verts.len(), &tris);
1171 assert_eq!(g, 0, "tetrahedron genus should be 0");
1172 }
1173
1174 #[test]
1175 fn test_triangulate_polygon_triangle() {
1176 let poly = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
1177 let result = triangulate_polygon(&poly);
1178 assert_eq!(result.len(), 1, "triangle polygon = 1 tri");
1179 assert_eq!(result[0], [0, 1, 2]);
1180 }
1181
1182 #[test]
1183 fn test_triangulate_polygon_quad() {
1184 let poly = vec![
1186 [0.0f64, 0.0, 0.0],
1187 [1.0, 0.0, 0.0],
1188 [1.0, 1.0, 0.0],
1189 [0.0, 1.0, 0.0],
1190 ];
1191 let result = triangulate_polygon(&poly);
1192 assert_eq!(result.len(), 2, "quad polygon = 2 tris");
1193 }
1194
1195 #[test]
1196 fn test_triangulate_polygon_pentagon() {
1197 let poly: Vec<[f64; 3]> = (0..5)
1199 .map(|i| {
1200 let a = 2.0 * std::f64::consts::PI * i as f64 / 5.0;
1201 [a.cos(), a.sin(), 0.0]
1202 })
1203 .collect();
1204 let result = triangulate_polygon(&poly);
1205 assert_eq!(result.len(), 3, "pentagon polygon = 3 tris");
1206 }
1207
1208 #[test]
1211 fn test_decimate_reduces_triangles() {
1212 let (verts, tris) = unit_tetrahedron();
1213 let (_, new_tris) = quadric_decimate(&verts, &tris, 2);
1214 assert!(
1216 new_tris.len() <= tris.len(),
1217 "decimation should not increase triangles"
1218 );
1219 }
1220
1221 #[test]
1222 fn test_decimate_preserves_non_degenerate_tris() {
1223 let (verts, tris) = unit_tetrahedron();
1224 let (new_verts, new_tris) = quadric_decimate(&verts, &tris, 1);
1225 for tri in &new_tris {
1226 let a = new_verts[tri[0]];
1227 let b = new_verts[tri[1]];
1228 let c = new_verts[tri[2]];
1229 let e1 = mo_sub(b, a);
1230 let e2 = mo_sub(c, a);
1231 let cross = mo_cross(e1, e2);
1232 let area = mo_norm(cross) * 0.5;
1233 assert!(area >= 0.0, "negative area triangle");
1234 }
1235 }
1236
1237 #[test]
1240 fn test_laplacian_smooth_reduces_roughness() {
1241 let (mut verts, tris) = unit_tetrahedron();
1243 verts[0] = [0.5, 0.5, 0.5]; let smoothed = laplacian_smooth(&verts, &tris, 5, 0.5);
1245 assert_eq!(smoothed.len(), verts.len());
1247 }
1248
1249 #[test]
1250 fn test_laplacian_smooth_returns_correct_count() {
1251 let (verts, tris) = unit_tetrahedron();
1252 let smoothed = laplacian_smooth(&verts, &tris, 3, 0.5);
1253 assert_eq!(
1254 smoothed.len(),
1255 verts.len(),
1256 "smoothed should have same vertex count"
1257 );
1258 }
1259
1260 #[test]
1263 fn test_merge_meshes_vertex_count() {
1264 let (v1, t1) = unit_tetrahedron();
1265 let v2: Vec<[f64; 3]> = v1.iter().map(|&v| [v[0] + 5.0, v[1], v[2]]).collect();
1266 let (mv, mt) = merge_meshes(&v1, &t1, &v2, &t1);
1267 assert_eq!(mv.len(), v1.len() + v2.len(), "merged vertices");
1268 assert_eq!(mt.len(), t1.len() * 2, "merged triangles");
1269 }
1270
1271 #[test]
1272 fn test_merge_meshes_index_offset() {
1273 let (v1, t1) = unit_tetrahedron();
1274 let v2: Vec<[f64; 3]> = v1.iter().map(|&v| [v[0] + 5.0, v[1], v[2]]).collect();
1275 let (mv, mt) = merge_meshes(&v1, &t1, &v2, &t1);
1276 for tri in &mt[t1.len()..] {
1278 for &vi in tri {
1279 assert!(vi >= v1.len(), "index {} should be offset", vi);
1280 }
1281 }
1282 assert_eq!(mv.len(), v1.len() + v2.len());
1283 }
1284
1285 #[test]
1288 fn test_split_edge_increases_vertices() {
1289 let (mut verts, mut tris) = unit_tetrahedron();
1290 let n_before = verts.len();
1291 split_edge(&mut verts, &mut tris, 0, 1);
1292 assert_eq!(verts.len(), n_before + 1, "split should add one vertex");
1293 }
1294
1295 #[test]
1296 fn test_split_edge_increases_triangles() {
1297 let (mut verts, mut tris) = unit_tetrahedron();
1298 let n_before = tris.len();
1299 split_edge(&mut verts, &mut tris, 0, 1);
1300 assert!(tris.len() > n_before, "split should add triangles");
1301 }
1302
1303 #[test]
1306 fn test_find_boundary_edges_tetrahedron() {
1307 let (_verts, tris) = unit_tetrahedron();
1308 let boundary = find_boundary_edges(&tris);
1309 assert!(
1311 boundary.is_empty(),
1312 "tetrahedron has no boundary, got {:?}",
1313 boundary
1314 );
1315 }
1316
1317 #[test]
1318 fn test_find_boundary_edges_open_mesh() {
1319 let tris = vec![[0usize, 1, 2], [1, 3, 2]];
1321 let boundary = find_boundary_edges(&tris);
1322 assert!(!boundary.is_empty(), "open mesh should have boundary edges");
1323 }
1324}