use axiolid_core::Point3;
use axiolid_mesh::TriangleMeshView;
use crate::proximity::{closest_points_on_triangles, ClosestPoints3, ProximityError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MeshProximityError {
EmptyMesh,
IndexOutOfRange,
InvalidThreshold,
Primitive(ProximityError),
}
impl From<ProximityError> for MeshProximityError {
fn from(error: ProximityError) -> Self {
Self::Primitive(error)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MeshDistance {
pub point_a: Point3,
pub point_b: Point3,
pub distance_squared: f64,
pub triangle_a: usize,
pub triangle_b: usize,
pub surfaces_cross: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProximityComponent {
pub witness: MeshDistance,
pub triangles_a: Vec<usize>,
pub triangles_b: Vec<usize>,
}
pub fn mesh_distance<A, B>(first: &A, second: &B) -> Result<MeshDistance, MeshProximityError>
where
A: TriangleMeshView + ?Sized,
B: TriangleMeshView + ?Sized,
{
let (triangles_a, _) = collect(first)?;
let (triangles_b, _) = collect(second)?;
let mut best: Option<MeshDistance> = None;
for (index_a, tri_a) in triangles_a.iter().enumerate() {
for (index_b, tri_b) in triangles_b.iter().enumerate() {
let pair = closest_points_on_triangles(*tri_a, *tri_b)?;
let candidate = to_distance(pair, index_a, index_b);
if best.is_none_or(|current| candidate.distance_squared < current.distance_squared) {
best = Some(candidate);
}
}
}
best.ok_or(MeshProximityError::EmptyMesh)
}
fn to_distance(pair: ClosestPoints3, triangle_a: usize, triangle_b: usize) -> MeshDistance {
MeshDistance {
point_a: pair.point_a,
point_b: pair.point_b,
distance_squared: pair.distance_squared,
triangle_a,
triangle_b,
surfaces_cross: pair.distance_squared == 0.0,
}
}
type Collected = (Vec<[Point3; 3]>, Vec<[usize; 3]>);
fn collect<M: TriangleMeshView + ?Sized>(mesh: &M) -> Result<Collected, MeshProximityError> {
let positions = mesh.position_count();
let mut triangles = Vec::with_capacity(mesh.triangle_count());
let mut corner_indices = Vec::with_capacity(mesh.triangle_count());
for index in 0..mesh.triangle_count() {
let corners = mesh.triangle(index);
let mut points = [Point3::ZERO; 3];
let mut corner_ids = [0usize; 3];
for (slot, corner) in corners.iter().enumerate() {
let corner =
usize::try_from(*corner).map_err(|_| MeshProximityError::IndexOutOfRange)?;
if corner >= positions {
return Err(MeshProximityError::IndexOutOfRange);
}
points[slot] = mesh.position(corner);
corner_ids[slot] = corner;
}
triangles.push(points);
corner_indices.push(corner_ids);
}
if triangles.is_empty() {
return Err(MeshProximityError::EmptyMesh);
}
Ok((triangles, corner_indices))
}
pub fn proximity_components<A, B>(
first: &A,
second: &B,
threshold: f64,
) -> Result<Vec<ProximityComponent>, MeshProximityError>
where
A: TriangleMeshView + ?Sized,
B: TriangleMeshView + ?Sized,
{
if !threshold.is_finite() || threshold < 0.0 {
return Err(MeshProximityError::InvalidThreshold);
}
let (triangles_a, corners_a) = collect(first)?;
let (triangles_b, corners_b) = collect(second)?;
let limit = threshold * threshold;
let mut close = Vec::new();
for (index_a, tri_a) in triangles_a.iter().enumerate() {
for (index_b, tri_b) in triangles_b.iter().enumerate() {
let pair = closest_points_on_triangles(*tri_a, *tri_b)?;
if pair.distance_squared <= limit {
close.push(to_distance(pair, index_a, index_b));
}
}
}
if close.is_empty() {
return Ok(Vec::new());
}
let mut parent: Vec<usize> = (0..close.len()).collect();
for i in 0..close.len() {
for j in i + 1..close.len() {
let linked_a = adjacent(&corners_a, close[i].triangle_a, close[j].triangle_a);
let linked_b = adjacent(&corners_b, close[i].triangle_b, close[j].triangle_b);
if linked_a && linked_b {
union(&mut parent, i, j);
}
}
}
let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
for index in 0..close.len() {
let root = find(&mut parent, index);
match groups.iter_mut().find(|(key, _)| *key == root) {
Some((_, members)) => members.push(index),
None => groups.push((root, vec![index])),
}
}
let mut components: Vec<ProximityComponent> = groups
.into_iter()
.map(|(_, members)| build_component(&close, &members))
.collect();
components.sort_by(|a, b| {
a.witness
.distance_squared
.total_cmp(&b.witness.distance_squared)
.then(a.witness.triangle_a.cmp(&b.witness.triangle_a))
.then(a.witness.triangle_b.cmp(&b.witness.triangle_b))
});
Ok(components)
}
fn build_component(close: &[MeshDistance], members: &[usize]) -> ProximityComponent {
let mut witness = close[members[0]];
let mut triangles_a = Vec::new();
let mut triangles_b = Vec::new();
for &index in members {
let entry = close[index];
if entry.distance_squared < witness.distance_squared {
witness = entry;
}
triangles_a.push(entry.triangle_a);
triangles_b.push(entry.triangle_b);
}
triangles_a.sort_unstable();
triangles_a.dedup();
triangles_b.sort_unstable();
triangles_b.dedup();
ProximityComponent {
witness,
triangles_a,
triangles_b,
}
}
fn find(parent: &mut [usize], mut node: usize) -> usize {
while parent[node] != node {
parent[node] = parent[parent[node]];
node = parent[node];
}
node
}
fn union(parent: &mut [usize], a: usize, b: usize) {
let (root_a, root_b) = (find(parent, a), find(parent, b));
if root_a != root_b {
parent[root_b] = root_a;
}
}
fn adjacent(corners: &[[usize; 3]], first: usize, second: usize) -> bool {
first == second
|| corners[first]
.iter()
.any(|a| corners[second].iter().any(|b| a == b))
}