The Box
Axis Aligned Bounding Box
In computational geometry, a Box is one of the simplest ways to represent the volume occupied by an object. It becomes most useful when we are not interested in the complex geometry of the object, but we only need to know its extent. In these cases, it is a 3D rectangular prism whose faces are perfectly aligned with the X, Y, and Z axes.
The Quick Filter: Before a computer does expensive math, it first checks: "Does the light even hit the Box enclosing the mesh?" If the answer is No, the computer skips the expensive math entirely.
Data Structure
A Box is incredibly memory-efficient. You do not need to store 8 vertices or 6 faces. Mathematically, it is defined entirely by just two data points. There are two common ways to store this in memory:
1class Box_MinMax:2 # Stored as two opposite corners3 def __init__(self, min_pt: Point3d, max_pt: Point3d):4 self.Min = min_pt5 self.Max = max_pt67class Box_CenterExtents:8 # Stored as center point and half-size vector9 def __init__(self, center: Point3d, extents: Vector3d):10 self.Center = center11 self.Extents = extents
Diagonal / Center:
(Min + Max) / 2. These are the most commonly used properties in parametric scripts for sizing, spacing, and centering geometry.1def GetBoxExtents(box: BoundingBox) -> tuple:2 """Returns the width, height, and depth of the bounding box."""34 diagonal = box.Diagonal # Vector3d from Min to Max56 width = diagonal.X # Extent in X7 height = diagonal.Y # Extent in Y8 depth = diagonal.Z # Extent in Z9 center = box.Center # Midpoint between Min and Max1011 return (width, height, depth, center)
Box Metrics:
1def SurfaceArea(box: BoundingBox) -> float:2 dx = box.Max.X - box.Min.X3 dy = box.Max.Y - box.Min.Y4 dz = box.Max.Z - box.Min.Z5 return 2.0 * (dx*dy + dy*dz + dx*dz)67def Volume(box: BoundingBox) -> float:8 dx = box.Max.X - box.Min.X9 dy = box.Max.Y - box.Min.Y10 dz = box.Max.Z - box.Min.Z11 return dx * dy * dz
BoxCorners:
1def BoxCorners(bbox: 'BoundingBox') -> list['Point3d']:2 """Returns the 8 corner points of the bounding box in canonical order."""34 # Bottom face (Z = Min.Z), Counter-Clockwise from (Min.X, Min.Y)5 c0 = Point3d(bbox.Min.X, bbox.Min.Y, bbox.Min.Z)6 c1 = Point3d(bbox.Max.X, bbox.Min.Y, bbox.Min.Z)7 c2 = Point3d(bbox.Max.X, bbox.Max.Y, bbox.Min.Z)8 c3 = Point3d(bbox.Min.X, bbox.Max.Y, bbox.Min.Z)910 # Top face (Z = Max.Z), Counter-Clockwise from (Min.X, Min.Y)11 c4 = Point3d(bbox.Min.X, bbox.Min.Y, bbox.Max.Z)12 c5 = Point3d(bbox.Max.X, bbox.Min.Y, bbox.Max.Z)13 c6 = Point3d(bbox.Max.X, bbox.Max.Y, bbox.Max.Z)14 c7 = Point3d(bbox.Min.X, bbox.Max.Y, bbox.Max.Z)1516 return [c0, c1, c2, c3, c4, c5, c6, c7]
The `GetCorners()` method generates these 8 points on demand. They are always returned in a specific order: the bottom 4 points first (counter-clockwise), then the top 4 points.
Inflate(dist):
1class Box:2 def Inflate(self, distance: float):3 """Expands the box outward in all directions."""4 self.Min.X -= distance5 self.Min.Y -= distance6 self.Min.Z -= distance7 self.Max.X += distance8 self.Max.Y += distance9 self.Max.Z += distance
Move:
Min and Max points. Because the box is axis-aligned, it remains parallel to the world axes.1def Move(self, vector: 'Vector3d'):2 """Translates the box by moving both Min and Max corners."""3 self.Min += vector4 self.Max += vector
Scale:
1def Scale(self, scale_factor: float):2 """Scales the box relative to its center."""34 # 1. Find the center5 center = self.Center67 # 2. Scale the distances from center to Min/Max8 vector_min = (self.Min - center) * scale_factor9 vector_max = (self.Max - center) * scale_factor1011 # 3. Apply new extents12 self.Min = center + vector_min13 self.Max = center + vector_max
Rotate:
1def RotateZ(self, angle_rad: float):2 """Calculate a new bounding box that encloses the rotated shape."""3 import math4 cos_a = math.cos(angle_rad)5 sin_a = math.sin(angle_rad)67 rotated = []8 for pt in self.GetCorners():9 # Rotate around Z axis10 new_x = pt.X * cos_a - pt.Y * sin_a11 new_y = pt.X * sin_a + pt.Y * cos_a12 rotated.append((new_x, new_y, pt.Z))1314 self.Min.X = min(p[0] for p in rotated)15 self.Min.Y = min(p[1] for p in rotated)16 self.Min.Z = min(p[2] for p in rotated)1718 self.Max.X = max(p[0] for p in rotated)19 self.Max.Y = max(p[1] for p in rotated)20 self.Max.Z = max(p[2] for p in rotated)
Intersection:
1def Intersects(self, other: 'Box') -> bool:2 """The fast AABB overlap test."""3 return (self.Min.X <= other.Max.X and self.Max.X >= other.Min.X) and \4 (self.Min.Y <= other.Max.Y and self.Max.Y >= other.Min.Y) and \5 (self.Min.Z <= other.Max.Z and self.Max.Z >= other.Min.Z)
Intersection(a, b):
1def Intersection(boxA: 'BoundingBox', boxB: 'BoundingBox') -> 'BoundingBox':2 """Checks if two Axis-Aligned Bounding Boxes intersect."""34 # 1. Simple fast check (AABB algorithm)5 if (boxA.Max.X < boxB.Min.X or boxA.Min.X > boxB.Max.X or6 boxA.Max.Y < boxB.Min.Y or boxA.Min.Y > boxB.Max.Y or7 boxA.Max.Z < boxB.Min.Z or boxA.Min.Z > boxB.Max.Z):8 return None # No intersection910 # 2. Compute intersection box11 resMin = Point3d(max(boxA.Min.X, boxB.Min.X),12 max(boxA.Min.Y, boxB.Min.Y),13 max(boxA.Min.Z, boxB.Min.Z))14 resMax = Point3d(min(boxA.Max.X, boxB.Max.X),15 min(boxA.Max.Y, boxB.Max.Y),16 min(boxA.Max.Z, boxB.Max.Z))1718 return BoundingBox(resMin, resMax)
Union(a, b):
1def MergeBoxes(a: 'BoundingBox', b: 'BoundingBox') -> 'BoundingBox':2 """Returns the smallest box that encloses both A and B."""34 # 1. Find the lowest coordinate on each axis5 min_pt = Point3d(6 min(a.Min.X, b.Min.X),7 min(a.Min.Y, b.Min.Y),8 min(a.Min.Z, b.Min.Z)9 )1011 # 2. Find the highest coordinate on each axis12 max_pt = Point3d(13 max(a.Max.X, b.Max.X),14 max(a.Max.Y, b.Max.Y),15 max(a.Max.Z, b.Max.Z)16 )1718 return BoundingBox(min_pt, max_pt)
Encapsulate (Grow):
1def Encapsulate(self, pt: Point3d):2 """Mutates the box by expanding it to include the given point."""34 if pt.X < self.Min.X: self.Min.X = pt.X5 if pt.Y < self.Min.Y: self.Min.Y = pt.Y6 if pt.Z < self.Min.Z: self.Min.Z = pt.Z78 if pt.X > self.Max.X: self.Max.X = pt.X9 if pt.Y > self.Max.Y: self.Max.Y = pt.Y10 if pt.Z > self.Max.Z: self.Max.Z = pt.Z
Encapsulate(vertex) on each one.Contains(Point3d):
1def BoxContains(box: 'BoundingBox', pt: 'Point3d', strict: bool) -> bool:2 """Checks if a point is inside an Axis-Aligned Bounding Box."""34 if strict:5 # Point must be strictly inside (not on boundaries)6 return (box.Min.X < pt.X < box.Max.X and7 box.Min.Y < pt.Y < box.Max.Y and8 box.Min.Z < pt.Z < box.Max.Z)9 else:10 # Point can be on the boundary11 return (box.Min.X <= pt.X <= box.Max.X and12 box.Min.Y <= pt.Y <= box.Max.Y and13 box.Min.Z <= pt.Z <= box.Max.Z)
ClosestPoint:
Min and Max bounds of the box.1def ClosestPoint(box: Box, test_pt: Point3d) -> Point3d:2 """Clamps a point to the axis-aligned bounds."""3 x = max(box.Min.X, min(test_pt.X, box.Max.X))4 y = max(box.Min.Y, min(test_pt.Y, box.Max.Y))5 z = max(box.Min.Z, min(test_pt.Z, box.Max.Z))6 return Point3d(x, y, z)
DistanceTo:
1def DistanceTo(box: BoundingBox, test_pt: Point3d) -> float:2 """Returns the distance from the point to the closest surface of the box."""34 # 1. First find the closest point on the box5 closest = box.ClosestPoint(test_pt)67 # 2. Return distance between test point and closest point8 return test_pt.DistanceTo(closest)
Ray Intersection:
1def RayIntersectsBox(ray_origin: Point3d, ray_dir: Vector3d, box_min: Point3d, box_max: Point3d) -> bool:2 """Uses the Slab method to test if a ray intersects an AABB."""3 t_min = float('-inf')4 t_max = float('inf')56 # Check X axis7 if abs(ray_dir.X) < 1e-6:8 if ray_origin.X < box_min.X or ray_origin.X > box_max.X: return False9 else:10 t1 = (box_min.X - ray_origin.X) / ray_dir.X11 t2 = (box_max.X - ray_origin.X) / ray_dir.X12 t_min = max(t_min, min(t1, t2))13 t_max = min(t_max, max(t1, t2))1415 # Check Y axis16 if abs(ray_dir.Y) < 1e-6:17 if ray_origin.Y < box_min.Y or ray_origin.Y > box_max.Y: return False18 else:19 t1 = (box_min.Y - ray_origin.Y) / ray_dir.Y20 t2 = (box_max.Y - ray_origin.Y) / ray_dir.Y21 t_min = max(t_min, min(t1, t2))22 t_max = min(t_max, max(t1, t2))2324 # Check Z axis25 if abs(ray_dir.Z) < 1e-6:26 if ray_origin.Z < box_min.Z or ray_origin.Z > box_max.Z: return False27 else:28 t1 = (box_min.Z - ray_origin.Z) / ray_dir.Z29 t2 = (box_max.Z - ray_origin.Z) / ray_dir.Z30 t_min = max(t_min, min(t1, t2))31 t_max = min(t_max, max(t1, t2))3233 return t_max >= t_min and t_max >= 0
Plane Intersection:
1def PlaneIntersectsBox(plane_origin: Point3d, plane_normal: Vector3d, box_min: Point3d, box_max: Point3d) -> bool:2 """Checks if a plane intersects an AABB by projecting box extents onto the plane normal."""3 center = Point3d((box_min.X + box_max.X)*0.5, (box_min.Y + box_max.Y)*0.5, (box_min.Z + box_max.Z)*0.5)4 extents = Vector3d((box_max.X - box_min.X)*0.5, (box_max.Y - box_min.Y)*0.5, (box_max.Z - box_min.Z)*0.5)56 # Project the half-diagonal onto the normal7 r = extents.X * abs(plane_normal.X) + extents.Y * abs(plane_normal.Y) + extents.Z * abs(plane_normal.Z)89 # Distance from box center to plane10 s = (center.X - plane_origin.X) * plane_normal.X + (center.Y - plane_origin.Y) * plane_normal.Y + (center.Z - plane_origin.Z) * plane_normal.Z1112 # Intersection occurs if the center is closer to the plane than the projected extents13 return abs(s) <= r
Contains Box:
1def ContainsBox(outer: 'BoundingBox', inner: 'BoundingBox') -> bool:2 """Checks if the outer box completely encapsulates the inner box."""3 return (outer.Min.X <= inner.Min.X and4 outer.Min.Y <= inner.Min.Y and5 outer.Min.Z <= inner.Min.Z and6 outer.Max.X >= inner.Max.X and7 outer.Max.Y >= inner.Max.Y and8 outer.Max.Z >= inner.Max.Z)