// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// An axis-aligned box in the plane, given by two opposite corners.
///
/// Broad-phase collision and culling ask "is this thing anywhere near that
/// thing", and a box around each answers it without touching the contents. The
/// corners are `min` and `max` rather than a corner and a size because that is
/// the form the tests read, and because merging two boxes is then two component
/// comparisons.
///
/// Values are immutable.
#derive(Eq)
pub(all) struct Aabb2 {
/// The corner with the smallest coordinate on each axis.
min : Vec2
/// The corner with the largest coordinate on each axis.
max : Vec2
}
///|
pub extend Aabb2 with Eq::{equal, not_equal}
///|
/// The box with the two given corners.
pub fn Aabb2::new(min : Vec2, max : Vec2) -> Aabb2 {
{ min, max, }
}
///|
/// The box with the given centre and half-extents.
pub fn Aabb2::from_center_half_extents(
center : Vec2,
half_extents : Vec2,
) -> Aabb2 {
{ min: center - half_extents, max: center + half_extents, }
}
///|
/// The smallest box around the points, or `None` when there are none.
pub fn Aabb2::from_points(points : FixedArray[Vec2]) -> Aabb2? {
if points.length() == 0 {
return None
}
let mut low = points[0]
let mut high = points[0]
for point in points {
low = low.min(point)
high = high.max(point)
}
Some({ min: low, max: high, })
}
///|
/// A box with nothing in it: the identity for `union`.
///
/// Its corners are the wrong way round on purpose, so that merging anything into
/// it gives that thing back unchanged.
pub fn Aabb2::empty() -> Aabb2 {
let infinity : Float = 3.4028234663852886e38
{ min: { x: infinity, y: infinity, }, max: { x: -infinity, y: -infinity, }, }
}
///|
/// The point in the middle.
pub fn Aabb2::center(self : Aabb2) -> Vec2 {
let half : Float = 0.5
(self.min + self.max).scale(half)
}
///|
/// The full extent along each axis.
pub fn Aabb2::size(self : Aabb2) -> Vec2 {
self.max - self.min
}
///|
/// The half extent along each axis: the distance from the middle to a face.
pub fn Aabb2::extents(self : Aabb2) -> Vec2 {
let half : Float = 0.5
self.size().scale(half)
}
///|
/// The area the box covers, zero when it is empty.
pub fn Aabb2::area(self : Aabb2) -> Float {
let size = self.size()
size.x.max(0.0) * size.y.max(0.0)
}
///|
/// Whether the box holds nothing.
pub fn Aabb2::is_empty(self : Aabb2) -> Bool {
self.min.x > self.max.x || self.min.y > self.max.y
}
///|
/// Whether the point is inside or on the boundary.
pub fn Aabb2::contains_point(self : Aabb2, point : Vec2) -> Bool {
point.x >= self.min.x &&
point.x <= self.max.x &&
point.y >= self.min.y &&
point.y <= self.max.y
}
///|
/// Whether `other` lies entirely inside this box.
pub fn Aabb2::contains(self : Aabb2, other : Aabb2) -> Bool {
other.min.x >= self.min.x &&
other.max.x <= self.max.x &&
other.min.y >= self.min.y &&
other.max.y <= self.max.y
}
///|
/// Whether the two boxes overlap in positive area.
pub fn Aabb2::intersects(self : Aabb2, other : Aabb2) -> Bool {
self.min.x < other.max.x &&
other.min.x < self.max.x &&
self.min.y < other.max.y &&
other.min.y < self.max.y
}
///|
/// The overlapping box, or `None` when they do not overlap.
pub fn Aabb2::intersection(self : Aabb2, other : Aabb2) -> Aabb2? {
let low = self.min.max(other.min)
let high = self.max.min(other.max)
if low.x >= high.x || low.y >= high.y {
None
} else {
Some({ min: low, max: high, })
}
}
///|
/// The smallest box containing both.
pub fn Aabb2::union(self : Aabb2, other : Aabb2) -> Aabb2 {
{ min: self.min.min(other.min), max: self.max.max(other.max), }
}
///|
/// The same box grown by `amount` in every direction.
pub fn Aabb2::expand(self : Aabb2, amount : Float) -> Aabb2 {
let padding : Vec2 = { x: amount, y: amount, }
{ min: self.min - padding, max: self.max + padding, }
}
///|
/// The four corners, starting at `min` and going counter-clockwise.
pub fn Aabb2::corners(self : Aabb2) -> FixedArray[Vec2] {
[
self.min,
{ x: self.max.x, y: self.min.y, },
self.max,
{ x: self.min.x, y: self.max.y, },
]
}
///|
/// The same points as a rectangle.
pub fn Aabb2::to_rect(self : Aabb2) -> Rect {
{ origin: self.min, size: self.size(), }
}
///|
/// Whether the two boxes agree to within `eps`.
pub fn Aabb2::approx(self : Aabb2, other : Aabb2, eps? : Float) -> Bool {
self.min.approx(other.min, eps?) && self.max.approx(other.max, eps?)
}
///|
pub impl Show for Aabb2 with fn output(self, logger) {
logger.write_string("Aabb2[\{self.min}, \{self.max}]")
}
///|
pub extend Aabb2 with Show::{output, to_string}
///|
pub impl Debug for Aabb2 with fn to_repr(self) {
@debug.Repr::ctor("Aabb2", [
(Some("min"), self.min.to_repr()),
(Some("max"), self.max.to_repr()),
])
}
///|
pub extend Aabb2 with Debug::{to_repr}
///|
/// An axis-aligned box in space, given by two opposite corners.
///
/// The same broad-phase role as `Aabb2`, with a third axis: a frustum is tested
/// against one of these per object per frame, so the tests here are written to
/// read the extremes of a plane's distance rather than to visit eight corners.
///
/// Values are immutable.
#derive(Eq)
pub(all) struct Aabb3 {
/// The corner with the smallest coordinate on each axis.
min : Vec3
/// The corner with the largest coordinate on each axis.
max : Vec3
}
///|
pub extend Aabb3 with Eq::{equal, not_equal}
///|
/// The box with the two given corners.
pub fn Aabb3::new(min : Vec3, max : Vec3) -> Aabb3 {
{ min, max, }
}
///|
/// The box with the given centre and half-extents.
pub fn Aabb3::from_center_half_extents(
center : Vec3,
half_extents : Vec3,
) -> Aabb3 {
{ min: center - half_extents, max: center + half_extents, }
}
///|
/// The smallest box around the points, or `None` when there are none.
pub fn Aabb3::from_points(points : FixedArray[Vec3]) -> Aabb3? {
if points.length() == 0 {
return None
}
let mut low = points[0]
let mut high = points[0]
for point in points {
low = low.min(point)
high = high.max(point)
}
Some({ min: low, max: high, })
}
///|
/// A box with nothing in it: the identity for `union`.
pub fn Aabb3::empty() -> Aabb3 {
let infinity : Float = 3.4028234663852886e38
{
min: { x: infinity, y: infinity, z: infinity, },
max: { x: -infinity, y: -infinity, z: -infinity, },
}
}
///|
/// The point in the middle.
pub fn Aabb3::center(self : Aabb3) -> Vec3 {
let half : Float = 0.5
(self.min + self.max).scale(half)
}
///|
/// The full extent along each axis.
pub fn Aabb3::size(self : Aabb3) -> Vec3 {
self.max - self.min
}
///|
/// The half extent along each axis.
pub fn Aabb3::extents(self : Aabb3) -> Vec3 {
let half : Float = 0.5
self.size().scale(half)
}
///|
/// The volume the box encloses, zero when it is empty.
pub fn Aabb3::volume(self : Aabb3) -> Float {
let size = self.size()
size.x.max(0.0) * size.y.max(0.0) * size.z.max(0.0)
}
///|
/// The total area of the six faces, zero when it is empty.
pub fn Aabb3::surface_area(self : Aabb3) -> Float {
let size = self.size()
let twice : Float = 2.0
let x = size.x.max(0.0)
let y = size.y.max(0.0)
let z = size.z.max(0.0)
twice * (x * y + y * z + z * x)
}
///|
/// Whether the box holds nothing.
pub fn Aabb3::is_empty(self : Aabb3) -> Bool {
self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z
}
///|
/// Whether the point is inside or on the boundary.
pub fn Aabb3::contains_point(self : Aabb3, point : Vec3) -> Bool {
point.x >= self.min.x &&
point.x <= self.max.x &&
point.y >= self.min.y &&
point.y <= self.max.y &&
point.z >= self.min.z &&
point.z <= self.max.z
}
///|
/// Whether `other` lies entirely inside this box.
pub fn Aabb3::contains(self : Aabb3, other : Aabb3) -> Bool {
other.min.x >= self.min.x &&
other.max.x <= self.max.x &&
other.min.y >= self.min.y &&
other.max.y <= self.max.y &&
other.min.z >= self.min.z &&
other.max.z <= self.max.z
}
///|
/// Whether the two boxes overlap in positive volume.
pub fn Aabb3::intersects(self : Aabb3, other : Aabb3) -> Bool {
self.min.x < other.max.x &&
other.min.x < self.max.x &&
self.min.y < other.max.y &&
other.min.y < self.max.y &&
self.min.z < other.max.z &&
other.min.z < self.max.z
}
///|
/// The overlapping box, or `None` when they do not overlap.
pub fn Aabb3::intersection(self : Aabb3, other : Aabb3) -> Aabb3? {
let low = self.min.max(other.min)
let high = self.max.min(other.max)
if low.x >= high.x || low.y >= high.y || low.z >= high.z {
None
} else {
Some({ min: low, max: high, })
}
}
///|
/// The smallest box containing both.
pub fn Aabb3::union(self : Aabb3, other : Aabb3) -> Aabb3 {
{ min: self.min.min(other.min), max: self.max.max(other.max), }
}
///|
/// The same box grown by `amount` in every direction.
pub fn Aabb3::expand(self : Aabb3, amount : Float) -> Aabb3 {
let padding : Vec3 = { x: amount, y: amount, z: amount, }
{ min: self.min - padding, max: self.max + padding, }
}
///|
/// The eight corners.
pub fn Aabb3::corners(self : Aabb3) -> FixedArray[Vec3] {
let low = self.min
let high = self.max
[
{ x: low.x, y: low.y, z: low.z, },
{ x: high.x, y: low.y, z: low.z, },
{ x: high.x, y: high.y, z: low.z, },
{ x: low.x, y: high.y, z: low.z, },
{ x: low.x, y: low.y, z: high.z, },
{ x: high.x, y: low.y, z: high.z, },
{ x: high.x, y: high.y, z: high.z, },
{ x: low.x, y: high.y, z: high.z, },
]
}
///|
/// The box around the transformed box.
///
/// A box turned by a rotation is no longer axis-aligned, so the result is the
/// smallest axis-aligned box around the turned one: correct, and looser than it
/// was before the turn.
pub fn Aabb3::transform(self : Aabb3, m : Mat4) -> Aabb3 {
let points = FixedArray::make(8, Vec3::zero())
let corners = self.corners()
for i in 0..<8 {
points[i] = m.transform_point(corners[i])
}
match Aabb3::from_points(points) {
Some(box) => box
None => self
}
}
///|
/// Whether the two boxes agree to within `eps`.
pub fn Aabb3::approx(self : Aabb3, other : Aabb3, eps? : Float) -> Bool {
self.min.approx(other.min, eps?) && self.max.approx(other.max, eps?)
}
///|
pub impl Show for Aabb3 with fn output(self, logger) {
logger.write_string("Aabb3[\{self.min}, \{self.max}]")
}
///|
pub extend Aabb3 with Show::{output, to_string}
///|
pub impl Debug for Aabb3 with fn to_repr(self) {
@debug.Repr::ctor("Aabb3", [
(Some("min"), self.min.to_repr()),
(Some("max"), self.max.to_repr()),
])
}
///|
pub extend Aabb3 with Debug::{to_repr}