use crate::collider::{Support, AABB};
use crate::math::{Fix128, QuatFix, Vec3Fix};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cylinder {
pub center: Vec3Fix,
pub half_height: Fix128,
pub radius: Fix128,
pub rotation: QuatFix,
}
impl Cylinder {
#[inline]
#[must_use]
pub const fn new(center: Vec3Fix, half_height: Fix128, radius: Fix128) -> Self {
Self {
center,
half_height,
radius,
rotation: QuatFix::IDENTITY,
}
}
#[inline]
#[must_use]
pub const fn with_rotation(
center: Vec3Fix,
half_height: Fix128,
radius: Fix128,
rotation: QuatFix,
) -> Self {
Self {
center,
half_height,
radius,
rotation,
}
}
#[must_use]
pub fn aabb(&self) -> AABB {
let local_y = Vec3Fix::new(Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
let world_y = self.rotation.rotate_vec(local_y);
let axis_extent = Vec3Fix::new(
(world_y.x * self.half_height).abs(),
(world_y.y * self.half_height).abs(),
(world_y.z * self.half_height).abs(),
);
let radial_extent = Vec3Fix::new(self.radius, self.radius, self.radius);
let total = Vec3Fix::new(
axis_extent.x + radial_extent.x,
axis_extent.y + radial_extent.y,
axis_extent.z + radial_extent.z,
);
AABB::new(self.center - total, self.center + total)
}
#[inline]
#[must_use]
pub fn volume(&self) -> Fix128 {
let pi = Fix128::from_ratio(355, 113);
let two = Fix128::from_int(2);
pi * self.radius * self.radius * two * self.half_height
}
#[inline]
#[must_use]
pub fn surface_area(&self) -> Fix128 {
let pi = Fix128::from_ratio(355, 113);
let two = Fix128::from_int(2);
two * pi * self.radius * (self.radius + two * self.half_height)
}
#[must_use]
pub fn inertia_diagonal(&self, mass: Fix128) -> Vec3Fix {
let r2 = self.radius * self.radius;
let h = self.half_height * Fix128::from_int(2);
let h2 = h * h;
let three = Fix128::from_int(3);
let twelve = Fix128::from_int(12);
let two = Fix128::from_int(2);
let ixx = mass * (three * r2 + h2) / twelve;
let iyy = mass * r2 / two;
Vec3Fix::new(ixx, iyy, ixx)
}
}
impl Support for Cylinder {
fn support(&self, direction: Vec3Fix) -> Vec3Fix {
let local_dir = self.rotation.conjugate().rotate_vec(direction);
let y_support = if local_dir.y >= Fix128::ZERO {
self.half_height
} else {
-self.half_height
};
let xz_len_sq = local_dir.x * local_dir.x + local_dir.z * local_dir.z;
let local_support = if xz_len_sq.is_zero() {
Vec3Fix::new(Fix128::ZERO, y_support, Fix128::ZERO)
} else {
let xz_len = xz_len_sq.sqrt();
let x_support = self.radius * local_dir.x / xz_len;
let z_support = self.radius * local_dir.z / xz_len;
Vec3Fix::new(x_support, y_support, z_support)
};
self.center + self.rotation.rotate_vec(local_support)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cylinder_support_up() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::from_int(2), Fix128::ONE);
let s = cyl.support(Vec3Fix::UNIT_Y);
assert_eq!(s.y.hi, 2);
}
#[test]
fn test_cylinder_support_side() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::from_int(2), Fix128::from_int(3));
let s = cyl.support(Vec3Fix::UNIT_X);
assert_eq!(s.x.hi, 3); assert_eq!(s.y.hi, 2); }
#[test]
fn test_cylinder_support_down() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::from_int(2), Fix128::ONE);
let s = cyl.support(-Vec3Fix::UNIT_Y);
assert_eq!(s.y.hi, -2);
}
#[test]
fn test_cylinder_aabb() {
let cyl = Cylinder::new(Vec3Fix::from_int(5, 0, 0), Fix128::from_int(2), Fix128::ONE);
let aabb = cyl.aabb();
assert_eq!(aabb.min.x.hi, 4); assert_eq!(aabb.max.x.hi, 6); assert_eq!(aabb.min.y.hi, -3); assert_eq!(aabb.max.y.hi, 3); }
#[test]
fn test_cylinder_volume() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::ONE, Fix128::ONE);
let v = cyl.volume();
assert!(
v.hi >= 6 && v.hi <= 7,
"Volume should be ~6.28, got {}",
v.hi
);
}
#[test]
fn test_cylinder_inertia() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::ONE, Fix128::ONE);
let mass = Fix128::from_int(12);
let inertia = cyl.inertia_diagonal(mass);
assert_eq!(inertia.x.hi, 7);
assert_eq!(inertia.y.hi, 6);
}
#[test]
fn test_cylinder_surface_area() {
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::ONE, Fix128::ONE);
let sa = cyl.surface_area();
assert!(
sa.hi >= 18 && sa.hi <= 19,
"Surface area should be ~18.85, got {}",
sa.hi
);
}
#[test]
fn test_rotated_cylinder_support() {
let rot = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::HALF_PI);
let cyl = Cylinder::with_rotation(Vec3Fix::ZERO, Fix128::from_int(3), Fix128::ONE, rot);
let s = cyl.support(Vec3Fix::UNIT_X);
assert!(
s.x > Fix128::ONE,
"Rotated cylinder support should extend along X"
);
}
#[test]
fn test_cylinder_gjk_collision() {
use crate::collider::{gjk, Sphere};
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::from_int(2), Fix128::ONE);
let sphere = Sphere::new(Vec3Fix::from_int(0, 0, 0), Fix128::ONE);
let result = gjk(&cyl, &sphere);
assert!(
result.colliding,
"Overlapping cylinder and sphere should collide"
);
}
#[test]
fn test_cylinder_gjk_no_collision() {
use crate::collider::{gjk, Sphere};
let cyl = Cylinder::new(Vec3Fix::ZERO, Fix128::ONE, Fix128::ONE);
let sphere = Sphere::new(Vec3Fix::from_int(10, 0, 0), Fix128::ONE);
let result = gjk(&cyl, &sphere);
assert!(
!result.colliding,
"Separated cylinder and sphere should not collide"
);
}
}