use crate::aabb::{aabb_contains, bounds_of_points, overlap_bounds, signed_gap, Aabb};
use crate::triangle::{tri_tri_distance, tri_tri_intersect};
use crate::tri_mesh::TriMesh;
use crate::vec3::{centroid, mid, Vec3};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ClashStatus {
Hard = 0,
Clearance = 1,
Touch = 2,
}
pub struct NarrowResult {
pub status: ClashStatus,
pub distance: f64,
pub point: Vec3,
pub bounds: Aabb,
}
#[allow(clippy::too_many_arguments)]
pub fn test_pair(
aabb_a: &Aabb,
tri_a: &TriMesh,
aabb_b: &Aabb,
tri_b: &TriMesh,
mode: u8,
tolerance: f64,
clearance: f64,
report_touch: bool,
) -> Option<NarrowResult> {
let is_clearance = mode == 1;
let margin = tolerance.max(if is_clearance { clearance } else { 0.0 });
let a_smaller = tri_a.count <= tri_b.count;
let (small, large) = if a_smaller {
(tri_a, tri_b)
} else {
(tri_b, tri_a)
};
let mut intersects = false;
let mut contact_sum: [f64; 3] = [0.0, 0.0, 0.0];
let mut contact_n: u32 = 0;
let mut min_dist = f64::INFINITY;
let mut closest_a: Vec3 = aabb_a.min;
let mut closest_b: Vec3 = aabb_b.min;
for ts in 0..small.count {
let sb = small.tri_bounds(ts);
let hits = large.query_tris(&sb.inflate(margin));
if hits.is_empty() {
continue;
}
let [s0, s1, s2] = small.tri(ts);
for tl in hits {
let [l0, l1, l2] = large.tri(tl as usize);
if tri_tri_intersect(s0, s1, s2, l0, l1, l2) {
intersects = true;
let c = mid(centroid(s0, s1, s2), centroid(l0, l1, l2));
contact_sum[0] += c[0];
contact_sum[1] += c[1];
contact_sum[2] += c[2];
contact_n += 1;
} else if !intersects {
let (dist, p_a, p_b) = tri_tri_distance(s0, s1, s2, l0, l1, l2);
if dist < min_dist {
min_dist = dist;
closest_a = p_a;
closest_b = p_b;
}
}
}
}
let overlap = overlap_bounds(aabb_a, aabb_b);
if intersects {
let point: Vec3 = if contact_n > 0 {
let n = contact_n as f64;
[contact_sum[0] / n, contact_sum[1] / n, contact_sum[2] / n]
} else {
overlap.center()
};
let penetration = (-signed_gap(aabb_a, aabb_b)).max(0.0);
return Some(NarrowResult {
status: ClashStatus::Hard,
distance: -penetration,
point,
bounds: overlap,
});
}
let enclosed = if aabb_contains(aabb_b, aabb_a) {
tri_a.count > 0 && tri_b.contains_point(tri_a.tri(0)[0])
} else if aabb_contains(aabb_a, aabb_b) {
tri_b.count > 0 && tri_a.contains_point(tri_b.tri(0)[0])
} else {
false
};
if enclosed {
return Some(NarrowResult {
status: ClashStatus::Hard,
distance: signed_gap(aabb_a, aabb_b),
point: overlap.center(),
bounds: overlap,
});
}
if min_dist == f64::INFINITY {
return None;
}
if min_dist <= tolerance {
let gap = signed_gap(aabb_a, aabb_b);
if gap < -tolerance {
return Some(NarrowResult {
status: ClashStatus::Hard,
distance: gap,
point: overlap.center(),
bounds: overlap,
});
}
}
if is_clearance && min_dist <= clearance {
return Some(NarrowResult {
status: ClashStatus::Clearance,
distance: min_dist,
point: mid(closest_a, closest_b),
bounds: bounds_of_points(closest_a, closest_b),
});
}
if min_dist <= tolerance {
if !report_touch {
return None;
}
return Some(NarrowResult {
status: ClashStatus::Touch,
distance: min_dist,
point: mid(closest_a, closest_b),
bounds: bounds_of_points(closest_a, closest_b),
});
}
None
}