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