use crate::collider::{Support, AABB};
use crate::math::{Fix128, QuatFix, Vec3Fix};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrientedBox {
pub center: Vec3Fix,
pub half_extents: Vec3Fix,
pub rotation: QuatFix,
}
impl OrientedBox {
#[inline]
#[must_use]
pub const fn new(center: Vec3Fix, half_extents: Vec3Fix, rotation: QuatFix) -> Self {
Self {
center,
half_extents,
rotation,
}
}
#[inline]
#[must_use]
pub const fn axis_aligned(center: Vec3Fix, half_extents: Vec3Fix) -> Self {
Self {
center,
half_extents,
rotation: QuatFix::IDENTITY,
}
}
#[must_use]
pub fn aabb(&self) -> AABB {
let local_x = Vec3Fix::new(self.half_extents.x, Fix128::ZERO, Fix128::ZERO);
let local_y = Vec3Fix::new(Fix128::ZERO, self.half_extents.y, Fix128::ZERO);
let local_z = Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, self.half_extents.z);
let world_x = self.rotation.unit_rotation().rotate_vec(local_x);
let world_y = self.rotation.unit_rotation().rotate_vec(local_y);
let world_z = self.rotation.unit_rotation().rotate_vec(local_z);
let extent = Vec3Fix::new(
world_x.x.abs() + world_y.x.abs() + world_z.x.abs(),
world_x.y.abs() + world_y.y.abs() + world_z.y.abs(),
world_x.z.abs() + world_y.z.abs() + world_z.z.abs(),
);
AABB::new(self.center - extent, self.center + extent)
}
#[must_use]
pub fn corner(&self, index: usize) -> Vec3Fix {
let sx = if index & 1 == 0 {
self.half_extents.x
} else {
-self.half_extents.x
};
let sy = if index & 2 == 0 {
self.half_extents.y
} else {
-self.half_extents.y
};
let sz = if index & 4 == 0 {
self.half_extents.z
} else {
-self.half_extents.z
};
let local = Vec3Fix::new(sx, sy, sz);
self.center + self.rotation.unit_rotation().rotate_vec(local)
}
#[must_use]
pub fn corners(&self) -> [Vec3Fix; 8] {
let mut result = [Vec3Fix::ZERO; 8];
for (i, item) in result.iter_mut().enumerate() {
*item = self.corner(i);
}
result
}
#[inline]
#[must_use]
pub fn volume(&self) -> Fix128 {
let eight = Fix128::from_int(8);
self.half_extents.x * self.half_extents.y * self.half_extents.z * eight
}
#[inline]
#[must_use]
pub fn surface_area(&self) -> Fix128 {
let two = Fix128::from_int(2);
let hx = self.half_extents.x * two;
let hy = self.half_extents.y * two;
let hz = self.half_extents.z * two;
two * (hx * hy + hy * hz + hz * hx)
}
#[must_use]
pub fn inertia_diagonal(&self, mass: Fix128) -> Vec3Fix {
let three = Fix128::from_int(3);
let ex2 = self.half_extents.x * self.half_extents.x;
let ey2 = self.half_extents.y * self.half_extents.y;
let ez2 = self.half_extents.z * self.half_extents.z;
Vec3Fix::new(
mass * (ey2 + ez2) / three,
mass * (ex2 + ez2) / three,
mass * (ex2 + ey2) / three,
)
}
}
impl Support for OrientedBox {
fn support(&self, direction: Vec3Fix) -> Vec3Fix {
let local_dir = self
.rotation
.unit_rotation()
.conjugate()
.rotate_vec(direction);
let local_support = Vec3Fix::new(
if local_dir.x >= Fix128::ZERO {
self.half_extents.x
} else {
-self.half_extents.x
},
if local_dir.y >= Fix128::ZERO {
self.half_extents.y
} else {
-self.half_extents.y
},
if local_dir.z >= Fix128::ZERO {
self.half_extents.z
} else {
-self.half_extents.z
},
);
self.center + self.rotation.unit_rotation().rotate_vec(local_support)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_axis_aligned_box_support() {
let b = OrientedBox::axis_aligned(Vec3Fix::ZERO, Vec3Fix::from_int(2, 3, 4));
let s = b.support(Vec3Fix::UNIT_X);
assert_eq!(s.x.hi, 2);
assert_eq!(s.y.hi, 3);
assert_eq!(s.z.hi, 4);
let s = b.support(-Vec3Fix::UNIT_X);
assert_eq!(s.x.hi, -2);
}
#[test]
fn test_box_aabb() {
let b = OrientedBox::axis_aligned(Vec3Fix::from_int(5, 0, 0), Vec3Fix::from_int(1, 1, 1));
let aabb = b.aabb();
assert_eq!(aabb.min.x.hi, 4);
assert_eq!(aabb.max.x.hi, 6);
}
#[test]
fn test_box_corners() {
let b = OrientedBox::axis_aligned(Vec3Fix::ZERO, Vec3Fix::from_int(1, 1, 1));
let corners = b.corners();
assert_eq!(corners.len(), 8);
for c in &corners {
let dist_sq = c.length_squared();
assert_eq!(dist_sq.hi, 3);
}
}
#[test]
fn test_rotated_box_support() {
let rot = QuatFix::from_axis_angle(Vec3Fix::UNIT_Y, Fix128::HALF_PI);
let b = OrientedBox::new(
Vec3Fix::ZERO,
Vec3Fix::new(Fix128::from_int(2), Fix128::ONE, Fix128::ONE),
rot,
);
let s = b.support(Vec3Fix::UNIT_Z);
assert!(
s.z > Fix128::ONE,
"Rotated box support should reflect orientation"
);
}
#[test]
fn test_box_volume() {
let b = OrientedBox::axis_aligned(Vec3Fix::ZERO, Vec3Fix::from_int(1, 2, 3));
assert_eq!(b.volume().hi, 48);
}
#[test]
fn test_box_inertia() {
let b = OrientedBox::axis_aligned(Vec3Fix::ZERO, Vec3Fix::from_int(1, 1, 1));
let inertia = b.inertia_diagonal(Fix128::from_int(6));
assert_eq!(inertia.x.hi, 4);
}
}