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