1use nalgebra::{Point3, Vector3};
7use rayon::prelude::*;
8use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
9use tracing::{debug, info, warn};
10
11use crate::types::{Mesh, Triangle};
12
13#[derive(Debug, Clone)]
15pub struct SelfIntersectionResult {
16 pub has_intersections: bool,
18 pub intersection_count: usize,
20 pub intersecting_pairs: Vec<(u32, u32)>,
23 pub faces_checked: usize,
25 pub truncated: bool,
27}
28
29impl SelfIntersectionResult {
30 pub fn is_clean(&self) -> bool {
32 !self.has_intersections
33 }
34}
35
36impl std::fmt::Display for SelfIntersectionResult {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 if self.has_intersections {
39 write!(
40 f,
41 "Self-intersections found: {} pair(s){}",
42 self.intersection_count,
43 if self.truncated { " (truncated)" } else { "" }
44 )
45 } else {
46 write!(f, "No self-intersections detected")
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct IntersectionParams {
54 pub max_reported: usize,
57 pub epsilon: f64,
59 pub skip_adjacent: bool,
62 pub use_gpu: bool,
67}
68
69impl Default for IntersectionParams {
70 fn default() -> Self {
71 Self {
72 max_reported: 100,
73 epsilon: 1e-10,
74 skip_adjacent: true,
75 use_gpu: false,
76 }
77 }
78}
79
80impl IntersectionParams {
81 pub fn with_gpu(mut self) -> Self {
86 self.use_gpu = true;
87 self
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
93struct Aabb {
94 min: Point3<f64>,
95 max: Point3<f64>,
96}
97
98impl Aabb {
99 fn from_triangle(tri: &Triangle) -> Self {
101 let min = Point3::new(
102 tri.v0.x.min(tri.v1.x).min(tri.v2.x),
103 tri.v0.y.min(tri.v1.y).min(tri.v2.y),
104 tri.v0.z.min(tri.v1.z).min(tri.v2.z),
105 );
106 let max = Point3::new(
107 tri.v0.x.max(tri.v1.x).max(tri.v2.x),
108 tri.v0.y.max(tri.v1.y).max(tri.v2.y),
109 tri.v0.z.max(tri.v1.z).max(tri.v2.z),
110 );
111 Self { min, max }
112 }
113
114 fn expand(&self, epsilon: f64) -> Self {
116 Self {
117 min: Point3::new(
118 self.min.x - epsilon,
119 self.min.y - epsilon,
120 self.min.z - epsilon,
121 ),
122 max: Point3::new(
123 self.max.x + epsilon,
124 self.max.y + epsilon,
125 self.max.z + epsilon,
126 ),
127 }
128 }
129
130 fn overlaps(&self, other: &Aabb) -> bool {
132 self.min.x <= other.max.x
133 && self.max.x >= other.min.x
134 && self.min.y <= other.max.y
135 && self.max.y >= other.min.y
136 && self.min.z <= other.max.z
137 && self.max.z >= other.min.z
138 }
139}
140
141pub fn detect_self_intersections(
167 mesh: &Mesh,
168 params: &IntersectionParams,
169) -> SelfIntersectionResult {
170 let face_count = mesh.faces.len();
171
172 if face_count < 2 {
173 return SelfIntersectionResult {
174 has_intersections: false,
175 intersection_count: 0,
176 intersecting_pairs: Vec::new(),
177 faces_checked: face_count,
178 truncated: false,
179 };
180 }
181
182 info!("Checking {} faces for self-intersections", face_count);
183
184 let triangles: Vec<Triangle> = mesh.triangles().collect();
186 let aabbs: Vec<Aabb> = triangles
187 .iter()
188 .map(|t| Aabb::from_triangle(t).expand(params.epsilon))
189 .collect();
190
191 let adjacency = if params.skip_adjacent {
193 Some(build_face_adjacency(&mesh.faces))
194 } else {
195 None
196 };
197
198 let max_pairs = if params.max_reported == 0 {
199 usize::MAX
200 } else {
201 params.max_reported
202 };
203
204 let intersection_count = AtomicUsize::new(0);
206 let should_stop = AtomicBool::new(false);
207
208 let intersecting_pairs: Vec<(u32, u32)> = (0..face_count)
211 .into_par_iter()
212 .flat_map(|i| {
213 if should_stop.load(Ordering::Relaxed) {
215 return Vec::new();
216 }
217
218 let mut local_pairs = Vec::new();
219
220 for j in (i + 1)..face_count {
221 if should_stop.load(Ordering::Relaxed) {
223 break;
224 }
225
226 if !aabbs[i].overlaps(&aabbs[j]) {
228 continue;
229 }
230
231 if let Some(ref adj) = adjacency
233 && adj[i].contains(&(j as u32))
234 {
235 continue;
236 }
237
238 if triangles_intersect(&triangles[i], &triangles[j], params.epsilon) {
240 let count = intersection_count.fetch_add(1, Ordering::Relaxed);
241
242 if count < max_pairs {
243 local_pairs.push((i as u32, j as u32));
244 }
245
246 if count + 1 >= max_pairs && params.max_reported > 0 {
247 should_stop.store(true, Ordering::Relaxed);
248 break;
249 }
250 }
251 }
252
253 local_pairs
254 })
255 .collect();
256
257 let final_count = intersection_count.load(Ordering::Relaxed);
258 let truncated = params.max_reported > 0 && final_count >= max_pairs;
259
260 if truncated {
261 debug!(
262 "Stopping intersection search after {} pairs (max_reported limit)",
263 max_pairs
264 );
265 }
266
267 if final_count > 0 {
268 warn!("Found {} self-intersecting triangle pair(s)", final_count);
269 } else {
270 info!("No self-intersections found");
271 }
272
273 SelfIntersectionResult {
274 has_intersections: final_count > 0,
275 intersection_count: final_count,
276 intersecting_pairs,
277 faces_checked: face_count,
278 truncated,
279 }
280}
281
282fn build_face_adjacency(faces: &[[u32; 3]]) -> Vec<hashbrown::HashSet<u32>> {
284 use hashbrown::{HashMap, HashSet};
285
286 let mut vertex_to_faces: HashMap<u32, Vec<u32>> = HashMap::new();
288 for (face_idx, face) in faces.iter().enumerate() {
289 for &v in face {
290 vertex_to_faces.entry(v).or_default().push(face_idx as u32);
291 }
292 }
293
294 let mut adjacency: Vec<HashSet<u32>> = vec![HashSet::new(); faces.len()];
296 for (face_idx, face) in faces.iter().enumerate() {
297 for &v in face {
298 if let Some(neighbors) = vertex_to_faces.get(&v) {
299 for &neighbor in neighbors {
300 if neighbor != face_idx as u32 {
301 adjacency[face_idx].insert(neighbor);
302 }
303 }
304 }
305 }
306 }
307
308 adjacency
309}
310
311fn triangles_intersect(t1: &Triangle, t2: &Triangle, epsilon: f64) -> bool {
316 let n1 = t1.normal_unnormalized();
318 let n2 = t2.normal_unnormalized();
319
320 if n1.norm_squared() < epsilon * epsilon || n2.norm_squared() < epsilon * epsilon {
322 return false;
323 }
324
325 let edges1 = [t1.v1 - t1.v0, t1.v2 - t1.v1, t1.v0 - t1.v2];
327 let edges2 = [t2.v1 - t2.v0, t2.v2 - t2.v1, t2.v0 - t2.v2];
328
329 let cross_normals = n1.cross(&n2);
331 let is_coplanar =
332 cross_normals.norm_squared() < epsilon * epsilon * n1.norm_squared() * n2.norm_squared();
333
334 if is_coplanar {
335 for edge in &edges1 {
340 let axis = n1.cross(edge);
341 if axis.norm_squared() > epsilon * epsilon && separated_by_axis(&axis, t1, t2, epsilon)
342 {
343 return false;
344 }
345 }
346
347 for edge in &edges2 {
349 let axis = n2.cross(edge);
350 if axis.norm_squared() > epsilon * epsilon && separated_by_axis(&axis, t1, t2, epsilon)
351 {
352 return false;
353 }
354 }
355
356 return true;
358 }
359
360 if separated_by_axis(&n1, t1, t2, epsilon) {
364 return false;
365 }
366 if separated_by_axis(&n2, t1, t2, epsilon) {
367 return false;
368 }
369
370 for e1 in &edges1 {
372 for e2 in &edges2 {
373 let axis = e1.cross(e2);
374 if axis.norm_squared() > epsilon * epsilon && separated_by_axis(&axis, t1, t2, epsilon)
375 {
376 return false;
377 }
378 }
379 }
380
381 true
383}
384
385fn separated_by_axis(axis: &Vector3<f64>, t1: &Triangle, t2: &Triangle, epsilon: f64) -> bool {
387 let p1_0 = axis.dot(&t1.v0.coords);
389 let p1_1 = axis.dot(&t1.v1.coords);
390 let p1_2 = axis.dot(&t1.v2.coords);
391 let min1 = p1_0.min(p1_1).min(p1_2);
392 let max1 = p1_0.max(p1_1).max(p1_2);
393
394 let p2_0 = axis.dot(&t2.v0.coords);
396 let p2_1 = axis.dot(&t2.v1.coords);
397 let p2_2 = axis.dot(&t2.v2.coords);
398 let min2 = p2_0.min(p2_1).min(p2_2);
399 let max2 = p2_0.max(p2_1).max(p2_2);
400
401 max1 + epsilon < min2 || max2 + epsilon < min1
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::Vertex;
409
410 fn create_xy_triangle(x: f64, y: f64, size: f64) -> Triangle {
411 Triangle::new(
412 Point3::new(x, y, 0.0),
413 Point3::new(x + size, y, 0.0),
414 Point3::new(x + size / 2.0, y + size, 0.0),
415 )
416 }
417
418 #[test]
419 fn test_aabb_overlap() {
420 let aabb1 = Aabb {
421 min: Point3::new(0.0, 0.0, 0.0),
422 max: Point3::new(1.0, 1.0, 1.0),
423 };
424 let aabb2 = Aabb {
425 min: Point3::new(0.5, 0.5, 0.5),
426 max: Point3::new(1.5, 1.5, 1.5),
427 };
428 let aabb3 = Aabb {
429 min: Point3::new(2.0, 2.0, 2.0),
430 max: Point3::new(3.0, 3.0, 3.0),
431 };
432
433 assert!(aabb1.overlaps(&aabb2));
434 assert!(aabb2.overlaps(&aabb1));
435 assert!(!aabb1.overlaps(&aabb3));
436 assert!(!aabb3.overlaps(&aabb1));
437 }
438
439 #[test]
440 fn test_non_intersecting_triangles() {
441 let t1 = create_xy_triangle(0.0, 0.0, 1.0);
443 let t2 = create_xy_triangle(10.0, 10.0, 1.0);
444
445 assert!(!triangles_intersect(&t1, &t2, 1e-10));
446 }
447
448 #[test]
449 fn test_coplanar_non_intersecting() {
450 let t1 = create_xy_triangle(0.0, 0.0, 1.0);
452 let t2 = create_xy_triangle(2.0, 0.0, 1.0);
453
454 assert!(!triangles_intersect(&t1, &t2, 1e-10));
455 }
456
457 #[test]
458 fn test_coplanar_intersecting() {
459 let t1 = create_xy_triangle(0.0, 0.0, 2.0);
461 let t2 = create_xy_triangle(0.5, 0.5, 2.0);
462
463 assert!(triangles_intersect(&t1, &t2, 1e-10));
464 }
465
466 #[test]
467 fn test_perpendicular_intersecting() {
468 let t1 = Triangle::new(
470 Point3::new(-1.0, -1.0, 0.0),
471 Point3::new(1.0, -1.0, 0.0),
472 Point3::new(0.0, 1.0, 0.0),
473 );
474 let t2 = Triangle::new(
476 Point3::new(-1.0, 0.0, -1.0),
477 Point3::new(1.0, 0.0, -1.0),
478 Point3::new(0.0, 0.0, 1.0),
479 );
480
481 assert!(triangles_intersect(&t1, &t2, 1e-10));
482 }
483
484 #[test]
485 fn test_perpendicular_non_intersecting() {
486 let t1 = Triangle::new(
488 Point3::new(-1.0, -1.0, 0.0),
489 Point3::new(1.0, -1.0, 0.0),
490 Point3::new(0.0, 1.0, 0.0),
491 );
492 let t2 = Triangle::new(
494 Point3::new(-1.0, 5.0, -1.0),
495 Point3::new(1.0, 5.0, -1.0),
496 Point3::new(0.0, 5.0, 1.0),
497 );
498
499 assert!(!triangles_intersect(&t1, &t2, 1e-10));
500 }
501
502 #[test]
503 fn test_detect_clean_mesh() {
504 let mut mesh = Mesh::new();
506 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
507 mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
508 mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
509 mesh.vertices.push(Vertex::from_coords(0.5, 0.5, 1.0));
510 mesh.faces.push([0, 1, 2]);
511 mesh.faces.push([0, 1, 3]);
512 mesh.faces.push([1, 2, 3]);
513 mesh.faces.push([2, 0, 3]);
514
515 let result = detect_self_intersections(&mesh, &IntersectionParams::default());
516 assert!(result.is_clean());
517 assert_eq!(result.intersection_count, 0);
518 }
519
520 #[test]
521 fn test_detect_self_intersecting_mesh() {
522 let mut mesh = Mesh::new();
524
525 mesh.vertices.push(Vertex::from_coords(-1.0, -1.0, 0.0));
527 mesh.vertices.push(Vertex::from_coords(1.0, -1.0, 0.0));
528 mesh.vertices.push(Vertex::from_coords(0.0, 1.0, 0.0));
529
530 mesh.vertices.push(Vertex::from_coords(-1.0, 0.0, -1.0));
532 mesh.vertices.push(Vertex::from_coords(1.0, 0.0, -1.0));
533 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 1.0));
534
535 mesh.faces.push([0, 1, 2]);
536 mesh.faces.push([3, 4, 5]);
537
538 let result = detect_self_intersections(&mesh, &IntersectionParams::default());
539 assert!(!result.is_clean());
540 assert_eq!(result.intersection_count, 1);
541 assert_eq!(result.intersecting_pairs.len(), 1);
542 assert_eq!(result.intersecting_pairs[0], (0, 1));
543 }
544
545 #[test]
546 fn test_skip_adjacent_triangles() {
547 let mut mesh = Mesh::new();
549 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
550 mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
551 mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
552 mesh.vertices.push(Vertex::from_coords(0.5, -1.0, 0.0));
553 mesh.faces.push([0, 1, 2]);
554 mesh.faces.push([0, 3, 1]); let params = IntersectionParams {
557 skip_adjacent: true,
558 ..Default::default()
559 };
560 let result = detect_self_intersections(&mesh, ¶ms);
561 assert!(result.is_clean());
562 }
563
564 #[test]
565 fn test_empty_mesh() {
566 let mesh = Mesh::new();
567 let result = detect_self_intersections(&mesh, &IntersectionParams::default());
568 assert!(result.is_clean());
569 assert_eq!(result.faces_checked, 0);
570 }
571
572 #[test]
573 fn test_single_triangle() {
574 let mut mesh = Mesh::new();
575 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
576 mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
577 mesh.vertices.push(Vertex::from_coords(0.0, 1.0, 0.0));
578 mesh.faces.push([0, 1, 2]);
579
580 let result = detect_self_intersections(&mesh, &IntersectionParams::default());
581 assert!(result.is_clean());
582 assert_eq!(result.faces_checked, 1);
583 }
584
585 #[test]
586 fn test_result_display() {
587 let result = SelfIntersectionResult {
588 has_intersections: true,
589 intersection_count: 5,
590 intersecting_pairs: vec![(0, 1), (2, 3)],
591 faces_checked: 100,
592 truncated: false,
593 };
594 let output = format!("{}", result);
595 assert!(output.contains("5 pair(s)"));
596
597 let clean_result = SelfIntersectionResult {
598 has_intersections: false,
599 intersection_count: 0,
600 intersecting_pairs: Vec::new(),
601 faces_checked: 100,
602 truncated: false,
603 };
604 let clean_output = format!("{}", clean_result);
605 assert!(clean_output.contains("No self-intersections"));
606 }
607
608 #[test]
609 fn test_max_reported_limit() {
610 let mut mesh = Mesh::new();
612
613 for i in 0..5 {
615 let offset = i as f64 * 0.1;
616 mesh.vertices
618 .push(Vertex::from_coords(-1.0 + offset, -1.0, 0.0));
619 mesh.vertices
620 .push(Vertex::from_coords(1.0 + offset, -1.0, 0.0));
621 mesh.vertices
622 .push(Vertex::from_coords(0.0 + offset, 1.0, 0.0));
623 mesh.vertices
625 .push(Vertex::from_coords(-1.0 + offset, 0.0, -1.0));
626 mesh.vertices
627 .push(Vertex::from_coords(1.0 + offset, 0.0, -1.0));
628 mesh.vertices
629 .push(Vertex::from_coords(0.0 + offset, 0.0, 1.0));
630
631 let base = (i * 6) as u32;
632 mesh.faces.push([base, base + 1, base + 2]);
633 mesh.faces.push([base + 3, base + 4, base + 5]);
634 }
635
636 let params = IntersectionParams {
637 max_reported: 2,
638 ..Default::default()
639 };
640 let result = detect_self_intersections(&mesh, ¶ms);
641 assert!(!result.is_clean());
642 assert!(result.intersecting_pairs.len() <= 2);
643 }
644}