AABB Trees
An Axis-Aligned Bounding Box (AABB) Tree is a spatial partitioning tree where every node is bounded by a box aligned with the coordinate axes.
AABBNode(primitives):
1class AABBNode:2 def __init__(self, primitives):3 self.primitives = primitives # List of triangles/points4 self.bbox = self.compute_bounds(primitives)5 self.left = None6 self.right = None78 def compute_bounds(self, primitives):9 min_x = min(p.min_x for p in primitives)10 max_x = max(p.max_x for p in primitives)11 min_y = min(p.min_y for p in primitives)12 max_y = max(p.max_y for p in primitives)13 min_z = min(p.min_z for p in primitives)14 max_z = max(p.max_z for p in primitives)15 return BoundingBox(min_x, max_x, min_y, max_y, min_z, max_z)
To split primitives efficiently, we find the longest coordinate axis of the bounding box and partition the primitives at the midpoint. This yields balanced children bounding boxes.
SplitNode(node, primitives):
1def split_node(node, primitives):2 if len(primitives) <= 1:3 return45 # 1. Determine longest axis of current bounding box6 bbox = node.bbox7 dx = bbox.max_x - bbox.min_x8 dy = bbox.max_y - bbox.min_y9 dz = bbox.max_z - bbox.min_z1011 longest_axis = 0 # X12 if dy > dx and dy > dz:13 longest_axis = 1 # Y14 elif dz > dx and dz > dy:15 longest_axis = 2 # Z1617 # 2. Sort primitives along this axis18 primitives.sort(key=lambda p: p.centroid[longest_axis])19 mid = len(primitives) // 22021 # 3. Create children22 node.left = AABBNode(primitives[:mid])23 node.right = AABBNode(primitives[mid:])
Ray casting is accelerated by walking the AABB tree recursively. If a ray misses a parent bounding box, we prune its entire branch instantly.
Raycast(node, ray):
1def ray_intersects_aabb(ray, bbox) -> bool:2 tmin = (bbox.min_x - ray.origin.x) / ray.direction.x3 tmax = (bbox.max_x - ray.origin.x) / ray.direction.x4 if tmin > tmax: tmin, tmax = tmax, tmin56 for min_v, max_v, orig, dir in [7 (bbox.min_y, bbox.max_y, ray.origin.y, ray.direction.y),8 (bbox.min_z, bbox.max_z, ray.origin.z, ray.direction.z)9 ]:10 t1 = (min_v - orig) / dir11 t2 = (max_v - orig) / dir12 tmin = max(tmin, min(t1, t2))13 tmax = min(tmax, max(t1, t2))1415 return tmin <= tmax and tmax >= 0.01617def query_raycast(node, ray) -> TriangleIntersection:18 if not ray_intersects_aabb(ray, node.bbox):19 return None # Prune subtree2021 if node.is_leaf():22 return find_closest_triangle(ray, node.primitives)2324 left_hit = query_raycast(node.left, ray)25 right_hit = query_raycast(node.right, ray)26 return get_closer_hit(left_hit, right_hit)
AABB-AABB overlap tests are the foundation of BVH collision queries. Using the Separating Axis Theorem (SAT) projection rules, two boxes overlap if and only if they overlap along all three coordinate axes simultaneously.
AABBOverlap(a, b):
1def check_aabb_overlap(a, b) -> bool:2 # Check for separation along all 3 coordinate axes3 for i in range(3):4 if a.max_pt[i] < b.min_pt[i] or b.max_pt[i] < a.min_pt[i]:5 return False # Separating axis found: boxes do not overlap67 # Overlaps along all axes: boxes intersect8 return True