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