use crate::{BBox2D, Point3D, f64_max, f64_min};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BBox3D {
pub min_x: f64,
pub min_y: f64,
pub min_z: f64,
pub max_x: f64,
pub max_y: f64,
pub max_z: f64,
}
impl BBox3D {
#[must_use]
#[inline]
pub const fn new(
min_x: f64,
min_y: f64,
min_z: f64,
max_x: f64,
max_y: f64,
max_z: f64,
) -> Self {
Self {
min_x,
min_y,
min_z,
max_x,
max_y,
max_z,
}
}
#[must_use]
#[inline]
pub fn is_valid(&self) -> bool {
self.min_x <= self.max_x && self.min_y <= self.max_y && self.min_z <= self.max_z
}
#[must_use]
#[inline]
pub fn contains_point(&self, p: &Point3D) -> bool {
p.x >= self.min_x
&& p.x <= self.max_x
&& p.y >= self.min_y
&& p.y <= self.max_y
&& p.z >= self.min_z
&& p.z <= self.max_z
}
#[must_use]
#[inline]
pub fn intersects(&self, other: &BBox3D) -> bool {
self.min_x <= other.max_x
&& self.max_x >= other.min_x
&& self.min_y <= other.max_y
&& self.max_y >= other.min_y
&& self.min_z <= other.max_z
&& self.max_z >= other.min_z
}
#[must_use]
#[inline]
pub fn union(&self, other: &BBox3D) -> BBox3D {
BBox3D::new(
f64_min(self.min_x, other.min_x),
f64_min(self.min_y, other.min_y),
f64_min(self.min_z, other.min_z),
f64_max(self.max_x, other.max_x),
f64_max(self.max_y, other.max_y),
f64_max(self.max_z, other.max_z),
)
}
#[must_use]
#[inline]
pub fn volume(&self) -> f64 {
let w = self.max_x - self.min_x;
let h = self.max_y - self.min_y;
let d = self.max_z - self.min_z;
if w < 0.0 || h < 0.0 || d < 0.0 {
0.0
} else {
w * h * d
}
}
#[must_use]
#[inline]
pub fn center(&self) -> Point3D {
Point3D::new(
(self.min_x + self.max_x) * 0.5,
(self.min_y + self.max_y) * 0.5,
(self.min_z + self.max_z) * 0.5,
)
}
#[must_use]
#[inline]
pub fn expand(&self, amount: f64) -> BBox3D {
BBox3D::new(
self.min_x - amount,
self.min_y - amount,
self.min_z - amount,
self.max_x + amount,
self.max_y + amount,
self.max_z + amount,
)
}
#[must_use]
#[inline]
pub fn surface_area(&self) -> f64 {
let w = self.max_x - self.min_x;
let h = self.max_y - self.min_y;
let d = self.max_z - self.min_z;
if w < 0.0 || h < 0.0 || d < 0.0 {
0.0
} else {
2.0 * (w * h + h * d + w * d)
}
}
#[must_use]
#[inline]
pub fn to_2d(&self) -> BBox2D {
BBox2D::new(self.min_x, self.min_y, self.max_x, self.max_y)
}
#[must_use]
pub fn diagonal(&self) -> f64 {
let dx = self.max_x - self.min_x;
let dy = self.max_y - self.min_y;
let dz = self.max_z - self.min_z;
crate::libm_sqrt(dx * dx + dy * dy + dz * dz)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_and_valid() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
assert!(bbox.is_valid());
}
#[test]
fn test_invalid() {
let bbox = BBox3D::new(1.0, 0.0, 0.0, 0.0, 1.0, 1.0);
assert!(!bbox.is_valid());
}
#[test]
fn test_contains_point_inside() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
assert!(bbox.contains_point(&Point3D::new(5.0, 5.0, 5.0)));
}
#[test]
fn test_contains_point_on_boundary() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
assert!(bbox.contains_point(&Point3D::new(0.0, 0.0, 0.0)));
assert!(bbox.contains_point(&Point3D::new(10.0, 10.0, 10.0)));
}
#[test]
fn test_contains_point_outside() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
assert!(!bbox.contains_point(&Point3D::new(-1.0, 5.0, 5.0)));
assert!(!bbox.contains_point(&Point3D::new(5.0, 11.0, 5.0)));
assert!(!bbox.contains_point(&Point3D::new(5.0, 5.0, -0.1)));
}
#[test]
fn test_intersects() {
let a = BBox3D::new(0.0, 0.0, 0.0, 5.0, 5.0, 5.0);
let b = BBox3D::new(3.0, 3.0, 3.0, 8.0, 8.0, 8.0);
assert!(a.intersects(&b));
assert!(b.intersects(&a));
}
#[test]
fn test_no_intersect() {
let a = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
let b = BBox3D::new(2.0, 2.0, 2.0, 3.0, 3.0, 3.0);
assert!(!a.intersects(&b));
}
#[test]
fn test_union() {
let a = BBox3D::new(0.0, 0.0, 0.0, 5.0, 5.0, 5.0);
let b = BBox3D::new(3.0, -1.0, 2.0, 8.0, 4.0, 10.0);
let u = a.union(&b);
assert!((u.min_x - 0.0).abs() < f64::EPSILON);
assert!((u.min_y - (-1.0)).abs() < f64::EPSILON);
assert!((u.min_z - 0.0).abs() < f64::EPSILON);
assert!((u.max_x - 8.0).abs() < f64::EPSILON);
assert!((u.max_y - 5.0).abs() < f64::EPSILON);
assert!((u.max_z - 10.0).abs() < f64::EPSILON);
}
#[test]
fn test_volume() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 2.0, 3.0, 4.0);
assert!((bbox.volume() - 24.0).abs() < f64::EPSILON);
}
#[test]
fn test_volume_zero() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 0.0, 3.0, 4.0);
assert!((bbox.volume() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_volume_invalid() {
let bbox = BBox3D::new(1.0, 0.0, 0.0, 0.0, 1.0, 1.0);
assert!((bbox.volume() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_center() {
let bbox = BBox3D::new(0.0, 2.0, 4.0, 10.0, 8.0, 12.0);
let c = bbox.center();
assert!((c.x - 5.0).abs() < f64::EPSILON);
assert!((c.y - 5.0).abs() < f64::EPSILON);
assert!((c.z - 8.0).abs() < f64::EPSILON);
}
#[test]
fn test_expand() {
let bbox = BBox3D::new(1.0, 1.0, 1.0, 3.0, 3.0, 3.0);
let expanded = bbox.expand(0.5);
assert!((expanded.min_x - 0.5).abs() < f64::EPSILON);
assert!((expanded.max_z - 3.5).abs() < f64::EPSILON);
assert!((expanded.volume() - 27.0).abs() < 1e-10);
}
#[test]
fn test_surface_area() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 2.0, 3.0, 4.0);
assert!((bbox.surface_area() - 52.0).abs() < f64::EPSILON);
}
#[test]
fn test_to_2d() {
let bbox3 = BBox3D::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
let bbox2 = bbox3.to_2d();
assert!((bbox2.min_x - 1.0).abs() < f64::EPSILON);
assert!((bbox2.min_y - 2.0).abs() < f64::EPSILON);
assert!((bbox2.max_x - 4.0).abs() < f64::EPSILON);
assert!((bbox2.max_y - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_diagonal() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 3.0, 4.0, 0.0);
assert!((bbox.diagonal() - 5.0).abs() < 1e-10);
}
#[test]
fn test_diagonal_cube() {
let bbox = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
assert!((bbox.diagonal() - 1.732_050_808_068_872_4).abs() < 1e-8);
}
#[test]
fn test_intersects_touching() {
let a = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
let b = BBox3D::new(1.0, 1.0, 1.0, 2.0, 2.0, 2.0);
assert!(a.intersects(&b)); }
}