1use std::collections::{HashMap, HashSet};
6
7use super::types::{DecimationMetrics, EdgeFeature, QuadricMatrix, SimpleMesh};
8
9pub(super) fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
10 [
11 a[1] * b[2] - a[2] * b[1],
12 a[2] * b[0] - a[0] * b[2],
13 a[0] * b[1] - a[1] * b[0],
14 ]
15}
16pub(super) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
17 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
18}
19pub(super) fn length(v: [f64; 3]) -> f64 {
20 dot(v, v).sqrt()
21}
22pub(super) fn normalize(v: [f64; 3]) -> [f64; 3] {
23 let len = length(v);
24 if len < 1e-300 {
25 [0.0, 0.0, 1.0]
26 } else {
27 [v[0] / len, v[1] / len, v[2] / len]
28 }
29}
30pub fn compute_vertex_quadrics(mesh: &SimpleMesh) -> Vec<QuadricMatrix> {
32 let n = mesh.vertex_count();
33 let mut quadrics: Vec<QuadricMatrix> = (0..n).map(|_| QuadricMatrix::zero()).collect();
34 for t_idx in 0..mesh.triangle_count() {
35 let [a, b, c] = mesh.triangles[t_idx];
36 let normal = mesh.triangle_normal(t_idx);
37 let va = mesh.vertices[a];
38 let d = -(normal[0] * va[0] + normal[1] * va[1] + normal[2] * va[2]);
39 let kp = QuadricMatrix::from_plane(normal[0], normal[1], normal[2], d);
40 quadrics[a] = quadrics[a].add(&kp);
41 quadrics[b] = quadrics[b].add(&kp);
42 quadrics[c] = quadrics[c].add(&kp);
43 }
44 quadrics
45}
46pub fn edge_collapse_cost(
52 v1: [f64; 3],
53 v2: [f64; 3],
54 q1: &QuadricMatrix,
55 q2: &QuadricMatrix,
56) -> (f64, [f64; 3]) {
57 let combined = q1.add(q2);
58 let mid = [
59 (v1[0] + v2[0]) * 0.5,
60 (v1[1] + v2[1]) * 0.5,
61 (v1[2] + v2[2]) * 0.5,
62 ];
63 let a00 = combined.q[0][0];
64 let a01 = combined.q[0][1];
65 let a02 = combined.q[0][2];
66 let a11 = combined.q[1][1];
67 let a12 = combined.q[1][2];
68 let a22 = combined.q[2][2];
69 let b0 = -combined.q[0][3];
70 let b1 = -combined.q[1][3];
71 let b2 = -combined.q[2][3];
72 let det = a00 * (a11 * a22 - a12 * a12) - a01 * (a01 * a22 - a12 * a02)
73 + a02 * (a01 * a12 - a11 * a02);
74 let opt = if det.abs() > 1e-10 {
75 let inv_det = 1.0 / det;
76 let x = inv_det
77 * (b0 * (a11 * a22 - a12 * a12)
78 + b1 * (a02 * a12 - a01 * a22)
79 + b2 * (a01 * a12 - a11 * a02));
80 let y = inv_det
81 * (b0 * (a12 * a02 - a01 * a22)
82 + b1 * (a00 * a22 - a02 * a02)
83 + b2 * (a01 * a02 - a00 * a12));
84 let z = inv_det
85 * (b0 * (a01 * a12 - a02 * a11)
86 + b1 * (a02 * a01 - a00 * a12)
87 + b2 * (a00 * a11 - a01 * a01));
88 [x, y, z]
89 } else {
90 mid
91 };
92 let error = combined.vertex_error(opt);
93 (error, opt)
94}
95pub fn cluster_id(v: [f64; 3], grid_size: f64) -> (i64, i64, i64) {
97 (
98 (v[0] / grid_size).floor() as i64,
99 (v[1] / grid_size).floor() as i64,
100 (v[2] / grid_size).floor() as i64,
101 )
102}
103pub fn vertex_clustering(mesh: &SimpleMesh, grid_size: f64) -> SimpleMesh {
107 let mut cell_to_cluster: HashMap<(i64, i64, i64), usize> = HashMap::new();
108 let mut cluster_sum: Vec<[f64; 3]> = Vec::new();
109 let mut cluster_count: Vec<f64> = Vec::new();
110 let mut vertex_to_cluster: Vec<usize> = Vec::with_capacity(mesh.vertex_count());
111 for &v in &mesh.vertices {
112 let cid = cluster_id(v, grid_size);
113 let idx = cell_to_cluster.entry(cid).or_insert_with(|| {
114 let new_idx = cluster_sum.len();
115 cluster_sum.push([0.0; 3]);
116 cluster_count.push(0.0);
117 new_idx
118 });
119 cluster_sum[*idx][0] += v[0];
120 cluster_sum[*idx][1] += v[1];
121 cluster_sum[*idx][2] += v[2];
122 cluster_count[*idx] += 1.0;
123 vertex_to_cluster.push(*idx);
124 }
125 let mut out = SimpleMesh::new();
126 for i in 0..cluster_sum.len() {
127 let cnt = cluster_count[i];
128 out.add_vertex([
129 cluster_sum[i][0] / cnt,
130 cluster_sum[i][1] / cnt,
131 cluster_sum[i][2] / cnt,
132 ]);
133 }
134 for &[a, b, c] in &mesh.triangles {
135 let ca = vertex_to_cluster[a];
136 let cb = vertex_to_cluster[b];
137 let cc = vertex_to_cluster[c];
138 if ca != cb && cb != cc && ca != cc {
139 out.add_triangle(ca, cb, cc);
140 }
141 }
142 out
143}
144pub fn collapse_edge(mesh: &mut SimpleMesh, v1: usize, v2: usize, new_pos: [f64; 3]) -> usize {
148 mesh.vertices[v1] = new_pos;
149 for tri in &mut mesh.triangles {
150 for idx in tri.iter_mut() {
151 if *idx == v2 {
152 *idx = v1;
153 }
154 }
155 }
156 mesh.triangles
157 .retain(|&[a, b, c]| a != b && b != c && a != c);
158 v1
159}
160pub fn find_boundary_edges(mesh: &SimpleMesh) -> Vec<(usize, usize)> {
162 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
163 for &[a, b, c] in &mesh.triangles {
164 let edges = [
165 (a.min(b), a.max(b)),
166 (b.min(c), b.max(c)),
167 (a.min(c), a.max(c)),
168 ];
169 for e in edges {
170 *edge_count.entry(e).or_insert(0) += 1;
171 }
172 }
173 edge_count
174 .into_iter()
175 .filter(|&(_, count)| count == 1)
176 .map(|(e, _)| e)
177 .collect()
178}
179pub fn is_manifold_after_collapse(mesh: &SimpleMesh, v1: usize, v2: usize) -> bool {
185 let mut ring1: HashSet<usize> = HashSet::new();
186 let mut ring2: HashSet<usize> = HashSet::new();
187 let mut shared: HashSet<usize> = HashSet::new();
188 for &[a, b, c] in &mesh.triangles {
189 let verts = [a, b, c];
190 let has_v1 = verts.contains(&v1);
191 let has_v2 = verts.contains(&v2);
192 if has_v1 {
193 for &v in &verts {
194 if v != v1 {
195 ring1.insert(v);
196 }
197 }
198 }
199 if has_v2 {
200 for &v in &verts {
201 if v != v2 {
202 ring2.insert(v);
203 }
204 }
205 }
206 if has_v1 && has_v2 {
207 for &v in &verts {
208 if v != v1 && v != v2 {
209 shared.insert(v);
210 }
211 }
212 }
213 }
214 ring1.remove(&v2);
215 ring2.remove(&v1);
216 let intersection: HashSet<usize> = ring1.intersection(&ring2).copied().collect();
217 intersection == shared
218}
219pub fn mesh_volume(mesh: &SimpleMesh) -> f64 {
222 let mut vol = 0.0;
223 for &[a, b, c] in &mesh.triangles {
224 let va = mesh.vertices[a];
225 let vb = mesh.vertices[b];
226 let vc = mesh.vertices[c];
227 vol += va[0] * (vb[1] * vc[2] - vc[1] * vb[2])
228 + vb[0] * (vc[1] * va[2] - va[1] * vc[2])
229 + vc[0] * (va[1] * vb[2] - vb[1] * va[2]);
230 }
231 vol / 6.0
232}
233pub fn mesh_centroid(mesh: &SimpleMesh) -> [f64; 3] {
235 let mut cx = 0.0;
236 let mut cy = 0.0;
237 let mut cz = 0.0;
238 let mut total_area = 0.0;
239 for t_idx in 0..mesh.triangle_count() {
240 let [a, b, c] = mesh.triangles[t_idx];
241 let va = mesh.vertices[a];
242 let vb = mesh.vertices[b];
243 let vc = mesh.vertices[c];
244 let area = mesh.triangle_area(t_idx);
245 let tcx = (va[0] + vb[0] + vc[0]) / 3.0;
246 let tcy = (va[1] + vb[1] + vc[1]) / 3.0;
247 let tcz = (va[2] + vb[2] + vc[2]) / 3.0;
248 cx += area * tcx;
249 cy += area * tcy;
250 cz += area * tcz;
251 total_area += area;
252 }
253 if total_area < 1e-300 {
254 return [0.0, 0.0, 0.0];
255 }
256 [cx / total_area, cy / total_area, cz / total_area]
257}
258pub fn classify_feature_vertices(mesh: &SimpleMesh, threshold_rad: f64) -> Vec<bool> {
264 let n = mesh.vertex_count();
265 let mut is_feature = vec![false; n];
266 let mut vtris: Vec<Vec<usize>> = vec![Vec::new(); n];
267 for (t_idx, &[a, b, c]) in mesh.triangles.iter().enumerate() {
268 vtris[a].push(t_idx);
269 vtris[b].push(t_idx);
270 vtris[c].push(t_idx);
271 }
272 for v in 0..n {
273 'outer: for &t1 in &vtris[v] {
274 for &t2 in &vtris[v] {
275 if t1 >= t2 {
276 continue;
277 }
278 let n1 = mesh.triangle_normal(t1);
279 let n2 = mesh.triangle_normal(t2);
280 let cos_ang = dot(n1, n2).clamp(-1.0, 1.0);
281 let angle = cos_ang.acos();
282 if angle > threshold_rad {
283 is_feature[v] = true;
284 break 'outer;
285 }
286 }
287 }
288 }
289 is_feature
290}
291pub fn simplify_with_features(mesh: &mut SimpleMesh, is_feature: &[bool], target_triangles: usize) {
297 while mesh.triangle_count() > target_triangles {
298 let quadrics = compute_vertex_quadrics(mesh);
299 let mut edge_set: std::collections::HashSet<(usize, usize)> =
300 std::collections::HashSet::new();
301 for &[a, b, c] in &mesh.triangles {
302 edge_set.insert((a.min(b), a.max(b)));
303 edge_set.insert((b.min(c), b.max(c)));
304 edge_set.insert((a.min(c), a.max(c)));
305 }
306 let mut best_cost = f64::MAX;
307 let mut best_v1 = usize::MAX;
308 let mut best_v2 = usize::MAX;
309 let mut best_pos = [0.0f64; 3];
310 for (v1, v2) in &edge_set {
311 let can_remove_v2 = !is_feature.get(*v2).copied().unwrap_or(false);
312 let can_remove_v1 = !is_feature.get(*v1).copied().unwrap_or(false);
313 if !can_remove_v2 && !can_remove_v1 {
314 continue;
315 }
316 let (v_kept, v_rem) = if can_remove_v2 {
317 (*v1, *v2)
318 } else {
319 (*v2, *v1)
320 };
321 let (cost, pos) = edge_collapse_cost(
322 mesh.vertices[v_kept],
323 mesh.vertices[v_rem],
324 &quadrics[v_kept],
325 &quadrics[v_rem],
326 );
327 if cost < best_cost {
328 best_cost = cost;
329 best_v1 = v_kept;
330 best_v2 = v_rem;
331 best_pos = pos;
332 }
333 }
334 if best_v1 == usize::MAX {
335 break;
336 }
337 collapse_edge(mesh, best_v1, best_v2, best_pos);
338 }
339}
340pub fn one_sided_hausdorff(mesh_a: &SimpleMesh, mesh_b: &SimpleMesh) -> f64 {
346 if mesh_a.vertex_count() == 0 || mesh_b.vertex_count() == 0 {
347 return 0.0;
348 }
349 let mut max_dist = 0.0f64;
350 for &va in &mesh_a.vertices {
351 let mut min_d = f64::MAX;
352 for &vb in &mesh_b.vertices {
353 let d = {
354 let dx = va[0] - vb[0];
355 let dy = va[1] - vb[1];
356 let dz = va[2] - vb[2];
357 (dx * dx + dy * dy + dz * dz).sqrt()
358 };
359 if d < min_d {
360 min_d = d;
361 }
362 }
363 if min_d > max_dist {
364 max_dist = min_d;
365 }
366 }
367 max_dist
368}
369pub fn average_edge_length(mesh: &SimpleMesh) -> f64 {
371 let mut total = 0.0;
372 let mut count = 0usize;
373 for &[a, b, c] in &mesh.triangles {
374 for (i, j) in [(a, b), (b, c), (a, c)] {
375 let vi = mesh.vertices[i];
376 let vj = mesh.vertices[j];
377 let d = length([vj[0] - vi[0], vj[1] - vi[1], vj[2] - vi[2]]);
378 total += d;
379 count += 1;
380 }
381 }
382 if count == 0 {
383 0.0
384 } else {
385 total / count as f64
386 }
387}
388pub fn mesh_compactness(mesh: &SimpleMesh) -> f64 {
392 let v = mesh_volume(mesh).abs();
393 let a = mesh.surface_area();
394 if a < 1e-300 {
395 return 0.0;
396 }
397 36.0 * std::f64::consts::PI * v * v / (a * a * a)
398}
399pub fn vertex_clustering_decimate(mesh: &SimpleMesh, cell_size: f64) -> SimpleMesh {
404 if mesh.vertices.is_empty() || cell_size <= 0.0 {
405 return SimpleMesh::new();
406 }
407 let mut mn = mesh.vertices[0];
408 let mut mx = mesh.vertices[0];
409 for v in &mesh.vertices {
410 for d in 0..3 {
411 if v[d] < mn[d] {
412 mn[d] = v[d];
413 }
414 if v[d] > mx[d] {
415 mx[d] = v[d];
416 }
417 }
418 }
419 let mut cell_to_new_idx: HashMap<[i64; 3], usize> = HashMap::new();
420 let mut vertex_remap: Vec<usize> = Vec::with_capacity(mesh.vertices.len());
421 let mut new_vertices: Vec<[f64; 3]> = Vec::new();
422 for v in &mesh.vertices {
423 let key = [
424 ((v[0] - mn[0]) / cell_size).floor() as i64,
425 ((v[1] - mn[1]) / cell_size).floor() as i64,
426 ((v[2] - mn[2]) / cell_size).floor() as i64,
427 ];
428 let idx = *cell_to_new_idx.entry(key).or_insert_with(|| {
429 let new_idx = new_vertices.len();
430 new_vertices.push(*v);
431 new_idx
432 });
433 vertex_remap.push(idx);
434 }
435 let mut new_tris: Vec<[usize; 3]> = Vec::new();
436 let mut seen: HashSet<[usize; 3]> = HashSet::new();
437 for tri in &mesh.triangles {
438 let a = vertex_remap[tri[0]];
439 let b = vertex_remap[tri[1]];
440 let c = vertex_remap[tri[2]];
441 if a == b || b == c || a == c {
442 continue;
443 }
444 let mut key = [a, b, c];
445 key.sort_unstable();
446 if seen.insert(key) {
447 new_tris.push([a, b, c]);
448 }
449 }
450 SimpleMesh {
451 vertices: new_vertices,
452 triangles: new_tris,
453 }
454}
455pub fn classify_edge(n0: [f64; 3], n1: [f64; 3], crease_angle_deg: f64) -> EdgeFeature {
460 let cos_angle = dot3(normalize3(n0), normalize3(n1)).clamp(-1.0, 1.0);
461 let angle_deg = cos_angle.acos().to_degrees();
462 if angle_deg > crease_angle_deg {
463 EdgeFeature::Crease
464 } else {
465 EdgeFeature::Smooth
466 }
467}
468pub fn feature_aware_cost(
475 base_cost: f64,
476 feature: EdgeFeature,
477 crease_weight: f64,
478 boundary_weight: f64,
479) -> f64 {
480 let w = match feature {
481 EdgeFeature::Smooth => 1.0,
482 EdgeFeature::Crease => crease_weight,
483 EdgeFeature::Boundary => boundary_weight,
484 };
485 base_cost * w
486}
487pub fn collect_decimation_metrics(
489 original: &SimpleMesh,
490 reduced: &SimpleMesh,
491 n_collapses: usize,
492 total_cost: f64,
493 max_cost: f64,
494) -> DecimationMetrics {
495 DecimationMetrics {
496 original_vertices: original.vertex_count(),
497 original_triangles: original.triangle_count(),
498 reduced_vertices: reduced.vertex_count(),
499 reduced_triangles: reduced.triangle_count(),
500 n_collapses,
501 total_qem_cost: total_cost,
502 max_qem_cost: max_cost,
503 }
504}
505#[inline]
506pub(super) fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
507 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
508}
509#[inline]
510pub(super) fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
511 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
512}
513#[inline]
514pub(super) fn normalize3(v: [f64; 3]) -> [f64; 3] {
515 let len = dot3(v, v).sqrt();
516 if len < 1e-15 {
517 return [0.0, 0.0, 1.0];
518 }
519 [v[0] / len, v[1] / len, v[2] / len]
520}
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::decimation::DecimationStats;
525 use crate::decimation::ProgressiveMesh;
526 fn unit_tetrahedron() -> SimpleMesh {
527 let mut m = SimpleMesh::new();
528 let v0 = m.add_vertex([0.0, 0.0, 0.0]);
529 let v1 = m.add_vertex([1.0, 0.0, 0.0]);
530 let v2 = m.add_vertex([0.0, 1.0, 0.0]);
531 let v3 = m.add_vertex([0.0, 0.0, 1.0]);
532 m.add_triangle(v0, v2, v1);
533 m.add_triangle(v0, v1, v3);
534 m.add_triangle(v1, v2, v3);
535 m.add_triangle(v0, v3, v2);
536 m
537 }
538 fn single_triangle() -> SimpleMesh {
539 let mut m = SimpleMesh::new();
540 let v0 = m.add_vertex([0.0, 0.0, 0.0]);
541 let v1 = m.add_vertex([1.0, 0.0, 0.0]);
542 let v2 = m.add_vertex([0.0, 1.0, 0.0]);
543 m.add_triangle(v0, v1, v2);
544 m
545 }
546 fn unit_cube() -> SimpleMesh {
547 let mut m = SimpleMesh::new();
548 let verts: [[f64; 3]; 8] = [
549 [0.0, 0.0, 0.0],
550 [1.0, 0.0, 0.0],
551 [1.0, 1.0, 0.0],
552 [0.0, 1.0, 0.0],
553 [0.0, 0.0, 1.0],
554 [1.0, 0.0, 1.0],
555 [1.0, 1.0, 1.0],
556 [0.0, 1.0, 1.0],
557 ];
558 for v in &verts {
559 m.add_vertex(*v);
560 }
561 m.add_triangle(0, 2, 1);
562 m.add_triangle(0, 3, 2);
563 m.add_triangle(4, 5, 6);
564 m.add_triangle(4, 6, 7);
565 m.add_triangle(0, 1, 5);
566 m.add_triangle(0, 5, 4);
567 m.add_triangle(2, 3, 7);
568 m.add_triangle(2, 7, 6);
569 m.add_triangle(0, 4, 7);
570 m.add_triangle(0, 7, 3);
571 m.add_triangle(1, 2, 6);
572 m.add_triangle(1, 6, 5);
573 m
574 }
575 #[test]
576 fn test_mesh_new_empty() {
577 let m = SimpleMesh::new();
578 assert_eq!(m.vertex_count(), 0);
579 assert_eq!(m.triangle_count(), 0);
580 }
581 #[test]
582 fn test_add_vertex_returns_index() {
583 let mut m = SimpleMesh::new();
584 let i0 = m.add_vertex([1.0, 2.0, 3.0]);
585 let i1 = m.add_vertex([4.0, 5.0, 6.0]);
586 assert_eq!(i0, 0);
587 assert_eq!(i1, 1);
588 assert_eq!(m.vertex_count(), 2);
589 }
590 #[test]
591 fn test_add_triangle_increments_count() {
592 let mut m = single_triangle();
593 assert_eq!(m.triangle_count(), 1);
594 m.add_triangle(0, 1, 2);
595 assert_eq!(m.triangle_count(), 2);
596 }
597 #[test]
598 fn test_triangle_area_unit_right_triangle() {
599 let m = single_triangle();
600 let area = m.triangle_area(0);
601 assert!((area - 0.5).abs() < 1e-12, "area={area}");
602 }
603 #[test]
604 fn test_surface_area_tetrahedron() {
605 let m = unit_tetrahedron();
606 let expected = 3.0 * 0.5 + 3.0_f64.sqrt() / 2.0;
607 let got = m.surface_area();
608 assert!(
609 (got - expected).abs() < 1e-10,
610 "got={got}, expected={expected}"
611 );
612 }
613 #[test]
614 fn test_triangle_normal_z_up() {
615 let m = single_triangle();
616 let n = m.triangle_normal(0);
617 assert!(n[2] > 0.9, "expected z-up normal, got {:?}", n);
618 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
619 assert!((len - 1.0).abs() < 1e-12, "normal not unit length");
620 }
621 #[test]
622 fn test_triangle_normal_unit_length() {
623 let m = unit_tetrahedron();
624 for i in 0..m.triangle_count() {
625 let n = m.triangle_normal(i);
626 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
627 assert!((len - 1.0).abs() < 1e-12, "face {i} normal not unit: {len}");
628 }
629 }
630 #[test]
631 fn test_quadric_zero() {
632 let q = QuadricMatrix::zero();
633 for i in 0..4 {
634 for j in 0..4 {
635 assert_eq!(q.q[i][j], 0.0);
636 }
637 }
638 }
639 #[test]
640 fn test_quadric_from_plane_vertex_error_zero_on_plane() {
641 let q = QuadricMatrix::from_plane(0.0, 0.0, 1.0, 0.0);
642 let err = q.vertex_error([3.0, -2.0, 0.0]);
643 assert!(err.abs() < 1e-12, "err={err}");
644 }
645 #[test]
646 fn test_quadric_from_plane_vertex_error_nonzero_off_plane() {
647 let q = QuadricMatrix::from_plane(0.0, 0.0, 1.0, 0.0);
648 let err = q.vertex_error([0.0, 0.0, 1.0]);
649 assert!((err - 1.0).abs() < 1e-12, "err={err}");
650 }
651 #[test]
652 fn test_quadric_add() {
653 let q1 = QuadricMatrix::from_plane(1.0, 0.0, 0.0, 0.0);
654 let q2 = QuadricMatrix::from_plane(0.0, 1.0, 0.0, 0.0);
655 let sum = q1.add(&q2);
656 let err = sum.vertex_error([1.0, 1.0, 0.0]);
657 assert!((err - 2.0).abs() < 1e-12, "err={err}");
658 }
659 #[test]
660 fn test_compute_vertex_quadrics_count() {
661 let m = unit_tetrahedron();
662 let qs = compute_vertex_quadrics(&m);
663 assert_eq!(qs.len(), m.vertex_count());
664 }
665 #[test]
666 fn test_compute_vertex_quadrics_nonzero() {
667 let m = single_triangle();
668 let qs = compute_vertex_quadrics(&m);
669 let total: f64 = qs[0].q.iter().flatten().map(|x| x.abs()).sum();
670 assert!(total > 0.0);
671 }
672 #[test]
673 fn test_edge_collapse_cost_midpoint_fallback() {
674 let q1 = QuadricMatrix::zero();
675 let q2 = QuadricMatrix::zero();
676 let v1 = [0.0, 0.0, 0.0];
677 let v2 = [2.0, 0.0, 0.0];
678 let (error, pos) = edge_collapse_cost(v1, v2, &q1, &q2);
679 assert!(error.abs() < 1e-12);
680 assert!((pos[0] - 1.0).abs() < 1e-12, "pos={:?}", pos);
681 }
682 #[test]
683 fn test_edge_collapse_cost_returns_finite() {
684 let m = unit_tetrahedron();
685 let qs = compute_vertex_quadrics(&m);
686 let (err, pos) = edge_collapse_cost(m.vertices[0], m.vertices[1], &qs[0], &qs[1]);
687 assert!(err.is_finite(), "error not finite");
688 assert!(pos.iter().all(|x| x.is_finite()), "pos not finite");
689 }
690 #[test]
691 fn test_cluster_id_basic() {
692 let id = cluster_id([0.5, 1.5, 2.5], 1.0);
693 assert_eq!(id, (0, 1, 2));
694 }
695 #[test]
696 fn test_cluster_id_negative() {
697 let id = cluster_id([-0.5, -1.5, -2.5], 1.0);
698 assert_eq!(id, (-1, -2, -3));
699 }
700 #[test]
701 fn test_vertex_clustering_reduces_vertices() {
702 let mut m = SimpleMesh::new();
703 let eps = 0.01;
704 let v0 = m.add_vertex([0.0, 0.0, 0.0]);
705 let v1 = m.add_vertex([1.0, 0.0, 0.0]);
706 let v2 = m.add_vertex([0.0, 1.0, 0.0]);
707 let v3 = m.add_vertex([0.0 + eps, 0.0, 0.0]);
708 let v4 = m.add_vertex([1.0 + eps, 0.0, 0.0]);
709 let v5 = m.add_vertex([0.0 + eps, 1.0, 0.0]);
710 m.add_triangle(v0, v1, v2);
711 m.add_triangle(v3, v4, v5);
712 let simplified = vertex_clustering(&m, 0.1);
713 assert!(simplified.vertex_count() < m.vertex_count());
714 }
715 #[test]
716 fn test_vertex_clustering_no_degenerate_triangles() {
717 let mut m = SimpleMesh::new();
718 let v0 = m.add_vertex([0.1, 0.1, 0.1]);
719 let v1 = m.add_vertex([0.2, 0.1, 0.1]);
720 let v2 = m.add_vertex([0.1, 0.2, 0.1]);
721 m.add_triangle(v0, v1, v2);
722 let simplified = vertex_clustering(&m, 1.0);
723 assert_eq!(simplified.triangle_count(), 0);
724 }
725 #[test]
726 fn test_collapse_edge_reduces_triangles() {
727 let mut m = unit_tetrahedron();
728 let before_tris = m.triangle_count();
729 collapse_edge(&mut m, 0, 1, [0.5, 0.0, 0.0]);
730 assert!(m.triangle_count() < before_tris);
731 }
732 #[test]
733 fn test_collapse_edge_no_degenerate_triangles_remain() {
734 let mut m = single_triangle();
735 collapse_edge(&mut m, 0, 1, [0.5, 0.0, 0.0]);
736 for &[a, b, c] in &m.triangles {
737 assert!(a != b && b != c && a != c, "degenerate triangle found");
738 }
739 }
740 #[test]
741 fn test_find_boundary_edges_single_triangle() {
742 let m = single_triangle();
743 let boundary = find_boundary_edges(&m);
744 assert_eq!(boundary.len(), 3);
745 }
746 #[test]
747 fn test_find_boundary_edges_closed_tetrahedron() {
748 let m = unit_tetrahedron();
749 let boundary = find_boundary_edges(&m);
750 assert_eq!(boundary.len(), 0);
751 }
752 #[test]
753 fn test_is_manifold_after_collapse_tetrahedron_edge() {
754 let m = unit_tetrahedron();
755 let result = is_manifold_after_collapse(&m, 0, 1);
756 assert!(result, "manifold check failed on tetrahedron edge");
757 }
758 #[test]
759 fn test_mesh_volume_unit_cube() {
760 let m = unit_cube();
761 let vol = mesh_volume(&m).abs();
762 assert!((vol - 1.0).abs() < 1e-10, "cube volume={vol}");
763 }
764 #[test]
765 fn test_mesh_volume_single_triangle_zero() {
766 let m = single_triangle();
767 let vol = mesh_volume(&m);
768 assert!(vol.abs() < 1e-12, "single triangle volume={vol}");
769 }
770 #[test]
771 fn test_mesh_centroid_single_triangle() {
772 let m = single_triangle();
773 let c = mesh_centroid(&m);
774 assert!((c[0] - 1.0 / 3.0).abs() < 1e-12, "cx={}", c[0]);
775 assert!((c[1] - 1.0 / 3.0).abs() < 1e-12, "cy={}", c[1]);
776 assert!(c[2].abs() < 1e-12, "cz={}", c[2]);
777 }
778 #[test]
779 fn test_mesh_centroid_unit_cube() {
780 let m = unit_cube();
781 let c = mesh_centroid(&m);
782 assert!((c[0] - 0.5).abs() < 1e-10, "cx={}", c[0]);
783 assert!((c[1] - 0.5).abs() < 1e-10, "cy={}", c[1]);
784 assert!((c[2] - 0.5).abs() < 1e-10, "cz={}", c[2]);
785 }
786 #[test]
787 fn test_progressive_mesh_collapse_one_reduces_triangles() {
788 let m = unit_tetrahedron();
789 let before = m.triangle_count();
790 let mut pm = ProgressiveMesh::new(m);
791 let ok = pm.collapse_one();
792 assert!(ok, "collapse_one should succeed on a tetrahedron");
793 assert!(
794 pm.mesh.triangle_count() < before,
795 "triangle count should decrease"
796 );
797 }
798 #[test]
799 fn test_progressive_mesh_decimate_to() {
800 let m = unit_cube();
801 let mut pm = ProgressiveMesh::new(m);
802 pm.decimate_to(4);
803 assert!(
804 pm.mesh.triangle_count() <= 4,
805 "got {}",
806 pm.mesh.triangle_count()
807 );
808 }
809 #[test]
810 fn test_progressive_mesh_history_recorded() {
811 let m = unit_tetrahedron();
812 let mut pm = ProgressiveMesh::new(m);
813 pm.collapse_one();
814 assert_eq!(pm.history.len(), 1);
815 let rec = &pm.history[0];
816 assert!(rec.old_pos.iter().all(|x| x.is_finite()));
817 assert!(rec.new_pos.iter().all(|x| x.is_finite()));
818 }
819 #[test]
820 fn test_progressive_mesh_stops_at_minimum() {
821 let m = single_triangle();
822 let mut pm = ProgressiveMesh::new(m);
823 let ok = pm.collapse_one();
824 assert!(!ok, "should not collapse a 1-triangle mesh");
825 }
826 #[test]
827 fn test_classify_feature_vertices_tetrahedron() {
828 let m = unit_tetrahedron();
829 let feat = classify_feature_vertices(&m, 0.01);
830 assert!(
831 feat.iter().any(|&f| f),
832 "at least one feature vertex expected"
833 );
834 }
835 #[test]
836 fn test_classify_feature_vertices_flat_mesh() {
837 let mut m = SimpleMesh::new();
838 let v0 = m.add_vertex([0.0, 0.0, 0.0]);
839 let v1 = m.add_vertex([1.0, 0.0, 0.0]);
840 let v2 = m.add_vertex([0.0, 1.0, 0.0]);
841 let v3 = m.add_vertex([1.0, 1.0, 0.0]);
842 m.add_triangle(v0, v1, v2);
843 m.add_triangle(v1, v3, v2);
844 let feat = classify_feature_vertices(&m, 0.1);
845 assert!(
846 feat.iter().all(|&f| !f),
847 "flat mesh should have no feature vertices"
848 );
849 }
850 #[test]
851 fn test_simplify_with_features_preserves_feature_vertex() {
852 let m = unit_cube();
853 let n = m.vertex_count();
854 let mut is_feature = vec![false; n];
855 is_feature[0] = true;
856 let mut mesh = m;
857 simplify_with_features(&mut mesh, &is_feature, 2);
858 let v0_used = mesh.triangles.iter().any(|t| t.contains(&0));
859 assert!(v0_used, "feature vertex 0 should survive decimation");
860 }
861 #[test]
862 fn test_simplify_with_features_reduces_triangles() {
863 let m = unit_cube();
864 let is_feature = vec![false; m.vertex_count()];
865 let target = 4;
866 let mut mesh = m;
867 simplify_with_features(&mut mesh, &is_feature, target);
868 assert!(mesh.triangle_count() <= target || mesh.triangle_count() < 12);
869 }
870 #[test]
871 fn test_decimation_stats_reduction_ratio() {
872 let m = unit_cube();
873 let orig_v = m.vertex_count();
874 let orig_t = m.triangle_count();
875 let mut pm = ProgressiveMesh::new(m);
876 pm.decimate_to(6);
877 let stats = DecimationStats::compute(orig_v, orig_t, &pm.mesh, 0.0);
878 assert!(stats.reduction_ratio >= 0.0 && stats.reduction_ratio <= 1.0);
879 assert_eq!(stats.original_triangles, orig_t);
880 assert_eq!(stats.result_triangles, pm.mesh.triangle_count());
881 }
882 #[test]
883 fn test_decimation_stats_no_change() {
884 let m = single_triangle();
885 let stats = DecimationStats::compute(m.vertex_count(), m.triangle_count(), &m, 0.0);
886 assert!((stats.reduction_ratio - 0.0).abs() < 1e-12);
887 }
888 #[test]
889 fn test_one_sided_hausdorff_same_mesh_zero() {
890 let m = single_triangle();
891 let d = one_sided_hausdorff(&m, &m);
892 assert!(d < 1e-12, "same mesh: d={d}");
893 }
894 #[test]
895 fn test_one_sided_hausdorff_offset_mesh() {
896 let mut m1 = SimpleMesh::new();
897 m1.add_vertex([0.0, 0.0, 0.0]);
898 let mut m2 = SimpleMesh::new();
899 m2.add_vertex([3.0, 0.0, 0.0]);
900 let d = one_sided_hausdorff(&m1, &m2);
901 assert!((d - 3.0).abs() < 1e-12, "d={d}");
902 }
903 #[test]
904 fn test_one_sided_hausdorff_empty_returns_zero() {
905 let m = single_triangle();
906 let empty = SimpleMesh::new();
907 let d = one_sided_hausdorff(&m, &empty);
908 assert_eq!(d, 0.0);
909 }
910 #[test]
911 fn test_average_edge_length_unit_right_triangle() {
912 let m = single_triangle();
913 let avg = average_edge_length(&m);
914 let expected = (2.0 + 2.0_f64.sqrt()) / 3.0;
915 assert!((avg - expected).abs() < 1e-10, "avg={avg}");
916 }
917 #[test]
918 fn test_average_edge_length_empty_mesh_zero() {
919 let m = SimpleMesh::new();
920 assert_eq!(average_edge_length(&m), 0.0);
921 }
922 #[test]
923 fn test_mesh_compactness_single_triangle_zero() {
924 let m = single_triangle();
925 let c = mesh_compactness(&m);
926 assert!(c >= 0.0);
927 }
928 #[test]
929 fn test_mesh_compactness_cube_positive() {
930 let m = unit_cube();
931 let c = mesh_compactness(&m);
932 assert!(c > 0.0 && c < 1.0, "compactness={c}");
933 }
934}