use axiolid_core::{Point3, Scalar, Vec3};
use crate::{project, ConvexShape};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Contact {
pub axis: Vec3,
pub distance: Scalar,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum SeparationResult {
Apart {
axis: Vec3,
distance: Scalar,
},
Overlapping,
}
#[must_use]
pub fn separation(a: &ConvexShape, b: &ConvexShape, tolerance: Scalar) -> SeparationResult {
if a.is_empty() || b.is_empty() {
return SeparationResult::Apart {
axis: Vec3::new(1.0, 0.0, 0.0),
distance: Scalar::INFINITY,
};
}
let mut best_axis = Vec3::ZERO;
let mut best_gap = Scalar::NEG_INFINITY;
for axis in candidate_axes(a, b, tolerance) {
let (a_min, a_max) = project(&a.points, axis);
let (b_min, b_max) = project(&b.points, axis);
let gap = (b_min - a_max).max(a_min - b_max);
if gap > best_gap {
best_gap = gap;
best_axis = if b_min - a_max >= a_min - b_max {
axis
} else {
-axis
};
}
if gap > tolerance {
return SeparationResult::Apart {
axis: best_axis,
distance: gap,
};
}
}
if best_gap > tolerance {
SeparationResult::Apart {
axis: best_axis,
distance: best_gap,
}
} else {
SeparationResult::Overlapping
}
}
fn candidate_axes(a: &ConvexShape, b: &ConvexShape, tolerance: Scalar) -> Vec<Vec3> {
let floor = (tolerance * tolerance).max(1e-24);
let mut axes = Vec::new();
let push = |v: Vec3, axes: &mut Vec<Vec3>| {
let length_squared = v.length_squared();
if length_squared > floor {
axes.push(v / length_squared.sqrt());
}
};
for &normal in a.normals.iter().chain(b.normals.iter()) {
push(normal, &mut axes);
}
for &edge_a in &a.edges {
for &edge_b in &b.edges {
push(edge_a.cross(edge_b), &mut axes);
}
}
if axes.is_empty() {
let direction = centroid(&b.points) - centroid(&a.points);
push(direction, &mut axes);
}
axes
}
fn centroid(points: &[Point3]) -> Point3 {
let mut sum = Vec3::ZERO;
for point in points {
sum += *point;
}
sum / points.len() as Scalar
}