1#[cfg(test)]
6mod tests_decimation_extended {
7 use crate::decimation::DecimationMetrics;
8 use crate::decimation::EdgeFeature;
9 use crate::decimation::EdgePriorityQueue;
10 use crate::decimation::MeshEdge;
11 use crate::decimation::NormalCone;
12 use crate::decimation::ProgressiveMeshSimple;
13 use crate::decimation::*;
14
15 use crate::decimation::SimpleMesh;
16 fn small_mesh() -> SimpleMesh {
17 let mut m = SimpleMesh::new();
18 m.add_vertex([0.0, 0.0, 0.0]);
19 m.add_vertex([1.0, 0.0, 0.0]);
20 m.add_vertex([1.0, 1.0, 0.0]);
21 m.add_vertex([0.0, 1.0, 0.0]);
22 m.add_triangle(0, 1, 2);
23 m.add_triangle(0, 2, 3);
24 m
25 }
26 #[test]
27 fn test_vertex_clustering_large_cell_merges_all() {
28 let m = small_mesh();
29 let reduced = vertex_clustering_decimate(&m, 100.0);
30 assert_eq!(reduced.vertex_count(), 1);
31 assert_eq!(reduced.triangle_count(), 0);
32 }
33 #[test]
34 fn test_vertex_clustering_small_cell_preserves() {
35 let m = small_mesh();
36 let reduced = vertex_clustering_decimate(&m, 0.01);
37 assert_eq!(reduced.vertex_count(), m.vertex_count());
38 }
39 #[test]
40 fn test_vertex_clustering_empty_mesh() {
41 let m = SimpleMesh::new();
42 let reduced = vertex_clustering_decimate(&m, 1.0);
43 assert_eq!(reduced.vertex_count(), 0);
44 }
45 #[test]
46 fn test_vertex_clustering_mid_cell_reduces() {
47 let mut m = SimpleMesh::new();
48 m.add_vertex([0.0, 0.0, 0.0]);
49 m.add_vertex([0.01, 0.0, 0.0]);
50 m.add_vertex([1.0, 1.0, 0.0]);
51 m.add_triangle(0, 1, 2);
52 let reduced = vertex_clustering_decimate(&m, 0.1);
53 assert!(reduced.vertex_count() < m.vertex_count());
54 }
55 #[test]
56 fn test_progressive_mesh_initial() {
57 let m = small_mesh();
58 let pm = ProgressiveMeshSimple::new(m);
59 assert_eq!(pm.n_collapses(), 0);
60 assert_eq!(pm.n_triangles(), 2);
61 }
62 #[test]
63 fn test_progressive_mesh_collapse_edge() {
64 let m = small_mesh();
65 let mut pm = ProgressiveMeshSimple::new(m);
66 pm.collapse_edge(2, 1);
67 assert_eq!(pm.n_collapses(), 1);
68 assert!(pm.history[0].removed == 2);
69 }
70 #[test]
71 fn test_progressive_mesh_multiple_collapses() {
72 let m = small_mesh();
73 let mut pm = ProgressiveMeshSimple::new(m);
74 pm.collapse_edge(2, 1);
75 pm.collapse_edge(3, 0);
76 assert_eq!(pm.n_collapses(), 2);
77 }
78 #[test]
79 fn test_edge_priority_queue_empty() {
80 let q = EdgePriorityQueue::new();
81 assert!(q.is_empty());
82 assert_eq!(q.len(), 0);
83 }
84 #[test]
85 fn test_edge_priority_queue_push_pop() {
86 let mut q = EdgePriorityQueue::new();
87 q.push(MeshEdge {
88 v0: 0,
89 v1: 1,
90 cost: 2.0,
91 });
92 q.push(MeshEdge {
93 v0: 1,
94 v1: 2,
95 cost: 0.5,
96 });
97 q.push(MeshEdge {
98 v0: 0,
99 v1: 2,
100 cost: 1.0,
101 });
102 let e = q.pop().unwrap();
103 assert!((e.cost - 0.5).abs() < 1e-12, "cost={}", e.cost);
104 }
105 #[test]
106 fn test_edge_priority_queue_from_mesh() {
107 let m = small_mesh();
108 let q = EdgePriorityQueue::from_mesh(&m);
109 assert_eq!(q.len(), 5);
110 }
111 #[test]
112 fn test_edge_priority_queue_ordering() {
113 let mut q = EdgePriorityQueue::new();
114 for i in 0..10 {
115 q.push(MeshEdge {
116 v0: 0,
117 v1: i,
118 cost: (10 - i) as f64,
119 });
120 }
121 let mut last_cost = f64::NEG_INFINITY;
122 while let Some(e) = q.pop() {
123 assert!(e.cost >= last_cost);
124 last_cost = e.cost;
125 }
126 }
127 #[test]
128 fn test_normal_cone_from_normal() {
129 let nc = NormalCone::from_normal([0.0, 0.0, 1.0]);
130 assert!((nc.axis[2] - 1.0).abs() < 1e-10);
131 assert_eq!(nc.half_angle, 0.0);
132 }
133 #[test]
134 fn test_normal_cone_contains_same() {
135 let nc = NormalCone::from_normal([0.0, 0.0, 1.0]);
136 assert!(nc.contains([0.0, 0.0, 1.0], 1e-6));
137 }
138 #[test]
139 fn test_normal_cone_excludes_opposite() {
140 let nc = NormalCone::from_normal([0.0, 0.0, 1.0]);
141 assert!(!nc.contains([0.0, 0.0, -1.0], 0.01));
142 }
143 #[test]
144 fn test_normal_cone_merge() {
145 let nc1 = NormalCone::from_normal([1.0, 0.0, 0.0]);
146 let nc2 = NormalCone::from_normal([0.0, 1.0, 0.0]);
147 let merged = nc1.merge(&nc2);
148 assert!(merged.contains([1.0, 0.0, 0.0], 1e-6));
149 assert!(merged.contains([0.0, 1.0, 0.0], 1e-6));
150 }
151 #[test]
152 fn test_normal_cone_half_angle_deg() {
153 let nc = NormalCone {
154 axis: [0.0, 0.0, 1.0],
155 half_angle: std::f64::consts::PI / 4.0,
156 };
157 assert!((nc.half_angle_deg() - 45.0).abs() < 1e-10);
158 }
159 #[test]
160 fn test_classify_edge_smooth() {
161 let n0 = [0.0, 0.0, 1.0];
162 let n1 = [0.0, 0.0, 1.0];
163 let feat = classify_edge(n0, n1, 30.0);
164 assert_eq!(feat, EdgeFeature::Smooth);
165 }
166 #[test]
167 fn test_classify_edge_crease() {
168 let n0 = [0.0, 0.0, 1.0];
169 let n1 = [0.0, 1.0, 0.0];
170 let feat = classify_edge(n0, n1, 30.0);
171 assert_eq!(feat, EdgeFeature::Crease);
172 }
173 #[test]
174 fn test_feature_aware_cost_smooth() {
175 let cost = feature_aware_cost(1.0, EdgeFeature::Smooth, 100.0, 1000.0);
176 assert!((cost - 1.0).abs() < 1e-12);
177 }
178 #[test]
179 fn test_feature_aware_cost_crease() {
180 let cost = feature_aware_cost(1.0, EdgeFeature::Crease, 50.0, 1000.0);
181 assert!((cost - 50.0).abs() < 1e-12);
182 }
183 #[test]
184 fn test_feature_aware_cost_boundary() {
185 let cost = feature_aware_cost(2.0, EdgeFeature::Boundary, 50.0, 100.0);
186 assert!((cost - 200.0).abs() < 1e-12);
187 }
188 #[test]
189 fn test_decimation_stats_default() {
190 let s = DecimationMetrics::default();
191 assert_eq!(s.vertex_reduction_ratio(), 0.0);
192 assert_eq!(s.triangle_reduction_ratio(), 0.0);
193 assert_eq!(s.avg_qem_cost(), 0.0);
194 }
195 #[test]
196 fn test_decimation_stats_reduction() {
197 let s = DecimationMetrics {
198 original_vertices: 100,
199 original_triangles: 200,
200 reduced_vertices: 50,
201 reduced_triangles: 100,
202 n_collapses: 50,
203 total_qem_cost: 10.0,
204 max_qem_cost: 0.5,
205 };
206 assert!((s.vertex_reduction_ratio() - 0.5).abs() < 1e-10);
207 assert!((s.triangle_reduction_ratio() - 0.5).abs() < 1e-10);
208 assert!((s.avg_qem_cost() - 0.2).abs() < 1e-10);
209 }
210 #[test]
211 fn test_collect_decimation_metrics() {
212 let orig = small_mesh();
213 let mut pm = ProgressiveMeshSimple::new(orig.clone());
214 pm.collapse_edge(2, 1);
215 let stats = collect_decimation_metrics(&orig, &pm.current, pm.n_collapses(), 0.5, 0.5);
216 assert_eq!(stats.original_vertices, 4);
217 assert_eq!(stats.n_collapses, 1);
218 }
219 #[test]
220 fn test_mesh_edge_min_heap_ordering() {
221 use std::collections::BinaryHeap;
222 let mut heap = BinaryHeap::new();
223 heap.push(MeshEdge {
224 v0: 0,
225 v1: 1,
226 cost: 5.0,
227 });
228 heap.push(MeshEdge {
229 v0: 0,
230 v1: 2,
231 cost: 1.0,
232 });
233 heap.push(MeshEdge {
234 v0: 1,
235 v1: 2,
236 cost: 3.0,
237 });
238 let e = heap.pop().unwrap();
239 assert!(
240 (e.cost - 1.0).abs() < 1e-12,
241 "Expected min cost 1.0, got {}",
242 e.cost
243 );
244 }
245}
246#[cfg(test)]
247mod tests_qem_decimation {
248
249 use crate::decimation::QemDecimation;
250 use crate::decimation::SimpleMesh;
251 fn quad_mesh() -> SimpleMesh {
253 let mut m = SimpleMesh::new();
254 m.add_vertex([0.0, 0.0, 0.0]);
255 m.add_vertex([1.0, 0.0, 0.0]);
256 m.add_vertex([1.0, 1.0, 0.0]);
257 m.add_vertex([0.0, 1.0, 0.0]);
258 m.add_triangle(0, 1, 2);
259 m.add_triangle(0, 2, 3);
260 m
261 }
262 fn crease_mesh() -> SimpleMesh {
264 let mut m = SimpleMesh::new();
265 m.add_vertex([0.0, 0.0, 0.0]);
266 m.add_vertex([1.0, 0.0, 0.0]);
267 m.add_vertex([1.0, 1.0, 0.0]);
268 m.add_vertex([0.0, 1.0, 0.0]);
269 m.add_vertex([0.0, 0.0, 1.0]);
270 m.add_vertex([1.0, 0.0, 1.0]);
271 m.add_triangle(0, 1, 2);
272 m.add_triangle(0, 2, 3);
273 m.add_triangle(0, 1, 4);
274 m.add_triangle(1, 5, 4);
275 m
276 }
277 #[test]
278 fn test_error_threshold_positive() {
279 let qd = QemDecimation::new(quad_mesh());
280 let thr = qd.compute_error_threshold(1.0);
281 assert!(thr > 0.0, "threshold must be positive, got {thr}");
282 }
283 #[test]
284 fn test_error_threshold_scales_with_factor() {
285 let qd = QemDecimation::new(crease_mesh());
286 let thr1 = qd.compute_error_threshold(0.001);
287 let thr2 = qd.compute_error_threshold(100.0);
288 assert!(thr2 >= thr1, "larger factor → larger or equal threshold");
289 }
290 #[test]
291 fn test_error_threshold_empty_mesh() {
292 let qd = QemDecimation::new(SimpleMesh::new());
293 let thr = qd.compute_error_threshold(1.0);
294 assert_eq!(thr, 0.0, "empty mesh → zero threshold");
295 }
296 #[test]
297 fn test_error_threshold_finite() {
298 let qd = QemDecimation::new(quad_mesh());
299 let thr = qd.compute_error_threshold(0.5);
300 assert!(thr.is_finite(), "threshold must be finite, got {thr}");
301 }
302 #[test]
303 fn test_preserve_boundary_no_collapse_with_negative_threshold() {
304 let mut qd = QemDecimation::new(quad_mesh());
305 let before = qd.mesh.triangle_count();
306 let n = qd.preserve_boundary(-1.0);
307 assert_eq!(n, 0, "negative threshold should collapse nothing");
308 assert_eq!(qd.mesh.triangle_count(), before);
309 }
310 #[test]
311 fn test_preserve_boundary_collapses_with_large_threshold() {
312 let mut qd = QemDecimation::new(quad_mesh());
313 let n = qd.preserve_boundary(1e10);
314 let _ = n;
315 }
316 #[test]
317 fn test_preserve_boundary_result_is_valid_mesh() {
318 let mut qd = QemDecimation::new(quad_mesh());
319 qd.preserve_boundary(1e6);
320 for tri in &qd.mesh.triangles {
321 for &vi in tri {
322 assert!(vi < qd.mesh.vertices.len(), "invalid vertex index {vi}");
323 }
324 }
325 }
326 #[test]
327 fn test_feature_score_flat_mesh_near_zero() {
328 let qd = QemDecimation::new(quad_mesh());
329 let scores = qd.compute_feature_score();
330 assert!(!scores.is_empty(), "should have scores for all edges");
331 for (&(_a, _b), &s) in &scores {
332 if s.is_finite() {
333 assert!(s >= 0.0, "score must be non-negative, got {s}");
334 }
335 }
336 }
337 #[test]
338 fn test_feature_score_crease_mesh_high_score() {
339 let qd = QemDecimation::new(crease_mesh());
340 let scores = qd.compute_feature_score();
341 let max_score = scores
342 .values()
343 .filter(|v| v.is_finite())
344 .cloned()
345 .fold(0.0f64, f64::max);
346 assert!(
347 max_score > 0.5,
348 "crease mesh should have a high feature score, got {max_score}"
349 );
350 }
351 #[test]
352 fn test_feature_score_boundary_edges_infinite() {
353 let mut m = SimpleMesh::new();
354 m.add_vertex([0.0, 0.0, 0.0]);
355 m.add_vertex([1.0, 0.0, 0.0]);
356 m.add_vertex([0.5, 1.0, 0.0]);
357 m.add_triangle(0, 1, 2);
358 let qd = QemDecimation::new(m);
359 let scores = qd.compute_feature_score();
360 for (&_, &s) in &scores {
361 assert!(
362 s.is_infinite(),
363 "boundary edge should have infinite score, got {s}"
364 );
365 }
366 }
367}