use crate::point::Point;
use crate::shape::Shape;
use nalgebra::SVector;
#[derive(Clone, Copy, Debug)]
pub struct Capsule<const D: usize> {
pub half_height: f64,
pub radius: f64,
pub axis: usize,
}
impl<const D: usize> Capsule<D> {
pub fn new(half_height: f64, radius: f64, axis: usize) -> Self {
debug_assert!(axis < D, "axis {axis} out of range for D={D}");
Self {
half_height,
radius,
axis,
}
}
pub fn y_aligned(half_height: f64, radius: f64) -> Self {
assert!(D >= 2, "Y-axis requires D >= 2");
Self::new(half_height, radius, 1)
}
pub fn total_length(&self) -> f64 {
2.0 * self.half_height + 2.0 * self.radius
}
}
impl<const D: usize> Shape<D> for Capsule<D> {
fn support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
let norm = direction.norm();
if norm < 1e-15 {
let mut result = SVector::zeros();
result[self.axis] = self.half_height;
return result;
}
let mut center = SVector::zeros();
center[self.axis] = if direction[self.axis] >= 0.0 {
self.half_height
} else {
-self.half_height
};
center + direction * (self.radius / norm)
}
fn bounding_sphere(&self) -> (Point<D>, f64) {
(Point::origin(), self.half_height + self.radius)
}
fn as_any(&self) -> &dyn std::any::Any { self }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn support_along_axis() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
let dir = SVector::from([0.0, 1.0, 0.0]);
let sp = cap.support(&dir);
assert!((sp[1] - 2.5).abs() < 1e-12, "support Y+ = {}", sp[1]);
}
#[test]
fn support_negative_axis() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
let dir = SVector::from([0.0, -1.0, 0.0]);
let sp = cap.support(&dir);
assert!((sp[1] - (-2.5)).abs() < 1e-12, "support Y- = {}", sp[1]);
}
#[test]
fn support_perpendicular() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
let dir = SVector::from([1.0, 0.0, 0.0]);
let sp = cap.support(&dir);
assert!((sp[0] - 0.5).abs() < 1e-12, "support X = {}", sp[0]);
}
#[test]
fn support_diagonal() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
let dir = SVector::from([1.0, 1.0, 0.0]);
let sp = cap.support(&dir);
let expected_y = 2.0 + 0.5 / 2.0f64.sqrt();
let expected_x = 0.5 / 2.0f64.sqrt();
assert!((sp[1] - expected_y).abs() < 1e-10, "diag Y = {}", sp[1]);
assert!((sp[0] - expected_x).abs() < 1e-10, "diag X = {}", sp[0]);
}
#[test]
fn bounding_sphere_contains_capsule() {
let cap = Capsule::<3>::y_aligned(3.0, 1.0);
let (center, radius) = cap.bounding_sphere();
assert!((center.coord(0)).abs() < 1e-12);
assert!((radius - 4.0).abs() < 1e-12);
}
#[test]
fn capsule_x_aligned() {
let cap = Capsule::<3>::new(1.5, 0.3, 0);
let dir = SVector::from([1.0, 0.0, 0.0]);
let sp = cap.support(&dir);
assert!((sp[0] - 1.8).abs() < 1e-12, "X-capsule support = {}", sp[0]);
}
#[test]
fn capsule_4d() {
let cap = Capsule::<4>::new(2.0, 1.0, 3); let dir = SVector::from([0.0, 0.0, 0.0, 1.0]);
let sp = cap.support(&dir);
assert!((sp[3] - 3.0).abs() < 1e-12, "4D capsule W+ = {}", sp[3]);
}
#[test]
fn capsule_2d() {
let cap = Capsule::<2>::new(1.0, 0.5, 0); let dir = SVector::from([0.0, 1.0]);
let sp = cap.support(&dir);
assert!((sp[1] - 0.5).abs() < 1e-12, "2D capsule Y = {}", sp[1]);
}
#[test]
fn degenerate_direction() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
let dir = SVector::from([0.0, 0.0, 0.0]);
let sp = cap.support(&dir);
assert!((sp[1] - 2.0).abs() < 1e-12);
}
#[test]
fn total_length() {
let cap = Capsule::<3>::y_aligned(2.0, 0.5);
assert!((cap.total_length() - 5.0).abs() < 1e-12);
}
}