use std::{error::Error, fmt};
pub(crate) type Num = f64;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BoundsError {
InvalidBounds {
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
},
InvalidBounds3D {
min_x: f64,
min_y: f64,
min_z: f64,
max_x: f64,
max_y: f64,
max_z: f64,
},
}
impl fmt::Display for BoundsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BoundsError::InvalidBounds { .. } => {
write!(f, "bounds must satisfy min_x <= max_x and min_y <= max_y")
}
BoundsError::InvalidBounds3D { .. } => write!(
f,
"bounds must satisfy min_x <= max_x, min_y <= max_y, and min_z <= max_z"
),
}
}
}
impl Error for BoundsError {}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Box2D {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
}
impl Box2D {
#[inline]
pub const fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
Self {
min_x,
min_y,
max_x,
max_y,
}
}
#[inline]
pub const fn from_point(point: Point2D) -> Self {
Self::new(point.x, point.y, point.x, point.y)
}
#[inline]
pub const fn try_new(
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
) -> Result<Self, BoundsError> {
if min_x <= max_x && min_y <= max_y {
Ok(Self::new(min_x, min_y, max_x, max_y))
} else {
Err(BoundsError::InvalidBounds {
min_x,
min_y,
max_x,
max_y,
})
}
}
#[inline]
pub fn overlaps(&self, other: Box2D) -> 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)
}
#[inline]
pub fn contains(&self, other: Box2D) -> bool {
(self.min_x <= other.min_x)
& (self.min_y <= other.min_y)
& (self.max_x >= other.max_x)
& (self.max_y >= other.max_y)
}
#[inline]
pub fn contains_point(&self, point: Point2D) -> bool {
(self.min_x <= point.x)
& (self.max_x >= point.x)
& (self.min_y <= point.y)
& (self.max_y >= point.y)
}
#[inline]
pub(crate) fn distance_squared_to(&self, point: Point2D) -> f64 {
let dx = axis_distance(point.x, self.min_x, self.max_x);
let dy = axis_distance(point.y, self.min_y, self.max_y);
dx * dx + dy * dy
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Box3D {
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 Box3D {
#[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,
}
}
#[inline]
pub const fn from_point(point: Point3D) -> Self {
Self::new(point.x, point.y, point.z, point.x, point.y, point.z)
}
#[inline]
pub const fn try_new(
min_x: f64,
min_y: f64,
min_z: f64,
max_x: f64,
max_y: f64,
max_z: f64,
) -> Result<Self, BoundsError> {
if min_x <= max_x && min_y <= max_y && min_z <= max_z {
Ok(Self::new(min_x, min_y, min_z, max_x, max_y, max_z))
} else {
Err(BoundsError::InvalidBounds3D {
min_x,
min_y,
min_z,
max_x,
max_y,
max_z,
})
}
}
#[inline]
pub fn overlaps(&self, other: Box3D) -> 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)
}
#[inline]
pub fn contains(&self, other: Box3D) -> bool {
(self.min_x <= other.min_x)
& (self.min_y <= other.min_y)
& (self.min_z <= other.min_z)
& (self.max_x >= other.max_x)
& (self.max_y >= other.max_y)
& (self.max_z >= other.max_z)
}
#[inline]
pub fn contains_point(&self, point: Point3D) -> bool {
(self.min_x <= point.x)
& (self.max_x >= point.x)
& (self.min_y <= point.y)
& (self.max_y >= point.y)
& (self.min_z <= point.z)
& (self.max_z >= point.z)
}
#[inline]
pub(crate) fn distance_squared_to(&self, point: Point3D) -> f64 {
let dx = axis_distance(point.x, self.min_x, self.max_x);
let dy = axis_distance(point.y, self.min_y, self.max_y);
let dz = axis_distance(point.z, self.min_z, self.max_z);
dx * dx + dy * dy + dz * dz
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point2D {
pub x: f64,
pub y: f64,
}
impl Point2D {
#[inline]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3D {
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[inline(always)]
pub(crate) const fn empty_box2d() -> Box2D {
Box2D::new(
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
)
}
#[inline(always)]
pub(crate) fn extend_box2d(bounds: &mut Box2D, other: Box2D) {
bounds.min_x = bounds.min_x.min(other.min_x);
bounds.min_y = bounds.min_y.min(other.min_y);
bounds.max_x = bounds.max_x.max(other.max_x);
bounds.max_y = bounds.max_y.max(other.max_y);
}
#[inline(always)]
pub(crate) const fn empty_box3d() -> Box3D {
Box3D::new(
f64::INFINITY,
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
)
}
#[inline(always)]
pub(crate) fn extend_box3d(bounds: &mut Box3D, other: Box3D) {
bounds.min_x = bounds.min_x.min(other.min_x);
bounds.min_y = bounds.min_y.min(other.min_y);
bounds.min_z = bounds.min_z.min(other.min_z);
bounds.max_x = bounds.max_x.max(other.max_x);
bounds.max_y = bounds.max_y.max(other.max_y);
bounds.max_z = bounds.max_z.max(other.max_z);
}
#[inline]
fn axis_distance(point: f64, min: f64, max: f64) -> f64 {
if point < min {
min - point
} else if point > max {
point - max
} else {
0.0
}
}