The Octree
An octree is a tree data structure in which each internal node has exactly eight children. It is most commonly used to partition a three-dimensional space by recursively subdividing it into eight octants.
Octree Node:
1# Octree Node structure2class OctreeNode:3 def __init__(self, bounds, capacity=8):4 self.bounds = bounds # AABB boundary (min_pt, max_pt)5 self.capacity = capacity # Maximum points before splitting6 self.points = [] # Points in this node7 self.children = None # Array of 8 child nodes (if split)8 self.is_leaf = True
A parent node partitions its bounding box into eight child nodes by bisecting the volume along three orthogonal planes.
Sub-Octant Partitioning:
1# Divide bounding box into 8 sub-octants2def get_octant_bounds(min_pt, max_pt, index):3 mid = [(a + b) / 2 for a, b in zip(min_pt, max_pt)]45 # Binary representation of index determines sub-box bounds6 x_min = mid[0] if (index & 1) else min_pt[0]7 x_max = max_pt[0] if (index & 1) else mid[0]89 y_min = mid[1] if (index & 2) else min_pt[1]10 y_max = max_pt[1] if (index & 2) else mid[1]1112 z_min = mid[2] if (index & 4) else min_pt[2]13 z_max = max_pt[2] if (index & 4) else mid[2]1415 return (x_min, y_min, z_min), (x_max, y_max, z_max)
Points are added to leaf nodes. When a node exceeds capacity limits, it subdivides and moves its points to child sub-octants.
Subdivision Rules:
1# Point insertion and node subdivision2def insert_point(node, point):3 if not node.is_inside(point):4 return False56 if node.is_leaf:7 if len(node.points) < node.capacity:8 node.points.append(point)9 return True10 node.subdivide() # Split into 8 children1112 # Re-insert existing and new points to child octants13 for p in node.points + [point]:14 for child in node.children:15 if child.insert_point(p):16 break17 node.points = []18 node.is_leaf = False19 return True
To perform spatial point queries, we traverse children sorted by distance to the target, pruning nodes whose bounding volumes are further than the current best distance.
Nearest Point Search:
1# Distance-based octant traversal2def query_closest_point(node, target_pt, best_pt=None, best_dist=float('inf')):3 if node is None:4 return best_pt, best_dist56 # Check distance to node bounding box AABB7 if distance_to_box(target_pt, node.bounds) >= best_dist:8 return best_pt, best_dist # Prune910 if node.is_leaf:11 for p in node.points:12 d = sq_distance(p, target_pt)13 if d < best_dist:14 best_dist = d15 best_pt = p16 return best_pt, best_dist1718 # Sort children based on proximity to target19 sorted_children = sorted(node.children, key=lambda c: distance_to_box(target_pt, c.bounds))20 for child in sorted_children:21 best_pt, best_dist = query_closest_point(child, target_pt, best_pt, best_dist)2223 return best_pt, best_dist
To optimize rendering pipelines, spatial nodes that fall outside the camera's view frustum are quickly discarded, focusing resources only on visible zones.
Frustum Culling:
1# Select octree nodes intersecting frustum planes2def frustum_cull_octree(node, frustum, visible_nodes):3 # Check node AABB intersection with 6 frustum planes4 intersection = check_aabb_frustum(node.bounds, frustum)56 if intersection == OUTSIDE:7 return89 if intersection == INSIDE:10 # Fully inside, add all descendents without further testing11 visible_nodes.extend(node.get_all_leaves())12 return1314 # Overlaps boundary, recurse children15 if node.is_leaf:16 visible_nodes.append(node)17 else:18 for child in node.children:19 frustum_cull_octree(child, frustum, visible_nodes)
Ray tracing applications walk along the ray direction, testing parametric line intersections against node AABB boundary slabs to locate intersecting leaf cells.
Ray Traversal:
1# Slab-method Ray-AABB intersection traversal2def ray_octree_intersect(node, ray_origin, ray_dir):3 t_min, t_max = intersect_slabs(node.bounds, ray_origin, ray_dir)45 if t_max < 0 or t_min > t_max:6 return [] # No intersection78 if node.is_leaf:9 return [node] # Intersects leaf node1011 # Traverse child nodes that overlap the ray path interval [t_min, t_max]12 intersections = []13 for child in node.children:14 intersections.extend(ray_octree_intersect(child, ray_origin, ray_dir))1516 return intersections
An octree can act as a multi-resolution voxelizer. By rendering the bounding volumes of occupied leaf nodes, we construct a discrete volumetric representation of continuous shape models.
Voxel Extraction:
1# Convert octree leaf nodes to voxel grid2def get_octree_voxels(node, resolution):3 voxels = []45 def traverse(n):6 if n.is_leaf:7 if n.points: # Only export occupied cells8 center = n.bounds.center()9 voxels.append((center, n.bounds.size()))10 return11 for child in n.children:12 traverse(child)1314 traverse(node)15 return voxels
Clash detection processes intersect two octrees to locate overlapping regions. By testing bounding boxes hierarchically, we discard non-overlapping branches and isolate intersections efficiently.
Clash Detection:
1# Intersect two Octrees recursively to detect overlaps2def intersect_octrees(nodeA, nodeB, overlaps):3 if not boxes_overlap(nodeA.bounds, nodeB.bounds):4 return56 if nodeA.is_leaf and nodeB.is_leaf:7 if len(nodeA.points) > 0 and len(nodeB.points) > 0:8 overlaps.append((nodeA.bounds, nodeB.bounds))9 return1011 # Traverse children of overlapping nodes12 for i in range(8):13 childA = nodeA.children[i] if not nodeA.is_leaf else nodeA14 childB = nodeB.children[i] if not nodeB.is_leaf else nodeB15 intersect_octrees(childA, childB, overlaps)