Source code for simvx.core.math.aabb

"""AABB — 3D axis-aligned bounding box."""

from __future__ import annotations

from .types import Vec3

# ============================================================================
# AABB
# ============================================================================

[docs] class AABB: """3D axis-aligned bounding box.""" __slots__ = ("x", "y", "z", "sx", "sy", "sz") def __init__(self, x=0.0, y=0.0, z=0.0, sx=0.0, sy=0.0, sz=0.0): self.x = float(x) self.y = float(y) self.z = float(z) self.sx = float(sx) self.sy = float(sy) self.sz = float(sz) @property def position(self) -> tuple[float, float, float]: return (self.x, self.y, self.z) @property def size(self) -> tuple[float, float, float]: return (self.sx, self.sy, self.sz) @property def end(self) -> tuple[float, float, float]: return (self.x + self.sx, self.y + self.sy, self.z + self.sz) @property def volume(self) -> float: return abs(self.sx * self.sy * self.sz) @property def center(self) -> tuple[float, float, float]: return (self.x + self.sx / 2, self.y + self.sy / 2, self.z + self.sz / 2) def _normalized(self) -> AABB: """Internal: return copy with positive sizes.""" x, y, z, sx, sy, sz = self.x, self.y, self.z, self.sx, self.sy, self.sz if sx < 0: x, sx = x + sx, -sx if sy < 0: y, sy = y + sy, -sy if sz < 0: z, sz = z + sz, -sz return AABB(x, y, z, sx, sy, sz)
[docs] def contains_point(self, x: float, y: float, z: float) -> bool: """Return True if (x, y, z) lies inside (or on the boundary of) this AABB.""" a = self._normalized() return a.x <= x <= a.x + a.sx and a.y <= y <= a.y + a.sy and a.z <= z <= a.z + a.sz
[docs] def intersects(self, other: AABB) -> bool: """Return True if this AABB overlaps with other.""" a, b = self._normalized(), other._normalized() return not ( a.x + a.sx < b.x or b.x + b.sx < a.x or a.y + a.sy < b.y or b.y + b.sy < a.y or a.z + a.sz < b.z or b.z + b.sz < a.z )
[docs] def intersection(self, other: AABB) -> AABB: """Return the overlapping volume, or a zero AABB if none.""" a, b = self._normalized(), other._normalized() x1 = max(a.x, b.x) y1 = max(a.y, b.y) z1 = max(a.z, b.z) x2 = min(a.x + a.sx, b.x + b.sx) y2 = min(a.y + a.sy, b.y + b.sy) z2 = min(a.z + a.sz, b.z + b.sz) if x2 < x1 or y2 < y1 or z2 < z1: return AABB() return AABB(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1)
[docs] def merge(self, other: AABB) -> AABB: """Return the smallest AABB enclosing both AABBs (union).""" a, b = self._normalized(), other._normalized() x1 = min(a.x, b.x) y1 = min(a.y, b.y) z1 = min(a.z, b.z) x2 = max(a.x + a.sx, b.x + b.sx) y2 = max(a.y + a.sy, b.y + b.sy) z2 = max(a.z + a.sz, b.z + b.sz) return AABB(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1)
[docs] def expand(self, point) -> AABB: """Return an AABB expanded to include the given point.""" if isinstance(point, Vec3): px, py, pz = point.x, point.y, point.z else: px, py, pz = float(point[0]), float(point[1]), float(point[2]) a = self._normalized() x1 = min(a.x, px) y1 = min(a.y, py) z1 = min(a.z, pz) x2 = max(a.x + a.sx, px) y2 = max(a.y + a.sy, py) z2 = max(a.z + a.sz, pz) return AABB(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1)
[docs] def grow(self, amount: float) -> AABB: """Return an AABB grown by amount on each side.""" return AABB( self.x - amount, self.y - amount, self.z - amount, self.sx + 2 * amount, self.sy + 2 * amount, self.sz + 2 * amount, )
[docs] def __eq__(self, other): if isinstance(other, AABB): return ( self.x == other.x and self.y == other.y and self.z == other.z and self.sx == other.sx and self.sy == other.sy and self.sz == other.sz ) return NotImplemented
[docs] def __repr__(self): return f"AABB({self.x:.4g}, {self.y:.4g}, {self.z:.4g}, {self.sx:.4g}, {self.sy:.4g}, {self.sz:.4g})"