#![allow(clippy::needless_range_loop)]
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct Aabb<S: ControlScalar> {
pub min: [S; 3],
pub max: [S; 3],
}
impl<S: ControlScalar> Aabb<S> {
pub fn new(min: [S; 3], max: [S; 3]) -> Self {
Self { min, max }
}
pub fn intersects(&self, other: &Self) -> bool {
for i in 0..3 {
if self.max[i] < other.min[i] || other.max[i] < self.min[i] {
return false;
}
}
true
}
pub fn contains_point(&self, p: [S; 3]) -> bool {
for i in 0..3 {
if p[i] < self.min[i] || p[i] > self.max[i] {
return false;
}
}
true
}
pub fn expand(&self, margin: S) -> Self {
Self {
min: core::array::from_fn(|i| self.min[i] - margin),
max: core::array::from_fn(|i| self.max[i] + margin),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Capsule<S: ControlScalar> {
pub p0: [S; 3],
pub p1: [S; 3],
pub radius: S,
}
impl<S: ControlScalar> Capsule<S> {
pub fn new(p0: [S; 3], p1: [S; 3], radius: S) -> Self {
Self { p0, p1, radius }
}
pub fn length(&self) -> S {
let dx = self.p1[0] - self.p0[0];
let dy = self.p1[1] - self.p0[1];
let dz = self.p1[2] - self.p0[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
}
pub fn segment_distance<S: ControlScalar>(p0: [S; 3], p1: [S; 3], q0: [S; 3], q1: [S; 3]) -> S {
let d1 = vec3_sub(p1, p0); let d2 = vec3_sub(q1, q0); let r = vec3_sub(p0, q0);
let a = vec3_dot(d1, d1); let e = vec3_dot(d2, d2); let f = vec3_dot(d2, r);
let c = vec3_dot(d1, r);
let eps = S::EPSILON * S::from_f64(1e6);
let mut s;
let mut t;
if a <= eps && e <= eps {
return vec3_len(r);
}
if a <= eps {
s = S::ZERO;
t = (f / e).clamp_val(S::ZERO, S::ONE);
} else if e <= eps {
t = S::ZERO;
s = (-c / a).clamp_val(S::ZERO, S::ONE);
} else {
let b = vec3_dot(d1, d2);
let denom = a * e - b * b;
if denom.abs() > eps {
s = ((b * f - c * e) / denom).clamp_val(S::ZERO, S::ONE);
} else {
s = S::ZERO; }
t = (b * s + f) / e;
if t < S::ZERO {
t = S::ZERO;
s = (-c / a).clamp_val(S::ZERO, S::ONE);
} else if t > S::ONE {
t = S::ONE;
s = ((b - c) / a).clamp_val(S::ZERO, S::ONE);
}
}
let cp = vec3_add(p0, vec3_scale(d1, s));
let cq = vec3_add(q0, vec3_scale(d2, t));
vec3_len(vec3_sub(cp, cq))
}
pub fn capsule_distance<S: ControlScalar>(a: &Capsule<S>, b: &Capsule<S>) -> S {
let seg_dist = segment_distance(a.p0, a.p1, b.p0, b.p1);
let surface_dist = seg_dist - a.radius - b.radius;
if surface_dist < S::ZERO {
S::ZERO
} else {
surface_dist
}
}
pub struct SelfCollisionChecker<S: ControlScalar, const N: usize> {
pub capsules: [Capsule<S>; N],
pub min_distance: S,
}
impl<S: ControlScalar, const N: usize> SelfCollisionChecker<S, N> {
pub fn new(capsules: [Capsule<S>; N], min_distance: S) -> Self {
Self {
capsules,
min_distance,
}
}
pub fn update_link(&mut self, i: usize, capsule: Capsule<S>) {
self.capsules[i] = capsule;
}
pub fn check_self_collision(&self) -> bool {
for i in 0..N {
for j in (i + 2)..N {
if self.link_distance(i, j) < self.min_distance {
return true;
}
}
}
false
}
pub fn link_distance(&self, i: usize, j: usize) -> S {
capsule_distance(&self.capsules[i], &self.capsules[j])
}
}
#[inline]
fn vec3_sub<S: ControlScalar>(a: [S; 3], b: [S; 3]) -> [S; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn vec3_add<S: ControlScalar>(a: [S; 3], b: [S; 3]) -> [S; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
#[inline]
fn vec3_scale<S: ControlScalar>(a: [S; 3], s: S) -> [S; 3] {
[a[0] * s, a[1] * s, a[2] * s]
}
#[inline]
fn vec3_dot<S: ControlScalar>(a: [S; 3], b: [S; 3]) -> S {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn vec3_len<S: ControlScalar>(a: [S; 3]) -> S {
vec3_dot(a, a).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aabb_intersection() {
let a = Aabb::new([0.0_f64; 3], [1.0; 3]);
let b = Aabb::new([0.5; 3], [1.5; 3]);
assert!(a.intersects(&b));
let c = Aabb::new([2.0; 3], [3.0; 3]);
assert!(!a.intersects(&c));
}
#[test]
fn aabb_contains_point() {
let a = Aabb::new([0.0_f64; 3], [1.0; 3]);
assert!(a.contains_point([0.5; 3]));
assert!(!a.contains_point([1.5; 3]));
}
#[test]
fn capsule_length() {
let c = Capsule::new([0.0_f64; 3], [3.0, 4.0, 0.0], 0.1);
assert!((c.length() - 5.0).abs() < 1e-10, "len={}", c.length());
}
#[test]
fn segment_distance_parallel_segments() {
let dist = segment_distance(
[0.0_f64, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0_f64, 1.0, 0.0],
[1.0, 1.0, 0.0],
);
assert!((dist - 1.0).abs() < 1e-9, "dist={dist}");
}
#[test]
fn capsule_distance_no_overlap() {
let a = Capsule::new([0.0_f64; 3], [1.0, 0.0, 0.0], 0.1);
let b = Capsule::new([2.0, 0.0, 0.0_f64], [3.0, 0.0, 0.0], 0.1);
let d = capsule_distance(&a, &b);
assert!((d - 0.8).abs() < 1e-9, "d={d}");
}
#[test]
fn self_collision_checker_no_collision() {
let caps = [
Capsule::new([0.0_f64; 3], [1.0, 0.0, 0.0], 0.05),
Capsule::new([1.0, 0.0, 0.0_f64], [2.0, 0.0, 0.0], 0.05),
Capsule::new([2.0, 0.0, 0.0_f64], [3.0, 0.0, 0.0], 0.05),
];
let checker = SelfCollisionChecker::new(caps, 0.01);
assert!(!checker.check_self_collision());
}
#[test]
fn self_collision_checker_detects_collision() {
let caps = [
Capsule::new([0.0_f64; 3], [1.0, 0.0, 0.0], 0.1),
Capsule::new([1.0, 0.0, 0.0_f64], [0.5, 0.1, 0.0], 0.1),
Capsule::new([0.5, 0.1, 0.0_f64], [0.1, 0.0, 0.0], 0.1),
];
let checker = SelfCollisionChecker::new(caps, 0.01);
assert!(checker.check_self_collision());
}
}