use crate::Point;
use num_traits::Float;
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::Deserialize<'de>")))]
pub struct AABB<T, const N: usize> {
min: Point<T, N>,
max: Point<T, N>,
}
impl<T, const N: usize> AABB<T, N>
where
T: Float,
{
#[inline]
pub fn new(min: Point<T, N>, max: Point<T, N>) -> Self {
Self { min, max }
}
#[inline]
pub fn min(&self) -> Point<T, N>
where
T: Copy,
{
self.min
}
#[inline]
pub fn max(&self) -> Point<T, N>
where
T: Copy,
{
self.max
}
#[inline]
pub fn min_ref(&self) -> &Point<T, N> {
&self.min
}
#[inline]
pub fn max_ref(&self) -> &Point<T, N> {
&self.max
}
#[inline]
pub fn set_min(&mut self, min: Point<T, N>) {
self.min = min;
}
#[inline]
pub fn set_max(&mut self, max: Point<T, N>) {
self.max = max;
}
#[inline]
pub fn min_ref_mut(&mut self) -> &mut Point<T, N> {
&mut self.min
}
#[inline]
pub fn max_ref_mut(&mut self) -> &mut Point<T, N> {
&mut self.max
}
#[inline]
pub fn intersects(&self, other: &Self) -> bool {
for i in 0..N {
if self.max_ref().coords_ref()[i] <= other.min_ref().coords_ref()[i]
|| other.max_ref().coords_ref()[i] <= self.min_ref().coords_ref()[i]
{
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Point;
#[test]
fn test_aabb_overlap_2d() {
let box1 = AABB::new(Point::new([0.0, 0.0]), Point::new([10.0, 10.0]));
let box2 = AABB::new(Point::new([5.0, 5.0]), Point::new([15.0, 15.0]));
assert!(
box1.intersects(&box2),
"Boxes should overlap in the center region"
);
}
#[test]
fn test_aabb_no_overlap_separated() {
let box1 = AABB::new(Point::new([0.0, 0.0]), Point::new([2.0, 2.0]));
let box2 = AABB::new(Point::new([5.0, 5.0]), Point::new([7.0, 7.0]));
assert!(!box1.intersects(&box2), "Boxes are clearly separated");
}
#[test]
fn test_aabb_touching_edge_returns_false() {
let box1 = AABB::new(Point::new([0.0, 0.0]), Point::new([5.0, 5.0]));
let box2 = AABB::new(Point::new([5.0, 0.0]), Point::new([10.0, 5.0]));
assert!(
!box1.intersects(&box2),
"Touching edges should not count as overlap"
);
}
#[test]
fn test_aabb_one_inside_another() {
let large = AABB::new(
Point::new([0.0, 0.0, 0.0]),
Point::new([100.0, 100.0, 100.0]),
);
let small = AABB::new(
Point::new([40.0, 40.0, 40.0]),
Point::new([60.0, 60.0, 60.0]),
);
assert!(
large.intersects(&small),
"Contained box must trigger intersection"
);
assert!(small.intersects(&large), "Symmetry check failed");
}
#[test]
fn test_aabb_3d_mismatch_on_one_axis() {
let box1 = AABB::new(Point::new([0.0, 0.0, 0.0]), Point::new([10.0, 10.0, 10.0]));
let box2 = AABB::new(Point::new([5.0, 5.0, 20.0]), Point::new([15.0, 15.0, 30.0]));
assert!(
!box1.intersects(&box2),
"Separation on Z axis should prevent overlap"
);
}
#[cfg(feature = "serde")]
#[test]
fn test_aabb_serialization_roundtrip() {
use serde_json;
let aabb = AABB::new(Point::new([0.0, 0.0]), Point::new([10.0, 10.0]));
let json = serde_json::to_string(&aabb).unwrap();
let restored: AABB<f64, 2> = serde_json::from_str(&json).unwrap();
assert_eq!(aabb.min_ref(), restored.min_ref());
assert_eq!(aabb.max_ref(), restored.max_ref());
}
}