use alloc::boxed::Box;
use core::{fmt, ops::Index};
use nalgebra::{Point, SVector};
use crate::bounding_hierarchy::BHValue;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Aabb<T: BHValue, const D: usize> {
pub min: Point<T, D>,
pub max: Point<T, D>,
}
impl<T: BHValue + fmt::Display, const D: usize> fmt::Display for Aabb<T, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Min bound: {}; Max bound: {}", self.min, self.max)
}
}
pub trait Bounded<T: BHValue, const D: usize> {
fn aabb(&self) -> Aabb<T, D>;
}
impl<T: BHValue, const D: usize, B: Bounded<T, D>> Bounded<T, D> for &B {
fn aabb(&self) -> Aabb<T, D> {
B::aabb(self)
}
}
impl<T: BHValue, const D: usize, B: Bounded<T, D>> Bounded<T, D> for &mut B {
fn aabb(&self) -> Aabb<T, D> {
B::aabb(self)
}
}
impl<T: BHValue, const D: usize, B: Bounded<T, D>> Bounded<T, D> for Box<B> {
fn aabb(&self) -> Aabb<T, D> {
B::aabb(self)
}
}
impl<T: BHValue, const D: usize> Aabb<T, D> {
pub fn with_bounds(min: Point<T, D>, max: Point<T, D>) -> Self {
Aabb { min, max }
}
pub fn empty() -> Self {
Self {
min: SVector::<T, D>::from_element(T::infinity()).into(),
max: SVector::<T, D>::from_element(T::neg_infinity()).into(),
}
}
pub fn infinite() -> Self {
Self {
min: SVector::<T, D>::from_element(T::neg_infinity()).into(),
max: SVector::<T, D>::from_element(T::infinity()).into(),
}
}
pub fn contains(&self, p: &Point<T, D>) -> bool {
p >= &self.min && p <= &self.max
}
pub fn approx_contains_eps(&self, p: &Point<T, D>, epsilon: T) -> bool {
let ne = -epsilon;
(p - self.min) > SVector::from_element(ne)
&& (p - self.max) < SVector::from_element(epsilon)
}
pub fn approx_contains_aabb_eps(&self, other: &Aabb<T, D>, epsilon: T) -> bool {
self.approx_contains_eps(&other.min, epsilon)
&& self.approx_contains_eps(&other.max, epsilon)
}
pub fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool {
for i in 0..D {
if self.max[i] < aabb.min[i] || aabb.max[i] < self.min[i] {
return false;
}
}
true
}
pub fn relative_eq(&self, other: &Aabb<T, D>, epsilon: T) -> bool {
let ep_vec = SVector::from_element(epsilon);
(self.min - other.min).abs() < ep_vec && (self.max - other.max).abs() < ep_vec
}
pub fn join(&self, other: &Aabb<T, D>) -> Aabb<T, D> {
Aabb::with_bounds(
self.min.coords.inf(&other.min.coords).into(),
self.max.coords.sup(&other.max.coords).into(),
)
}
pub fn join_mut(&mut self, other: &Aabb<T, D>) {
*self = self.join(other);
}
pub fn grow(&self, other: &Point<T, D>) -> Aabb<T, D> {
Aabb::with_bounds(
self.min.coords.inf(&other.coords).into(),
self.max.coords.sup(&other.coords).into(),
)
}
pub fn grow_mut(&mut self, other: &Point<T, D>) {
*self = self.grow(other);
}
pub fn join_bounded<B: Bounded<T, D>>(&self, other: &B) -> Aabb<T, D> {
self.join(&other.aabb())
}
pub fn size(&self) -> SVector<T, D> {
self.max - self.min
}
#[inline]
pub fn half_size(&self) -> SVector<T, D> {
self.size() * T::from_f32(0.5).unwrap()
}
pub fn center(&self) -> Point<T, D> {
(self.min.coords * T::from_f32(0.5).unwrap() + self.max.coords * T::from_f32(0.5).unwrap())
.into()
}
pub fn is_empty(&self) -> bool {
self.min.coords.sup(&self.max.coords) != self.max.coords
}
pub fn surface_area(&self) -> T {
let size = self.size();
T::from_f32(2.0).unwrap() * size.dot(&size)
}
pub fn volume(&self) -> T {
self.size().product()
}
pub fn largest_axis(&self) -> usize {
self.size().imax()
}
pub fn min_distance_squared(&self, point: Point<T, D>) -> T {
let half_size = self.half_size();
let center = self.min + half_size;
let delta = point - center;
let q = delta.abs() - half_size;
let outside_vec = q.map(|x| x.max(T::zero()));
outside_vec.dot(&outside_vec)
}
}
impl<T: BHValue, const D: usize> Default for Aabb<T, D> {
fn default() -> Aabb<T, D> {
Aabb::empty()
}
}
impl<T: BHValue, const D: usize> Index<usize> for Aabb<T, D> {
type Output = Point<T, D>;
fn index(&self, index: usize) -> &Point<T, D> {
[&self.min, &self.max][index]
}
}
impl<T: BHValue, const D: usize> Bounded<T, D> for Aabb<T, D> {
fn aabb(&self) -> Aabb<T, D> {
*self
}
}
impl<T: BHValue, const D: usize> Bounded<T, D> for Point<T, D> {
fn aabb(&self) -> Aabb<T, D> {
Aabb::with_bounds(*self, *self)
}
}
#[cfg(test)]
mod tests {
use crate::aabb::Bounded;
use crate::testbase::{
TAabb3, TPoint3, TVector3, TupleVec, tuple_to_point, tuple_to_vector,
tuplevec_large_strategy,
};
use alloc::vec::Vec;
use float_eq::assert_float_eq;
use proptest::prelude::*;
#[test]
fn test_overflowing_aabb_center() {
let p1 = tuple_to_point(&(-3.288583e38, 0.0, 0.0));
let p2 = tuple_to_point(&(5.4196525e37, 0.0, 0.0));
let aabb = TAabb3::empty().grow(&p1).join_bounded(&p2);
assert!(aabb.size()[0].is_infinite());
assert!(aabb.center()[0].is_finite());
assert!(aabb.contains(&aabb.center()));
}
proptest! {
#[test]
fn test_intersecting_aabbs(a: TupleVec, b: TupleVec, c: TupleVec, d: TupleVec, p: TupleVec) {
let a = tuple_to_point(&a);
let b = tuple_to_point(&b);
let c = tuple_to_point(&c);
let d = tuple_to_point(&d);
let aabb1 = TAabb3::empty().grow(&a).join_bounded(&b);
let aabb2 = TAabb3::empty().grow(&c).join_bounded(&d);
if aabb1.intersects_aabb(&aabb2) {
let mut closest = aabb1.center();
for i in 0..3 {
closest[i] = closest[i].clamp(aabb2.min[i], aabb2.max[i]);
}
assert!(aabb1.contains(&closest), "closest={closest:?}");
assert!(aabb2.contains(&closest), "closest={closest:?}");
} else {
let p = tuple_to_point(&p);
for point in [a, b, c, d, p] {
assert!(!aabb1.contains(&point) || !aabb2.contains(&point));
}
}
}
#[test]
fn test_empty_contains_nothing(tpl: TupleVec) {
let p = tuple_to_point(&tpl);
let aabb = TAabb3::empty();
assert!(!aabb.contains(&p));
}
#[test]
fn test_default_is_empty(tpl: TupleVec) {
let p = tuple_to_point(&tpl);
let aabb: TAabb3 = Default::default();
assert!(!aabb.contains(&p));
}
#[test]
fn test_aabb_contains_center(a: TupleVec, b: TupleVec) {
let p1 = tuple_to_point(&a);
let p2 = tuple_to_point(&b);
let aabb = TAabb3::empty().grow(&p1).join_bounded(&p2);
assert!(aabb.contains(&aabb.center()));
}
#[test]
fn test_join_two_aabbs(a: (TupleVec, TupleVec, TupleVec, TupleVec, TupleVec),
b: (TupleVec, TupleVec, TupleVec, TupleVec, TupleVec))
{
let points = [a.0, a.1, a.2, a.3, a.4, b.0, b.1, b.2, b.3, b.4];
let points = points.iter().map(tuple_to_point).collect::<Vec<TPoint3>>();
let aabb1 = points.iter().take(5).fold(TAabb3::empty(), |aabb, point| aabb.grow(point));
let aabb2 = points.iter().skip(5).fold(TAabb3::empty(), |aabb, point| aabb.grow(point));
let aabb1_contains_init_five = points.iter()
.take(5)
.all(|point| aabb1.contains(point));
let aabb2_contains_last_five = points.iter()
.skip(5)
.all(|point| aabb2.contains(point));
let aabbu = aabb1.join(&aabb2);
let aabbu_contains_all = points.iter()
.all(|point| aabbu.contains(point));
assert!(aabb1_contains_init_five && aabb2_contains_last_five && aabbu_contains_all);
}
#[test]
fn test_points_relative_to_center_and_size(a in tuplevec_large_strategy(), b in tuplevec_large_strategy()) {
let aabb = TAabb3::empty()
.grow(&tuple_to_point(&a))
.grow(&tuple_to_point(&b));
let size = aabb.size();
let size_half = size / 2.0;
let center = aabb.center();
let inside_ppp = center + size_half * 0.9;
let inside_mmm = center - size_half * 0.9;
let outside_ppp = inside_ppp + size_half * 1.1;
let outside_mmm = inside_mmm - size_half * 1.1;
assert!(aabb.approx_contains_eps(&inside_ppp, f32::EPSILON));
assert!(aabb.approx_contains_eps(&inside_mmm, f32::EPSILON));
assert!(!aabb.contains(&outside_ppp));
assert!(!aabb.contains(&outside_mmm));
}
#[test]
fn test_surface_always_positive(a: TupleVec, b: TupleVec) {
let aabb = TAabb3::empty()
.grow(&tuple_to_point(&a))
.grow(&tuple_to_point(&b));
assert!(aabb.surface_area() >= 0.0);
}
#[test]
fn test_surface_area_cube(pos: TupleVec, size in f32::EPSILON..10e30_f32) {
let pos = tuple_to_point(&pos);
let size_vec = TVector3::new(size, size, size);
let aabb = TAabb3::with_bounds(pos, pos + size_vec);
let area_a = aabb.surface_area();
let area_b = 6.0 * size * size;
assert_float_eq!(area_a, area_b, rmax <= f32::EPSILON);
}
#[test]
fn test_volume_always_positive(a in tuplevec_large_strategy(), b in tuplevec_large_strategy()) {
let aabb = TAabb3::empty()
.grow(&tuple_to_point(&a))
.grow(&tuple_to_point(&b));
assert!(aabb.volume() >= 0.0);
}
#[test]
fn test_volume_by_hand(pos in tuplevec_large_strategy(), size in tuplevec_large_strategy()) {
let pos = tuple_to_point(&pos);
let size = tuple_to_vector(&size);
let aabb = pos.aabb().grow(&(pos + size));
let volume_a = aabb.volume();
let volume_b = (size.x * size.y * size.z).abs();
assert_float_eq!(volume_a, volume_b, rmax <= f32::EPSILON);
}
#[test]
fn test_create_aabb_from_indexable(a: TupleVec, b: TupleVec, p: TupleVec) {
let point = tuple_to_point(&p);
let aabb = TAabb3::empty()
.grow(&tuple_to_point(&a))
.grow(&tuple_to_point(&b));
let aabb_by_index = TAabb3::with_bounds(aabb[0], aabb[1]);
assert!(aabb.contains(&point) == aabb_by_index.contains(&point));
}
}
}