1#![allow(clippy::needless_range_loop)]
6#[allow(unused_imports)]
7use super::functions::*;
8use super::functions::{Face, Vertex};
9use super::functions::{vec3_add, vec3_normalize, vec3_scale};
10use super::types::{AnisotropicSizeField, BooleanResult, ProcessMesh};
11
12pub fn mesh_intersection(a: &ProcessMesh, b: &ProcessMesh) -> BooleanResult {
16 let offset_b = a.verts.len();
17 let mut verts = a.verts.clone();
18 verts.extend_from_slice(&b.verts);
19 let faces_a: Vec<Face> = a
20 .faces
21 .iter()
22 .filter(|&&[i, j, k]| {
23 let centroid = vec3_scale(
24 vec3_add(vec3_add(a.verts[i], a.verts[j]), a.verts[k]),
25 1.0 / 3.0,
26 );
27 point_in_mesh(centroid, b)
28 })
29 .cloned()
30 .collect();
31 let faces_b: Vec<Face> = b
32 .faces
33 .iter()
34 .filter(|&&[i, j, k]| {
35 let centroid = vec3_scale(
36 vec3_add(vec3_add(b.verts[i], b.verts[j]), b.verts[k]),
37 1.0 / 3.0,
38 );
39 point_in_mesh(centroid, a)
40 })
41 .map(|&[i, j, k]| [i + offset_b, j + offset_b, k + offset_b])
42 .collect();
43 let mut faces = faces_a;
44 faces.extend(faces_b);
45 BooleanResult {
46 mesh: ProcessMesh::new(verts, faces),
47 is_exact: false,
48 }
49}
50pub fn mesh_difference(a: &ProcessMesh, b: &ProcessMesh) -> BooleanResult {
54 let offset_b = a.verts.len();
55 let mut verts = a.verts.clone();
56 verts.extend_from_slice(&b.verts);
57 let faces_a: Vec<Face> = a
58 .faces
59 .iter()
60 .filter(|&&[i, j, k]| {
61 let centroid = vec3_scale(
62 vec3_add(vec3_add(a.verts[i], a.verts[j]), a.verts[k]),
63 1.0 / 3.0,
64 );
65 !point_in_mesh(centroid, b)
66 })
67 .cloned()
68 .collect();
69 let faces_b: Vec<Face> = b
70 .faces
71 .iter()
72 .filter(|&&[i, j, k]| {
73 let centroid = vec3_scale(
74 vec3_add(vec3_add(b.verts[i], b.verts[j]), b.verts[k]),
75 1.0 / 3.0,
76 );
77 point_in_mesh(centroid, a)
78 })
79 .map(|&[i, j, k]| [j + offset_b, i + offset_b, k + offset_b])
80 .collect();
81 let mut faces = faces_a;
82 faces.extend(faces_b);
83 BooleanResult {
84 mesh: ProcessMesh::new(verts, faces),
85 is_exact: false,
86 }
87}
88pub fn make_tetrahedron() -> ProcessMesh {
90 let verts = vec![
91 [0.0, 0.0, 0.0],
92 [1.0, 0.0, 0.0],
93 [0.5, (3.0_f64).sqrt() / 2.0, 0.0],
94 [0.5, (3.0_f64).sqrt() / 6.0, (6.0_f64).sqrt() / 3.0],
95 ];
96 let faces = vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]];
97 ProcessMesh::new(verts, faces)
98}
99pub fn make_quad() -> ProcessMesh {
101 let verts = vec![
102 [0.0, 0.0, 0.0],
103 [1.0, 0.0, 0.0],
104 [1.0, 1.0, 0.0],
105 [0.0, 1.0, 0.0],
106 ];
107 let faces = vec![[0, 1, 2], [0, 2, 3]];
108 ProcessMesh::new(verts, faces)
109}
110pub fn make_icosphere(subdivisions: usize) -> ProcessMesh {
112 let phi = (1.0 + 5.0_f64.sqrt()) / 2.0;
113 let verts: Vec<Vertex> = vec![
114 vec3_normalize([-1.0, phi, 0.0]),
115 vec3_normalize([1.0, phi, 0.0]),
116 vec3_normalize([-1.0, -phi, 0.0]),
117 vec3_normalize([1.0, -phi, 0.0]),
118 vec3_normalize([0.0, -1.0, phi]),
119 vec3_normalize([0.0, 1.0, phi]),
120 vec3_normalize([0.0, -1.0, -phi]),
121 vec3_normalize([0.0, 1.0, -phi]),
122 vec3_normalize([phi, 0.0, -1.0]),
123 vec3_normalize([phi, 0.0, 1.0]),
124 vec3_normalize([-phi, 0.0, -1.0]),
125 vec3_normalize([-phi, 0.0, 1.0]),
126 ];
127 let faces: Vec<Face> = vec![
128 [0, 11, 5],
129 [0, 5, 1],
130 [0, 1, 7],
131 [0, 7, 10],
132 [0, 10, 11],
133 [1, 5, 9],
134 [5, 11, 4],
135 [11, 10, 2],
136 [10, 7, 6],
137 [7, 1, 8],
138 [3, 9, 4],
139 [3, 4, 2],
140 [3, 2, 6],
141 [3, 6, 8],
142 [3, 8, 9],
143 [4, 9, 5],
144 [2, 4, 11],
145 [6, 2, 10],
146 [8, 6, 7],
147 [9, 8, 1],
148 ];
149 let mut mesh = ProcessMesh::new(verts, faces);
150 for _ in 0..subdivisions {
151 mesh = midpoint_subdivide(&mesh);
152 for v in &mut mesh.verts {
153 *v = vec3_normalize(*v);
154 }
155 }
156 mesh
157}
158pub fn uniform_anisotropic_field(mesh: &ProcessMesh, len: f64) -> AnisotropicSizeField {
160 let nv = mesh.verts.len();
161 AnisotropicSizeField {
162 k_min_dir: vec![[1.0, 0.0, 0.0]; nv],
163 k_max_dir: vec![[0.0, 1.0, 0.0]; nv],
164 len_min: vec![len; nv],
165 len_max: vec![len; nv],
166 }
167}
168pub fn anisotropic_remesh(
173 mesh: &ProcessMesh,
174 field: &AnisotropicSizeField,
175 iters: usize,
176) -> ProcessMesh {
177 let mut out = mesh.clone();
178 for _ in 0..iters {
179 let avg_max: f64 =
180 field.len_max.iter().copied().sum::<f64>() / field.len_max.len().max(1) as f64;
181 let avg_min: f64 =
182 field.len_min.iter().copied().sum::<f64>() / field.len_min.len().max(1) as f64;
183 out = split_long_edges(&out, avg_max);
184 out = collapse_short_edges(&out, avg_min * 0.6);
185 out = laplacian_smooth(&out, 0.3, 1);
186 }
187 out
188}
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::mesh_processing::Quadric;
193 fn tet() -> ProcessMesh {
194 make_tetrahedron()
195 }
196 fn quad() -> ProcessMesh {
197 make_quad()
198 }
199 fn ico1() -> ProcessMesh {
200 make_icosphere(1)
201 }
202 #[test]
203 fn test_mesh_num_verts() {
204 let m = tet();
205 assert_eq!(m.num_verts(), 4);
206 }
207 #[test]
208 fn test_mesh_num_faces() {
209 let m = tet();
210 assert_eq!(m.num_faces(), 4);
211 }
212 #[test]
213 fn test_mesh_compute_normals() {
214 let mut m = tet();
215 m.compute_normals();
216 let normals = m.normals.as_ref().unwrap();
217 assert_eq!(normals.len(), 4);
218 for &n in normals {
219 let l = vec3_len(n);
220 assert!(l < 1.01, "normal too long: {l}");
221 }
222 }
223 #[test]
224 fn test_build_adjacency_tetrahedron() {
225 let m = tet();
226 let adj = m.build_adjacency();
227 assert_eq!(adj.len(), 4);
228 for i in 0..4 {
229 assert_eq!(adj[i].len(), 3, "vertex {i} should have 3 neighbours");
230 }
231 }
232 #[test]
233 fn test_laplacian_smooth_zero_iter() {
234 let m = tet();
235 let s = laplacian_smooth(&m, 0.5, 0);
236 for i in 0..4 {
237 assert_eq!(s.verts[i], m.verts[i]);
238 }
239 }
240 #[test]
241 fn test_laplacian_smooth_moves_vertices() {
242 let m = ico1();
243 let s = laplacian_smooth(&m, 0.5, 3);
244 let changed = m
245 .verts
246 .iter()
247 .zip(s.verts.iter())
248 .any(|(a, b)| vec3_len(vec3_sub(*a, *b)) > 1e-12);
249 assert!(changed);
250 }
251 #[test]
252 fn test_taubin_smooth_preserves_face_count() {
253 let m = ico1();
254 let s = taubin_smooth(&m, 0.5, -0.53, 3);
255 assert_eq!(s.num_faces(), m.num_faces());
256 }
257 #[test]
258 fn test_taubin_lambda_mu_signs() {
259 let m = ico1();
260 let _s = taubin_smooth(&m, 0.5, -0.53, 5);
261 }
262 #[test]
263 fn test_midpoint_subdivide_face_count() {
264 let m = tet();
265 let s = midpoint_subdivide(&m);
266 assert_eq!(s.num_faces(), m.num_faces() * 4);
267 }
268 #[test]
269 fn test_midpoint_subdivide_vertex_count_increases() {
270 let m = tet();
271 let s = midpoint_subdivide(&m);
272 assert!(s.num_verts() > m.num_verts());
273 }
274 #[test]
275 fn test_loop_subdivide_face_count() {
276 let m = tet();
277 let s = loop_subdivide(&m);
278 assert_eq!(s.num_faces(), m.num_faces() * 4);
279 }
280 #[test]
281 fn test_catmull_clark_subdivide_more_faces() {
282 let m = tet();
283 let s = catmull_clark_subdivide(&m);
284 assert!(s.num_faces() > m.num_faces());
285 }
286 #[test]
287 fn test_double_midpoint_subdivide() {
288 let m = tet();
289 let s1 = midpoint_subdivide(&m);
290 let s2 = midpoint_subdivide(&s1);
291 assert_eq!(s2.num_faces(), m.num_faces() * 16);
292 }
293 #[test]
294 fn test_quadric_zero() {
295 let q = Quadric::zero();
296 assert_eq!(q.evaluate([1.0, 2.0, 3.0]), 0.0);
297 }
298 #[test]
299 fn test_quadric_plane() {
300 let q = Quadric::from_plane(0.0, 0.0, 1.0, 0.0);
301 assert!(q.evaluate([1.0, 2.0, 0.0]).abs() < 1e-12);
302 assert!(q.evaluate([1.0, 2.0, 1.0]) > 0.0);
303 }
304 #[test]
305 fn test_qem_decimate_reduces_faces() {
306 let m = make_icosphere(2);
307 let target = 200;
308 let dec = qem_decimate(&m, target);
309 assert!(
310 dec.num_faces() < m.num_faces(),
311 "decimation should reduce faces; got={}",
312 dec.num_faces()
313 );
314 assert!(dec.num_faces() <= target + 5, "faces={}", dec.num_faces());
315 }
316 #[test]
317 fn test_qem_decimate_no_op_when_under_target() {
318 let m = tet();
319 let dec = qem_decimate(&m, 100);
320 assert_eq!(dec.num_faces(), m.num_faces());
321 }
322 #[test]
323 fn test_triangle_quality_equilateral() {
324 let va = [0.0, 0.0, 0.0];
325 let vb = [1.0, 0.0, 0.0];
326 let vc = [0.5, (3.0_f64).sqrt() / 2.0, 0.0];
327 let q = triangle_quality(va, vb, vc);
328 assert!((q.aspect_ratio - 1.0).abs() < 0.01, "ar={}", q.aspect_ratio);
329 assert!((q.area_quality - 1.0).abs() < 0.01, "aq={}", q.area_quality);
330 }
331 #[test]
332 fn test_triangle_quality_right_angle() {
333 let va = [0.0, 0.0, 0.0];
334 let vb = [1.0, 0.0, 0.0];
335 let vc = [0.0, 1.0, 0.0];
336 let q = triangle_quality(va, vb, vc);
337 assert!(q.min_angle > 0.0);
338 assert!(q.max_angle < std::f64::consts::PI);
339 }
340 #[test]
341 fn test_mesh_quality_stats_tetrahedron() {
342 let m = tet();
343 let stats = mesh_quality_stats(&m);
344 assert!(
345 stats.mean_aspect_ratio >= 1.0 - 1e-9,
346 "mean_aspect_ratio={}",
347 stats.mean_aspect_ratio
348 );
349 assert!(stats.min_angle_deg > 0.0);
350 }
351 #[test]
352 fn test_mesh_quality_empty_mesh() {
353 let m = ProcessMesh::new(vec![], vec![]);
354 let stats = mesh_quality_stats(&m);
355 assert_eq!(stats.mean_aspect_ratio, 0.0);
356 }
357 #[test]
358 fn test_detect_boundary_loops_closed_mesh() {
359 let m = tet();
360 let loops = detect_boundary_loops(&m);
361 assert!(loops.is_empty(), "tetrahedron should have no boundary");
362 }
363 #[test]
364 fn test_detect_boundary_loops_open_mesh() {
365 let m = quad();
366 let loops = detect_boundary_loops(&m);
367 assert!(!loops.is_empty());
368 }
369 #[test]
370 fn test_fill_hole_fan_single_triangle() {
371 let m = quad();
372 let loops = detect_boundary_loops(&m);
373 let mut verts = m.verts.clone();
374 let mut total_faces = m.faces.clone();
375 for lp in &loops {
376 let nf = fill_hole_fan(&mut verts, lp);
377 total_faces.extend(nf);
378 }
379 assert!(total_faces.len() > m.num_faces());
380 }
381 #[test]
382 fn test_fill_all_holes_closes_boundary() {
383 let m = quad();
384 let repaired = fill_all_holes(&m);
385 let loops = detect_boundary_loops(&repaired);
386 assert!(loops.is_empty(), "boundary should be closed after repair");
387 }
388 #[test]
389 fn test_feature_edges_tetrahedron_sharp() {
390 let m = tet();
391 let edges = detect_feature_edges(&m, 0.0);
392 assert!(!edges.is_empty());
393 }
394 #[test]
395 fn test_feature_edges_high_threshold() {
396 let m = tet();
397 let edges = detect_feature_edges(&m, 180.0);
398 assert!(edges.is_empty());
399 }
400 #[test]
401 fn test_split_long_edges_increases_verts() {
402 let m = make_icosphere(0);
403 let split = split_long_edges(&m, 0.5);
404 assert!(split.num_verts() >= m.num_verts());
405 }
406 #[test]
407 fn test_collapse_short_edges_reduces_verts() {
408 let m = make_icosphere(2);
409 let collapsed = collapse_short_edges(&m, 0.5);
410 assert!(collapsed.num_verts() <= m.num_verts());
411 }
412 #[test]
413 fn test_isotropic_remesh_smoke() {
414 let m = make_icosphere(1);
415 let rm = isotropic_remesh(&m, 0.4, 2);
416 assert!(rm.num_verts() > 0);
417 assert!(rm.num_faces() > 0);
418 }
419 #[test]
420 fn test_tutte_parameterize_uv_count() {
421 let m = make_icosphere(1);
422 let param = tutte_parameterize(&m, 50);
423 assert_eq!(param.uvs.len(), m.num_verts());
424 }
425 #[test]
426 fn test_tutte_parameterize_uv_range() {
427 let m = make_icosphere(1);
428 let param = tutte_parameterize(&m, 50);
429 for &[u, v] in ¶m.uvs {
430 assert!(u.is_finite(), "u not finite");
431 assert!(v.is_finite(), "v not finite");
432 }
433 }
434 #[test]
435 fn test_lscm_parameterize_returns_uvs() {
436 let m = make_icosphere(1);
437 let param = lscm_parameterize(&m);
438 assert_eq!(param.uvs.len(), m.num_verts());
439 }
440 #[test]
441 fn test_generate_texture_atlas_nonempty() {
442 let m = make_icosphere(1);
443 let atlas = generate_texture_atlas(&m, 512);
444 assert!(!atlas.is_empty());
445 }
446 #[test]
447 fn test_generate_texture_atlas_bounds_valid() {
448 let m = make_icosphere(1);
449 let atlas = generate_texture_atlas(&m, 512);
450 for patch in &atlas {
451 let [umin, vmin, umax, vmax] = patch.bounds;
452 assert!(umin <= umax, "umin > umax");
453 assert!(vmin <= vmax, "vmin > vmax");
454 }
455 }
456 #[test]
457 fn test_point_in_mesh_inside() {
458 let m = make_icosphere(2);
459 assert!(point_in_mesh([0.0, 0.0, 0.0], &m));
460 }
461 #[test]
462 fn test_point_in_mesh_outside() {
463 let m = make_icosphere(2);
464 assert!(!point_in_mesh([10.0, 0.0, 0.0], &m));
465 }
466 #[test]
467 fn test_mesh_union_has_faces() {
468 let a = make_icosphere(1);
469 let mut b = make_icosphere(1);
470 for v in &mut b.verts {
471 v[0] += 0.5;
472 }
473 let result = mesh_union(&a, &b);
474 assert!(result.mesh.num_faces() > 0);
475 }
476 #[test]
477 fn test_mesh_intersection_smoke() {
478 let a = make_icosphere(1);
479 let mut b = make_icosphere(1);
480 for v in &mut b.verts {
481 v[0] += 0.5;
482 }
483 let result = mesh_intersection(&a, &b);
484 let _ = result.mesh.num_faces();
485 }
486 #[test]
487 fn test_mesh_difference_smoke() {
488 let a = make_icosphere(1);
489 let b = make_icosphere(1);
490 let result = mesh_difference(&a, &b);
491 let _ = result.mesh.num_faces();
492 }
493 #[test]
494 fn test_anisotropic_remesh_smoke() {
495 let m = make_icosphere(1);
496 let field = uniform_anisotropic_field(&m, 0.5);
497 let rm = anisotropic_remesh(&m, &field, 2);
498 assert!(rm.num_verts() > 0);
499 }
500 #[test]
501 fn test_uniform_anisotropic_field_dimensions() {
502 let m = tet();
503 let field = uniform_anisotropic_field(&m, 1.0);
504 assert_eq!(field.len_min.len(), m.num_verts());
505 assert_eq!(field.len_max.len(), m.num_verts());
506 }
507 #[test]
508 fn test_make_icosphere_vertices_on_unit_sphere() {
509 let m = make_icosphere(0);
510 for v in &m.verts {
511 let r = vec3_len(*v);
512 assert!((r - 1.0).abs() < 1e-10, "radius={r}");
513 }
514 }
515 #[test]
516 fn test_make_icosphere_subdivision_grows_mesh() {
517 let m0 = make_icosphere(0);
518 let m1 = make_icosphere(1);
519 assert!(m1.num_faces() > m0.num_faces());
520 }
521 #[test]
522 fn test_vec3_cross_perpendicular() {
523 let a = [1.0, 0.0, 0.0];
524 let b = [0.0, 1.0, 0.0];
525 let c = vec3_cross(a, b);
526 assert!((c[0]).abs() < 1e-12);
527 assert!((c[1]).abs() < 1e-12);
528 assert!((c[2] - 1.0).abs() < 1e-12);
529 }
530 #[test]
531 fn test_vec3_normalize_unit() {
532 let v = [3.0, 4.0, 0.0];
533 let n = vec3_normalize(v);
534 assert!((vec3_len(n) - 1.0).abs() < 1e-12);
535 }
536 #[test]
537 fn test_vec3_lerp_midpoint() {
538 let a = [0.0, 0.0, 0.0];
539 let b = [2.0, 2.0, 2.0];
540 let m = vec3_lerp(a, b, 0.5);
541 assert!((m[0] - 1.0).abs() < 1e-12);
542 }
543}