1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
11#![allow(clippy::too_many_arguments)]
12
13#[derive(Debug, Clone)]
20pub struct GpuMesh {
21 pub vertices: Vec<[f32; 3]>,
23 pub normals: Vec<[f32; 3]>,
25 pub indices: Vec<[u32; 3]>,
27 pub tex_coords: Vec<[f32; 2]>,
29}
30
31impl GpuMesh {
32 pub fn new() -> Self {
34 Self {
35 vertices: Vec::new(),
36 normals: Vec::new(),
37 indices: Vec::new(),
38 tex_coords: Vec::new(),
39 }
40 }
41
42 pub fn add_vertex(&mut self, pos: [f32; 3], normal: [f32; 3], uv: [f32; 2]) {
44 self.vertices.push(pos);
45 self.normals.push(normal);
46 self.tex_coords.push(uv);
47 }
48
49 pub fn add_triangle(&mut self, i: u32, j: u32, k: u32) {
51 self.indices.push([i, j, k]);
52 }
53
54 pub fn vertex_count(&self) -> usize {
56 self.vertices.len()
57 }
58
59 pub fn triangle_count(&self) -> usize {
61 self.indices.len()
62 }
63}
64
65impl Default for GpuMesh {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71pub fn triangle_area(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> f32 {
77 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
78 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
79 let cross = cross3f(ab, ac);
80 0.5 * length3f(cross)
81}
82
83pub fn triangle_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
87 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
88 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
89 normalize3f(cross3f(ab, ac))
90}
91
92pub fn gpu_compute_normals(mesh: &mut GpuMesh) {
99 let nv = mesh.vertex_count();
100 let mut acc = vec![[0.0f32; 3]; nv];
101
102 for tri in &mesh.indices {
103 let [i, j, k] = [tri[0] as usize, tri[1] as usize, tri[2] as usize];
104 let a = mesh.vertices[i];
105 let b = mesh.vertices[j];
106 let c = mesh.vertices[k];
107 let area = triangle_area(a, b, c);
108 let n = triangle_normal(a, b, c);
109 for idx in [i, j, k] {
110 acc[idx][0] += n[0] * area;
111 acc[idx][1] += n[1] * area;
112 acc[idx][2] += n[2] * area;
113 }
114 }
115
116 for (v_idx, n) in mesh.normals.iter_mut().enumerate() {
117 *n = normalize3f(acc[v_idx]);
118 }
119}
120
121pub fn gpu_smooth_normals(mesh: &mut GpuMesh, n_iter: usize) {
125 let nv = mesh.vertex_count();
126 for _ in 0..n_iter {
127 let mut acc = vec![[0.0f32; 3]; nv];
128 let mut count = vec![0u32; nv];
129 for tri in &mesh.indices {
130 for &vi in tri.iter() {
131 let vi = vi as usize;
132 for &vj in tri.iter() {
133 let vj = vj as usize;
134 acc[vi][0] += mesh.normals[vj][0];
135 acc[vi][1] += mesh.normals[vj][1];
136 acc[vi][2] += mesh.normals[vj][2];
137 count[vi] += 1;
138 }
139 }
140 }
141 for i in 0..nv {
142 let c = count[i].max(1) as f32;
143 mesh.normals[i] = normalize3f([acc[i][0] / c, acc[i][1] / c, acc[i][2] / c]);
144 }
145 }
146}
147
148pub fn gpu_edge_collapse(mesh: &mut GpuMesh, error_threshold: f32) -> usize {
155 let mut removed = 0usize;
157 let nv = mesh.vertex_count();
158 let mut merge_target: Vec<usize> = (0..nv).collect(); for tri in &mesh.indices {
161 let verts = [tri[0] as usize, tri[1] as usize, tri[2] as usize];
162 for edge in [
163 (verts[0], verts[1]),
164 (verts[1], verts[2]),
165 (verts[0], verts[2]),
166 ] {
167 let (i, j) = edge;
168 let a = mesh.vertices[i];
169 let b = mesh.vertices[j];
170 let dist2 = (a[0] - b[0]) * (a[0] - b[0])
171 + (a[1] - b[1]) * (a[1] - b[1])
172 + (a[2] - b[2]) * (a[2] - b[2]);
173 if dist2 < error_threshold * error_threshold {
174 let root_i = find_root(&merge_target, i);
176 let root_j = find_root(&merge_target, j);
177 if root_i != root_j {
178 merge_target[root_j] = root_i;
179 removed += 1;
180 }
181 }
182 }
183 }
184
185 for tri in mesh.indices.iter_mut() {
187 for idx in tri.iter_mut() {
188 *idx = find_root(&merge_target, *idx as usize) as u32;
189 }
190 }
191 mesh.indices
193 .retain(|t| t[0] != t[1] && t[1] != t[2] && t[0] != t[2]);
194 removed
195}
196
197fn find_root(target: &[usize], mut i: usize) -> usize {
199 while target[i] != i {
200 i = target[i];
201 }
202 i
203}
204
205pub fn gpu_loop_subdivision(mesh: &GpuMesh) -> GpuMesh {
213 use std::collections::HashMap;
214
215 let mut new_mesh = GpuMesh::new();
216 for (&v, (&n, &uv)) in mesh
218 .vertices
219 .iter()
220 .zip(mesh.normals.iter().zip(mesh.tex_coords.iter()))
221 {
222 new_mesh.add_vertex(v, n, uv);
223 }
224
225 let mut edge_midpoint: HashMap<(u32, u32), u32> = HashMap::new();
226
227 let get_midpoint = |i: u32,
228 j: u32,
229 verts: &[([f32; 3], [f32; 3], [f32; 2])],
230 cache: &mut HashMap<(u32, u32), u32>,
231 new_v: &mut Vec<[f32; 3]>,
232 new_n: &mut Vec<[f32; 3]>,
233 new_uv: &mut Vec<[f32; 2]>|
234 -> u32 {
235 let key = if i < j { (i, j) } else { (j, i) };
236 if let Some(&m) = cache.get(&key) {
237 return m;
238 }
239 let (a, an, auv) = verts[i as usize];
240 let (b, bn, buv) = verts[j as usize];
241 let mid_v = [
242 (a[0] + b[0]) * 0.5,
243 (a[1] + b[1]) * 0.5,
244 (a[2] + b[2]) * 0.5,
245 ];
246 let mid_n = normalize3f([
247 (an[0] + bn[0]) * 0.5,
248 (an[1] + bn[1]) * 0.5,
249 (an[2] + bn[2]) * 0.5,
250 ]);
251 let mid_uv = [(auv[0] + buv[0]) * 0.5, (auv[1] + buv[1]) * 0.5];
252 let idx = (new_v.len()) as u32;
253 new_v.push(mid_v);
254 new_n.push(mid_n);
255 new_uv.push(mid_uv);
256 cache.insert(key, idx);
257 idx
258 };
259
260 let verts: Vec<([f32; 3], [f32; 3], [f32; 2])> = mesh
262 .vertices
263 .iter()
264 .zip(mesh.normals.iter().zip(mesh.tex_coords.iter()))
265 .map(|(&v, (&n, &uv))| (v, n, uv))
266 .collect();
267
268 let mut extra_v: Vec<[f32; 3]> = Vec::new();
269 let mut extra_n: Vec<[f32; 3]> = Vec::new();
270 let mut extra_uv: Vec<[f32; 2]> = Vec::new();
271
272 let mut new_tris: Vec<[u32; 3]> = Vec::new();
273
274 for tri in &mesh.indices {
275 let [a, b, c] = [tri[0], tri[1], tri[2]];
276 let ab = get_midpoint(
277 a,
278 b,
279 &verts,
280 &mut edge_midpoint,
281 &mut extra_v,
282 &mut extra_n,
283 &mut extra_uv,
284 );
285 let bc = get_midpoint(
286 b,
287 c,
288 &verts,
289 &mut edge_midpoint,
290 &mut extra_v,
291 &mut extra_n,
292 &mut extra_uv,
293 );
294 let ca = get_midpoint(
295 c,
296 a,
297 &verts,
298 &mut edge_midpoint,
299 &mut extra_v,
300 &mut extra_n,
301 &mut extra_uv,
302 );
303 let base = mesh.vertices.len() as u32;
304 let ab = ab + base;
305 let bc = bc + base;
306 let ca = ca + base;
307 new_tris.push([a, ab, ca]);
308 new_tris.push([b, bc, ab]);
309 new_tris.push([c, ca, bc]);
310 new_tris.push([ab, bc, ca]);
311 }
312
313 for ((v, n), uv) in extra_v.iter().zip(extra_n.iter()).zip(extra_uv.iter()) {
314 new_mesh.add_vertex(*v, *n, *uv);
315 }
316 for tri in new_tris {
317 new_mesh.add_triangle(tri[0], tri[1], tri[2]);
318 }
319 new_mesh
320}
321
322pub fn gpu_mesh_decimate(mesh: &GpuMesh, target_triangles: usize) -> GpuMesh {
329 let mut result = mesh.clone();
330 while result.triangle_count() > target_triangles {
331 let mut best_len2 = f32::MAX;
333 let mut best_edge = (0u32, 0u32);
334 for tri in &result.indices {
335 let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[0], tri[2])];
336 for (i, j) in edges {
337 let a = result.vertices[i as usize];
338 let b = result.vertices[j as usize];
339 let d2 = dist2_3f(a, b);
340 if d2 < best_len2 {
341 best_len2 = d2;
342 best_edge = (i, j);
343 }
344 }
345 }
346 if best_len2 == f32::MAX {
347 break;
348 }
349 let (keep, remove) = best_edge;
350 let mid = midpoint3f(
352 result.vertices[keep as usize],
353 result.vertices[remove as usize],
354 );
355 result.vertices[keep as usize] = mid;
356 for tri in result.indices.iter_mut() {
358 for idx in tri.iter_mut() {
359 if *idx == remove {
360 *idx = keep;
361 }
362 }
363 }
364 result
365 .indices
366 .retain(|t| t[0] != t[1] && t[1] != t[2] && t[0] != t[2]);
367 }
368 result
369}
370
371pub fn gpu_compute_aabb(mesh: &GpuMesh) -> ([f32; 3], [f32; 3]) {
378 if mesh.vertices.is_empty() {
379 return ([0.0; 3], [0.0; 3]);
380 }
381 let mut mn = [f32::MAX; 3];
382 let mut mx = [f32::MIN; 3];
383 for v in &mesh.vertices {
384 for axis in 0..3 {
385 mn[axis] = mn[axis].min(v[axis]);
386 mx[axis] = mx[axis].max(v[axis]);
387 }
388 }
389 (mn, mx)
390}
391
392pub fn gpu_compute_surface_area(mesh: &GpuMesh) -> f32 {
394 mesh.indices
395 .iter()
396 .map(|t| {
397 let a = mesh.vertices[t[0] as usize];
398 let b = mesh.vertices[t[1] as usize];
399 let c = mesh.vertices[t[2] as usize];
400 triangle_area(a, b, c)
401 })
402 .sum()
403}
404
405pub fn gpu_compute_volume(mesh: &GpuMesh) -> f32 {
409 let mut vol = 0.0f32;
410 for t in &mesh.indices {
411 let a = mesh.vertices[t[0] as usize];
412 let b = mesh.vertices[t[1] as usize];
413 let c = mesh.vertices[t[2] as usize];
414 vol += (a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
416 + a[2] * (b[0] * c[1] - b[1] * c[0]))
417 / 6.0;
418 }
419 vol
420}
421
422pub fn gpu_weld_vertices(mesh: &mut GpuMesh, tol: f32) -> usize {
428 let nv = mesh.vertex_count();
429 let mut remap: Vec<usize> = (0..nv).collect();
430 let mut merged = 0usize;
431
432 for i in 0..nv {
433 if remap[i] != i {
434 continue;
435 }
436 for j in (i + 1)..nv {
437 if remap[j] != j {
438 continue;
439 }
440 if dist2_3f(mesh.vertices[i], mesh.vertices[j]).sqrt() < tol {
441 remap[j] = i;
442 merged += 1;
443 }
444 }
445 }
446
447 for tri in mesh.indices.iter_mut() {
448 for idx in tri.iter_mut() {
449 *idx = remap[*idx as usize] as u32;
450 }
451 }
452 mesh.indices
453 .retain(|t| t[0] != t[1] && t[1] != t[2] && t[0] != t[2]);
454 merged
455}
456
457fn cross3f(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
460 [
461 a[1] * b[2] - a[2] * b[1],
462 a[2] * b[0] - a[0] * b[2],
463 a[0] * b[1] - a[1] * b[0],
464 ]
465}
466
467fn length3f(v: [f32; 3]) -> f32 {
468 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
469}
470
471fn normalize3f(v: [f32; 3]) -> [f32; 3] {
472 let len = length3f(v);
473 if len < 1e-9 {
474 return [0.0; 3];
475 }
476 [v[0] / len, v[1] / len, v[2] / len]
477}
478
479fn dist2_3f(a: [f32; 3], b: [f32; 3]) -> f32 {
480 let d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
481 d[0] * d[0] + d[1] * d[1] + d[2] * d[2]
482}
483
484fn midpoint3f(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
485 [
486 (a[0] + b[0]) * 0.5,
487 (a[1] + b[1]) * 0.5,
488 (a[2] + b[2]) * 0.5,
489 ]
490}
491
492#[cfg(test)]
496mod tests {
497 use super::*;
498
499 fn unit_triangle() -> GpuMesh {
500 let mut m = GpuMesh::new();
501 m.add_vertex([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0]);
502 m.add_vertex([1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0]);
503 m.add_vertex([0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0]);
504 m.add_triangle(0, 1, 2);
505 m
506 }
507
508 fn tetrahedron() -> GpuMesh {
509 let mut m = GpuMesh::new();
510 m.add_vertex([1.0, 1.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0]);
511 m.add_vertex([-1.0, -1.0, 1.0], [0.0, 0.0, 1.0], [1.0, 0.0]);
512 m.add_vertex([-1.0, 1.0, -1.0], [0.0, 0.0, 1.0], [0.0, 1.0]);
513 m.add_vertex([1.0, -1.0, -1.0], [0.0, 0.0, 1.0], [1.0, 1.0]);
514 m.add_triangle(0, 1, 2);
515 m.add_triangle(0, 1, 3);
516 m.add_triangle(0, 2, 3);
517 m.add_triangle(1, 2, 3);
518 m
519 }
520
521 #[test]
524 fn test_add_vertex_count() {
525 let m = unit_triangle();
526 assert_eq!(m.vertex_count(), 3);
527 }
528
529 #[test]
530 fn test_add_triangle_count() {
531 let m = unit_triangle();
532 assert_eq!(m.triangle_count(), 1);
533 }
534
535 #[test]
536 fn test_empty_mesh_counts() {
537 let m = GpuMesh::new();
538 assert_eq!(m.vertex_count(), 0);
539 assert_eq!(m.triangle_count(), 0);
540 }
541
542 #[test]
545 fn test_triangle_area_unit_right_triangle() {
546 let area = triangle_area([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
547 assert!((area - 0.5).abs() < 1e-6);
548 }
549
550 #[test]
551 fn test_triangle_area_degenerate_is_zero() {
552 let area = triangle_area([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]);
553 assert!(area < 1e-6);
554 }
555
556 #[test]
557 fn test_triangle_area_equilateral() {
558 let area = triangle_area([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 3.0f32.sqrt(), 0.0]);
560 let expected = 3.0f32.sqrt();
561 assert!(
562 (area - expected).abs() < 1e-5,
563 "area={area} expected={expected}"
564 );
565 }
566
567 #[test]
570 fn test_triangle_normal_unit_length() {
571 let n = triangle_normal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
572 let len = length3f(n);
573 assert!((len - 1.0).abs() < 1e-6, "normal length={len}");
574 }
575
576 #[test]
577 fn test_triangle_normal_xy_plane_is_z() {
578 let n = triangle_normal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
579 assert!(n[2].abs() > 0.99);
580 }
581
582 #[test]
583 fn test_triangle_normal_degenerate_zero() {
584 let n = triangle_normal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]);
585 assert_eq!(n, [0.0; 3]);
586 }
587
588 #[test]
591 fn test_aabb_bounds_all_vertices() {
592 let m = unit_triangle();
593 let (mn, mx) = gpu_compute_aabb(&m);
594 assert!(mn[0] <= 0.0 && mn[1] <= 0.0);
595 assert!(mx[0] >= 1.0 && mx[1] >= 1.0);
596 }
597
598 #[test]
599 fn test_aabb_min_lt_max_nonempty() {
600 let m = unit_triangle();
601 let (mn, mx) = gpu_compute_aabb(&m);
602 let any_spread = (0..3).any(|a| mx[a] > mn[a]);
604 assert!(any_spread);
605 }
606
607 #[test]
608 fn test_aabb_empty_mesh() {
609 let m = GpuMesh::new();
610 let (mn, mx) = gpu_compute_aabb(&m);
611 for i in 0..3 {
612 assert_eq!(mn[i], mx[i]);
613 }
614 }
615
616 #[test]
619 fn test_surface_area_positive_for_triangle() {
620 let m = unit_triangle();
621 let area = gpu_compute_surface_area(&m);
622 assert!(area > 0.0);
623 }
624
625 #[test]
626 fn test_surface_area_unit_right_triangle() {
627 let m = unit_triangle();
628 let area = gpu_compute_surface_area(&m);
629 assert!((area - 0.5).abs() < 1e-6);
630 }
631
632 #[test]
633 fn test_surface_area_empty_mesh() {
634 let m = GpuMesh::new();
635 assert!((gpu_compute_surface_area(&m)).abs() < 1e-10);
636 }
637
638 #[test]
641 fn test_compute_normals_unit_length() {
642 let mut m = unit_triangle();
643 gpu_compute_normals(&mut m);
644 for n in &m.normals {
645 let len = length3f(*n);
646 assert!((len - 1.0).abs() < 1e-5 || len < 1e-9, "len={len}");
647 }
648 }
649
650 #[test]
651 fn test_compute_normals_xy_plane() {
652 let mut m = unit_triangle();
653 gpu_compute_normals(&mut m);
654 for n in &m.normals {
655 assert!(n[2].abs() > 0.9, "expected z-dominant normal, got {:?}", n);
656 }
657 }
658
659 #[test]
662 fn test_smooth_normals_preserves_unit_length() {
663 let mut m = unit_triangle();
664 gpu_compute_normals(&mut m);
665 gpu_smooth_normals(&mut m, 2);
666 for n in &m.normals {
667 let len = length3f(*n);
668 assert!((len - 1.0).abs() < 0.01 || len < 1e-9);
669 }
670 }
671
672 #[test]
673 fn test_smooth_normals_zero_iter_unchanged() {
674 let mut m = unit_triangle();
675 gpu_compute_normals(&mut m);
676 let before = m.normals.clone();
677 gpu_smooth_normals(&mut m, 0);
678 assert_eq!(m.normals, before);
679 }
680
681 #[test]
684 fn test_loop_subdivision_quadruples_triangles() {
685 let m = unit_triangle();
686 let sub = gpu_loop_subdivision(&m);
687 assert_eq!(sub.triangle_count(), m.triangle_count() * 4);
688 }
689
690 #[test]
691 fn test_loop_subdivision_increases_vertex_count() {
692 let m = unit_triangle();
693 let sub = gpu_loop_subdivision(&m);
694 assert!(sub.vertex_count() > m.vertex_count());
695 }
696
697 #[test]
698 fn test_loop_subdivision_tetrahedron() {
699 let m = tetrahedron();
700 let sub = gpu_loop_subdivision(&m);
701 assert_eq!(sub.triangle_count(), m.triangle_count() * 4);
702 }
703
704 #[test]
707 fn test_decimate_below_target() {
708 let sub = gpu_loop_subdivision(&gpu_loop_subdivision(&unit_triangle()));
709 let dec = gpu_mesh_decimate(&sub, 2);
710 assert!(dec.triangle_count() <= 2 || dec.triangle_count() <= sub.triangle_count());
711 }
712
713 #[test]
714 fn test_decimate_preserves_at_least_one_triangle() {
715 let m = tetrahedron();
716 let before = m.triangle_count();
719 let dec = gpu_mesh_decimate(&m, 1);
720 assert!(dec.triangle_count() <= before);
721 }
722
723 #[test]
726 fn test_edge_collapse_large_threshold_reduces_triangles() {
727 let mut m = tetrahedron();
728 let before = m.triangle_count();
729 let _removed = gpu_edge_collapse(&mut m, 100.0);
730 assert!(m.triangle_count() <= before);
731 }
732
733 #[test]
734 fn test_edge_collapse_zero_threshold_no_change() {
735 let mut m = unit_triangle();
736 let before = m.triangle_count();
737 let removed = gpu_edge_collapse(&mut m, 0.0);
738 assert_eq!(removed, 0);
739 assert_eq!(m.triangle_count(), before);
740 }
741
742 #[test]
745 fn test_weld_vertices_no_duplicates() {
746 let mut m = unit_triangle();
747 let merged = gpu_weld_vertices(&mut m, 0.01);
748 assert_eq!(merged, 0);
749 assert_eq!(m.vertex_count(), 3);
750 }
751
752 #[test]
753 fn test_weld_vertices_all_same() {
754 let mut m = GpuMesh::new();
755 for _ in 0..3 {
756 m.add_vertex([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0]);
757 }
758 m.add_triangle(0, 1, 2);
759 let merged = gpu_weld_vertices(&mut m, 0.1);
760 assert!(merged > 0);
761 }
762
763 #[test]
766 fn test_volume_tetrahedron_nonzero() {
767 let mut m = GpuMesh::new();
770 m.add_vertex([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0]);
771 m.add_vertex([1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0]);
772 m.add_vertex([0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0]);
773 m.add_vertex([0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0]);
774 m.add_triangle(0, 2, 1); m.add_triangle(0, 1, 3);
777 m.add_triangle(1, 2, 3);
778 m.add_triangle(0, 3, 2);
779 let vol = gpu_compute_volume(&m);
780 assert!(vol.abs() > 0.01, "vol={vol}");
782 }
783
784 #[test]
785 fn test_volume_empty_mesh_zero() {
786 let m = GpuMesh::new();
787 assert!((gpu_compute_volume(&m)).abs() < 1e-10);
788 }
789}