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