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 let mut q_mat: Vec<[f64; 10]> = vec![[0.0; 10]; n];
580
581 for tri in tris {
582 let a = verts[tri[0]];
583 let b = verts[tri[1]];
584 let c = verts[tri[2]];
585 let e1 = mo_sub(b, a);
586 let e2 = mo_sub(c, a);
587 let nn = mo_cross(e1, e2);
588 let len = mo_norm(nn);
589 if len < 1e-14 {
590 continue;
591 }
592 let n4 = [
593 nn[0] / len,
594 nn[1] / len,
595 nn[2] / len,
596 -(nn[0] * a[0] + nn[1] * a[1] + nn[2] * a[2]) / len,
597 ];
598 for &vi in tri {
600 let q = &mut q_mat[vi];
601 q[0] += n4[0] * n4[0];
602 q[1] += n4[0] * n4[1];
603 q[2] += n4[0] * n4[2];
604 q[3] += n4[0] * n4[3];
605 q[4] += n4[1] * n4[1];
606 q[5] += n4[1] * n4[2];
607 q[6] += n4[1] * n4[3];
608 q[7] += n4[2] * n4[2];
609 q[8] += n4[2] * n4[3];
610 q[9] += n4[3] * n4[3];
611 }
612 }
613
614 let mut cur_verts: Vec<[f64; 3]> = verts.to_vec();
615 let mut cur_tris: Vec<[usize; 3]> = tris.to_vec();
616 let mut remap: Vec<usize> = (0..n).collect();
617
618 let add_q = |qa: &[f64; 10], qb: &[f64; 10]| -> [f64; 10] {
619 let mut r = [0.0f64; 10];
620 for i in 0..10 {
621 r[i] = qa[i] + qb[i];
622 }
623 r
624 };
625
626 let eval_q = |q: &[f64; 10], v: [f64; 3]| -> f64 {
627 let p = [v[0], v[1], v[2], 1.0];
628 let mut result = 0.0;
630 let idx = |r: usize, c: usize| -> usize {
631 if r <= c {
632 r * 4 - r * (r - 1) / 2 + c - r
633 } else {
634 c * 4 - c * (c - 1) / 2 + r - c
635 }
636 };
637 result += q[0] * p[0] * p[0]
639 + 2.0 * q[1] * p[0] * p[1]
640 + 2.0 * q[2] * p[0] * p[2]
641 + 2.0 * q[3] * p[0] * p[3];
642 result += q[4] * p[1] * p[1] + 2.0 * q[5] * p[1] * p[2] + 2.0 * q[6] * p[1] * p[3];
643 result += q[7] * p[2] * p[2] + 2.0 * q[8] * p[2] * p[3];
644 result += q[9] * p[3] * p[3];
645 let _ = idx;
646 result
647 };
648
649 for _ in 0..n_collapses {
650 let mut best_cost = f64::INFINITY;
652 let mut best_edge = (usize::MAX, usize::MAX);
653 let mut best_pos = [0.0f64; 3];
654
655 let mut edge_set = std::collections::BTreeSet::new();
657 for tri in &cur_tris {
658 for k in 0..3 {
659 let a = remap[tri[k]];
660 let b = remap[tri[(k + 1) % 3]];
661 let key = (a.min(b), a.max(b));
662 edge_set.insert(key);
663 }
664 }
665
666 for (ea, eb) in &edge_set {
667 let qa = &q_mat[*ea];
668 let qb = &q_mat[*eb];
669 let qsum = add_q(qa, qb);
670 let mid = [
672 (cur_verts[*ea][0] + cur_verts[*eb][0]) * 0.5,
673 (cur_verts[*ea][1] + cur_verts[*eb][1]) * 0.5,
674 (cur_verts[*ea][2] + cur_verts[*eb][2]) * 0.5,
675 ];
676 let cost = eval_q(&qsum, mid);
677 if cost < best_cost {
678 best_cost = cost;
679 best_edge = (*ea, *eb);
680 best_pos = mid;
681 }
682 }
683
684 if best_edge.0 == usize::MAX {
685 break;
686 }
687
688 let (ea, eb) = best_edge;
689 cur_verts[ea] = best_pos;
691 let qa = q_mat[ea];
692 let qb = q_mat[eb];
693 q_mat[ea] = add_q(&qa, &qb);
694
695 for r in remap.iter_mut() {
697 if *r == eb {
698 *r = ea;
699 }
700 }
701
702 cur_tris = cur_tris
704 .iter()
705 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
706 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
707 .collect();
708 }
709
710 (cur_verts, cur_tris)
711}
712
713pub fn laplacian_smooth(
720 verts: &[[f64; 3]],
721 tris: &[[usize; 3]],
722 iterations: usize,
723 lambda: f64,
724) -> Vec<[f64; 3]> {
725 let mut v = verts.to_vec();
726 let adj = compute_adjacency(tris, verts.len());
727 for _ in 0..iterations {
728 let prev = v.clone();
729 for i in 0..v.len() {
730 if adj[i].is_empty() {
731 continue;
732 }
733 let mut avg = [0.0f64; 3];
734 for &nb in &adj[i] {
735 avg[0] += prev[nb][0];
736 avg[1] += prev[nb][1];
737 avg[2] += prev[nb][2];
738 }
739 let k = adj[i].len() as f64;
740 avg[0] /= k;
741 avg[1] /= k;
742 avg[2] /= k;
743 v[i][0] += lambda * (avg[0] - prev[i][0]);
744 v[i][1] += lambda * (avg[1] - prev[i][1]);
745 v[i][2] += lambda * (avg[2] - prev[i][2]);
746 }
747 }
748 v
749}
750
751pub fn merge_meshes(
756 verts1: &[[f64; 3]],
757 tris1: &[[usize; 3]],
758 verts2: &[[f64; 3]],
759 tris2: &[[usize; 3]],
760) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
761 let offset = verts1.len();
762 let mut verts = verts1.to_vec();
763 verts.extend_from_slice(verts2);
764 let mut tris = tris1.to_vec();
765 for tri in tris2 {
766 tris.push([tri[0] + offset, tri[1] + offset, tri[2] + offset]);
767 }
768 (verts, tris)
769}
770
771pub fn split_edge(verts: &mut Vec<[f64; 3]>, tris: &mut Vec<[usize; 3]>, v0: usize, v1: usize) {
777 let mid_idx = verts.len();
778 let mid = [
779 (verts[v0][0] + verts[v1][0]) * 0.5,
780 (verts[v0][1] + verts[v1][1]) * 0.5,
781 (verts[v0][2] + verts[v1][2]) * 0.5,
782 ];
783 verts.push(mid);
784
785 let mut new_tris: Vec<[usize; 3]> = Vec::new();
786 let mut keep = vec![true; tris.len()];
787
788 for (ti, tri) in tris.iter().enumerate() {
789 let has_edge = (tri[0] == v0 || tri[1] == v0 || tri[2] == v0)
790 && (tri[0] == v1 || tri[1] == v1 || tri[2] == v1);
791 if has_edge {
792 keep[ti] = false;
793 let opp = tri
795 .iter()
796 .copied()
797 .find(|&v| v != v0 && v != v1)
798 .unwrap_or(v0);
799 new_tris.push([v0, mid_idx, opp]);
800 new_tris.push([mid_idx, v1, opp]);
801 }
802 }
803
804 let kept: Vec<[usize; 3]> = tris
805 .iter()
806 .enumerate()
807 .filter_map(|(i, &t)| if keep[i] { Some(t) } else { None })
808 .collect();
809 *tris = kept;
810 tris.extend(new_tris);
811}
812
813pub fn find_boundary_edges(tris: &[[usize; 3]]) -> Vec<(usize, usize)> {
819 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
820 for tri in tris {
821 let edges = [
822 (tri[0].min(tri[1]), tri[0].max(tri[1])),
823 (tri[1].min(tri[2]), tri[1].max(tri[2])),
824 (tri[0].min(tri[2]), tri[0].max(tri[2])),
825 ];
826 for e in edges {
827 *edge_count.entry(e).or_insert(0) += 1;
828 }
829 }
830 edge_count
831 .into_iter()
832 .filter(|(_, cnt)| *cnt == 1)
833 .map(|(e, _)| e)
834 .collect()
835}
836
837pub fn vertex_cluster_simplify(
844 verts: &[[f64; 3]],
845 tris: &[[usize; 3]],
846 grid_res: usize,
847) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
848 if verts.is_empty() || grid_res == 0 {
849 return (verts.to_vec(), tris.to_vec());
850 }
851
852 let mut bb_min = verts[0];
854 let mut bb_max = verts[0];
855 for v in verts {
856 for k in 0..3 {
857 if v[k] < bb_min[k] {
858 bb_min[k] = v[k];
859 }
860 if v[k] > bb_max[k] {
861 bb_max[k] = v[k];
862 }
863 }
864 }
865 let extent = [
866 (bb_max[0] - bb_min[0]).max(1e-12),
867 (bb_max[1] - bb_min[1]).max(1e-12),
868 (bb_max[2] - bb_min[2]).max(1e-12),
869 ];
870
871 let cell = |v: &[f64; 3]| -> (usize, usize, usize) {
872 let cx = ((v[0] - bb_min[0]) / extent[0] * grid_res as f64) as usize;
873 let cy = ((v[1] - bb_min[1]) / extent[1] * grid_res as f64) as usize;
874 let cz = ((v[2] - bb_min[2]) / extent[2] * grid_res as f64) as usize;
875 (
876 cx.min(grid_res - 1),
877 cy.min(grid_res - 1),
878 cz.min(grid_res - 1),
879 )
880 };
881
882 let mut cell_to_idx: HashMap<(usize, usize, usize), usize> = HashMap::new();
883 let mut cell_sums: Vec<[f64; 3]> = Vec::new();
884 let mut cell_counts: Vec<usize> = Vec::new();
885 let mut remap: Vec<usize> = vec![0; verts.len()];
886
887 for (i, v) in verts.iter().enumerate() {
888 let c = cell(v);
889 let idx = *cell_to_idx.entry(c).or_insert_with(|| {
890 let idx = cell_sums.len();
891 cell_sums.push([0.0; 3]);
892 cell_counts.push(0);
893 idx
894 });
895 cell_sums[idx][0] += v[0];
896 cell_sums[idx][1] += v[1];
897 cell_sums[idx][2] += v[2];
898 cell_counts[idx] += 1;
899 remap[i] = idx;
900 }
901
902 let new_verts: Vec<[f64; 3]> = cell_sums
903 .iter()
904 .zip(cell_counts.iter())
905 .map(|(s, &c)| {
906 let cf = c as f64;
907 [s[0] / cf, s[1] / cf, s[2] / cf]
908 })
909 .collect();
910
911 let new_tris: Vec<[usize; 3]> = tris
912 .iter()
913 .map(|tri| [remap[tri[0]], remap[tri[1]], remap[tri[2]]])
914 .filter(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2])
915 .collect();
916
917 (new_verts, new_tris)
918}
919
920pub fn edge_length_stats(verts: &[[f64; 3]], tris: &[[usize; 3]]) -> (f64, f64, f64) {
924 let mut min_len = f64::INFINITY;
925 let mut max_len = 0.0f64;
926 let mut total_len = 0.0f64;
927 let mut count = 0usize;
928
929 use std::collections::BTreeSet;
930 let mut seen: BTreeSet<(usize, usize)> = BTreeSet::new();
931 for tri in tris {
932 for k in 0..3 {
933 let a = tri[k];
934 let b = tri[(k + 1) % 3];
935 let key = (a.min(b), a.max(b));
936 if seen.insert(key) {
937 let d = mo_norm(mo_sub(verts[b], verts[a]));
938 min_len = min_len.min(d);
939 max_len = max_len.max(d);
940 total_len += d;
941 count += 1;
942 }
943 }
944 }
945 let avg = if count > 0 {
946 total_len / count as f64
947 } else {
948 0.0
949 };
950 (min_len, max_len, avg)
951}
952
953#[cfg(test)]
954mod tests {
955 use super::*;
956 use std::f64::consts::PI;
957
958 #[test]
959 fn test_trimesh_face_normal() {
960 let verts = vec![
962 Vec3::new(0.0, 0.0, 0.0),
963 Vec3::new(1.0, 0.0, 0.0),
964 Vec3::new(0.0, 1.0, 0.0),
965 ];
966 let tris = vec![[0, 1, 2]];
967 let mesh = TriMesh::new(verts, tris);
968 let n = mesh.face_normal(0);
969 assert!((n.z - 1.0).abs() < 1e-10, "normal.z={}, expected 1.0", n.z);
970 assert!(n.x.abs() < 1e-10);
971 assert!(n.y.abs() < 1e-10);
972 }
973
974 #[test]
975 fn test_trimesh_volume_sphere() {
976 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
977 let vol = mesh.signed_volume().abs();
978 let expected = (4.0 / 3.0) * PI; let rel_err = (vol - expected).abs() / expected;
980 assert!(
981 rel_err < 0.01,
982 "sphere volume: got={}, expected≈{}, rel_err={}",
983 vol,
984 expected,
985 rel_err
986 );
987 }
988
989 #[test]
990 fn test_trimesh_surface_area_sphere() {
991 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
992 let sa = mesh.surface_area();
993 let expected = 4.0 * PI; let rel_err = (sa - expected).abs() / expected;
995 assert!(
996 rel_err < 0.01,
997 "sphere surface area: got={}, expected≈{}, rel_err={}",
998 sa,
999 expected,
1000 rel_err
1001 );
1002 }
1003
1004 #[test]
1005 fn test_trimesh_vertex_normals() {
1006 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
1009 let vnormals = mesh.vertex_normals();
1010 for (i, (v, n)) in mesh.vertices.iter().zip(vnormals.iter()).enumerate() {
1011 let v_len = v.norm();
1012 if v_len < 1e-6 {
1013 continue; }
1015 let v_norm = v / v_len;
1016 let dot = v_norm.dot(n);
1017 assert!(
1018 dot > 0.0,
1019 "vertex {} normal does not point outward: dot={}",
1020 i,
1021 dot
1022 );
1023 }
1024 }
1025
1026 #[test]
1027 fn test_trimesh_contains_point() {
1028 let mesh = TriMesh::uv_sphere(1.0, 20, 40);
1029 let inside = mesh.contains_point(Vec3::new(0.0, 0.0, 0.0));
1031 assert!(inside, "center of sphere should be inside");
1032 let outside = mesh.contains_point(Vec3::new(10.0, 0.0, 0.0));
1034 assert!(!outside, "far point should be outside");
1035 }
1036
1037 #[test]
1038 fn test_trimesh_bounding_box() {
1039 let r = 1.5;
1040 let mesh = TriMesh::uv_sphere(r, 20, 40);
1041 let (min, max) = mesh.bounding_box();
1042 assert!(min.x >= -r - 1e-10, "min.x={} < -r={}", min.x, -r);
1044 assert!(min.y >= -r - 1e-10, "min.y={} < -r={}", min.y, -r);
1045 assert!(min.z >= -r - 1e-10, "min.z={} < -r={}", min.z, -r);
1046 assert!(max.x <= r + 1e-10, "max.x={} > r={}", max.x, r);
1047 assert!(max.y <= r + 1e-10, "max.y={} > r={}", max.y, r);
1048 assert!(max.z <= r + 1e-10, "max.z={} > r={}", max.z, r);
1049 assert!(max.x > r * 0.9, "max.x={} too small", max.x);
1051 assert!(max.y > r * 0.9, "max.y={} too small", max.y);
1052 assert!(max.z > r * 0.9, "max.z={} too small", max.z);
1053 }
1054
1055 fn unit_tetrahedron() -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
1059 let verts = vec![
1060 [0.0f64, 0.0, 0.0],
1061 [1.0, 0.0, 0.0],
1062 [0.5, 1.0, 0.0],
1063 [0.5, 0.5, 1.0],
1064 ];
1065 let tris = vec![[0usize, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]];
1066 (verts, tris)
1067 }
1068
1069 #[test]
1070 fn test_compute_face_normals_unit_length() {
1071 let (verts, tris) = unit_tetrahedron();
1072 let normals = compute_face_normals(&verts, &tris);
1073 assert_eq!(normals.len(), tris.len());
1074 for (i, n) in normals.iter().enumerate() {
1075 let len = (n[0].powi(2) + n[1].powi(2) + n[2].powi(2)).sqrt();
1076 assert!(
1077 (len - 1.0).abs() < 1e-10,
1078 "face {i} normal not unit: len={len}"
1079 );
1080 }
1081 }
1082
1083 #[test]
1084 fn test_compute_face_normals_xy_plane() {
1085 let verts = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1087 let tris = vec![[0usize, 1, 2]];
1088 let normals = compute_face_normals(&verts, &tris);
1089 assert!(
1090 (normals[0][2].abs() - 1.0).abs() < 1e-10,
1091 "nz={}",
1092 normals[0][2]
1093 );
1094 }
1095
1096 #[test]
1097 fn test_compute_vertex_normals_count() {
1098 let (verts, tris) = unit_tetrahedron();
1099 let vnormals = compute_vertex_normals(&verts, &tris);
1100 assert_eq!(vnormals.len(), verts.len());
1101 }
1102
1103 #[test]
1104 fn test_compute_vertex_normals_unit_length() {
1105 let (verts, tris) = unit_tetrahedron();
1106 let vnormals = compute_vertex_normals(&verts, &tris);
1107 for (i, n) in vnormals.iter().enumerate() {
1108 let len = (n[0].powi(2) + n[1].powi(2) + n[2].powi(2)).sqrt();
1109 assert!(
1110 (len - 1.0).abs() < 1e-10,
1111 "vertex {i} normal not unit: {len}"
1112 );
1113 }
1114 }
1115
1116 #[test]
1117 fn test_weld_vertices_basic() {
1118 let verts = vec![
1120 [0.0f64, 0.0, 0.0],
1121 [0.0, 0.0, 1e-10], [1.0, 0.0, 0.0],
1123 ];
1124 let tris = vec![[0usize, 1, 2]];
1125 let (new_verts, _new_tris) = weld_vertices(&verts, &tris, 1e-6);
1126 assert_eq!(new_verts.len(), 2, "expected 2 unique verts after weld");
1127 }
1128
1129 #[test]
1130 fn test_weld_vertices_no_merge() {
1131 let verts = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1133 let tris = vec![[0usize, 1, 2]];
1134 let (new_verts, _) = weld_vertices(&verts, &tris, 1e-6);
1135 assert_eq!(new_verts.len(), 3);
1136 }
1137
1138 #[test]
1139 fn test_compute_adjacency_tetrahedron() {
1140 let (verts, tris) = unit_tetrahedron();
1141 let adj = compute_adjacency(&tris, verts.len());
1142 assert_eq!(adj.len(), 4);
1144 for (i, neighbors) in adj.iter().enumerate() {
1145 assert_eq!(neighbors.len(), 3, "vertex {i} should have 3 neighbors");
1146 }
1147 }
1148
1149 #[test]
1150 fn test_is_watertight_tetrahedron() {
1151 let (_verts, tris) = unit_tetrahedron();
1152 assert!(is_watertight(&tris), "tetrahedron should be watertight");
1153 }
1154
1155 #[test]
1156 fn test_is_watertight_open_mesh() {
1157 let tris = vec![[0usize, 1, 2]];
1159 assert!(
1160 !is_watertight(&tris),
1161 "single triangle should not be watertight"
1162 );
1163 }
1164
1165 #[test]
1166 fn test_compute_genus_tetrahedron() {
1167 let (verts, tris) = unit_tetrahedron();
1169 let g = compute_genus(verts.len(), &tris);
1170 assert_eq!(g, 0, "tetrahedron genus should be 0");
1171 }
1172
1173 #[test]
1174 fn test_triangulate_polygon_triangle() {
1175 let poly = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
1176 let result = triangulate_polygon(&poly);
1177 assert_eq!(result.len(), 1, "triangle polygon = 1 tri");
1178 assert_eq!(result[0], [0, 1, 2]);
1179 }
1180
1181 #[test]
1182 fn test_triangulate_polygon_quad() {
1183 let poly = vec![
1185 [0.0f64, 0.0, 0.0],
1186 [1.0, 0.0, 0.0],
1187 [1.0, 1.0, 0.0],
1188 [0.0, 1.0, 0.0],
1189 ];
1190 let result = triangulate_polygon(&poly);
1191 assert_eq!(result.len(), 2, "quad polygon = 2 tris");
1192 }
1193
1194 #[test]
1195 fn test_triangulate_polygon_pentagon() {
1196 let poly: Vec<[f64; 3]> = (0..5)
1198 .map(|i| {
1199 let a = 2.0 * std::f64::consts::PI * i as f64 / 5.0;
1200 [a.cos(), a.sin(), 0.0]
1201 })
1202 .collect();
1203 let result = triangulate_polygon(&poly);
1204 assert_eq!(result.len(), 3, "pentagon polygon = 3 tris");
1205 }
1206
1207 #[test]
1210 fn test_decimate_reduces_triangles() {
1211 let (verts, tris) = unit_tetrahedron();
1212 let (_, new_tris) = quadric_decimate(&verts, &tris, 2);
1213 assert!(
1215 new_tris.len() <= tris.len(),
1216 "decimation should not increase triangles"
1217 );
1218 }
1219
1220 #[test]
1221 fn test_decimate_preserves_non_degenerate_tris() {
1222 let (verts, tris) = unit_tetrahedron();
1223 let (new_verts, new_tris) = quadric_decimate(&verts, &tris, 1);
1224 for tri in &new_tris {
1225 let a = new_verts[tri[0]];
1226 let b = new_verts[tri[1]];
1227 let c = new_verts[tri[2]];
1228 let e1 = mo_sub(b, a);
1229 let e2 = mo_sub(c, a);
1230 let cross = mo_cross(e1, e2);
1231 let area = mo_norm(cross) * 0.5;
1232 assert!(area >= 0.0, "negative area triangle");
1233 }
1234 }
1235
1236 #[test]
1239 fn test_laplacian_smooth_reduces_roughness() {
1240 let (mut verts, tris) = unit_tetrahedron();
1242 verts[0] = [0.5, 0.5, 0.5]; let smoothed = laplacian_smooth(&verts, &tris, 5, 0.5);
1244 assert_eq!(smoothed.len(), verts.len());
1246 }
1247
1248 #[test]
1249 fn test_laplacian_smooth_returns_correct_count() {
1250 let (verts, tris) = unit_tetrahedron();
1251 let smoothed = laplacian_smooth(&verts, &tris, 3, 0.5);
1252 assert_eq!(
1253 smoothed.len(),
1254 verts.len(),
1255 "smoothed should have same vertex count"
1256 );
1257 }
1258
1259 #[test]
1262 fn test_merge_meshes_vertex_count() {
1263 let (v1, t1) = unit_tetrahedron();
1264 let v2: Vec<[f64; 3]> = v1.iter().map(|&v| [v[0] + 5.0, v[1], v[2]]).collect();
1265 let (mv, mt) = merge_meshes(&v1, &t1, &v2, &t1);
1266 assert_eq!(mv.len(), v1.len() + v2.len(), "merged vertices");
1267 assert_eq!(mt.len(), t1.len() * 2, "merged triangles");
1268 }
1269
1270 #[test]
1271 fn test_merge_meshes_index_offset() {
1272 let (v1, t1) = unit_tetrahedron();
1273 let v2: Vec<[f64; 3]> = v1.iter().map(|&v| [v[0] + 5.0, v[1], v[2]]).collect();
1274 let (mv, mt) = merge_meshes(&v1, &t1, &v2, &t1);
1275 for tri in &mt[t1.len()..] {
1277 for &vi in tri {
1278 assert!(vi >= v1.len(), "index {} should be offset", vi);
1279 }
1280 }
1281 assert_eq!(mv.len(), v1.len() + v2.len());
1282 }
1283
1284 #[test]
1287 fn test_split_edge_increases_vertices() {
1288 let (mut verts, mut tris) = unit_tetrahedron();
1289 let n_before = verts.len();
1290 split_edge(&mut verts, &mut tris, 0, 1);
1291 assert_eq!(verts.len(), n_before + 1, "split should add one vertex");
1292 }
1293
1294 #[test]
1295 fn test_split_edge_increases_triangles() {
1296 let (mut verts, mut tris) = unit_tetrahedron();
1297 let n_before = tris.len();
1298 split_edge(&mut verts, &mut tris, 0, 1);
1299 assert!(tris.len() > n_before, "split should add triangles");
1300 }
1301
1302 #[test]
1305 fn test_find_boundary_edges_tetrahedron() {
1306 let (_verts, tris) = unit_tetrahedron();
1307 let boundary = find_boundary_edges(&tris);
1308 assert!(
1310 boundary.is_empty(),
1311 "tetrahedron has no boundary, got {:?}",
1312 boundary
1313 );
1314 }
1315
1316 #[test]
1317 fn test_find_boundary_edges_open_mesh() {
1318 let tris = vec![[0usize, 1, 2], [1, 3, 2]];
1320 let boundary = find_boundary_edges(&tris);
1321 assert!(!boundary.is_empty(), "open mesh should have boundary edges");
1322 }
1323}