Spatial Grids
Defining the Boundaries:
1# Calculating the Grid Extents2import math34def create_grid(points, cell_size):5 """Defines the boundaries of our voxel grid."""67 # 1. Find the Bounding Box of the entire point cloud8 min_x = min([p.x for p in points])9 max_x = max([p.x for p in points])10 min_y = min([p.y for p in points])11 max_y = max([p.y for p in points])12 min_z = min([p.z for p in points])13 max_z = max([p.z for p in points])1415 # 2. Divide by the cell size to find out how many16 # rows, columns, and layers we need!17 count_x = math.ceil((max_x - min_x) / cell_size)18 count_y = math.ceil((max_y - min_y) / cell_size)19 count_z = math.ceil((max_z - min_z) / cell_size)2021 return min_x, min_y, min_z, count_x, count_y, count_z
In polygon modeling, objects exist anywhere in smooth, continuous space. In Voxelization, we force the world into a rigid, discrete Grid (like Minecraft blocks or a 3D spreadsheet). This is called Discretization.
Flooring to Integers:
5.5 and Cell Size is 2.0, we get 2.75. 3. Apply a mathematical Floor function (drop the decimal). 2.75 becomes integer 2! 4. The point now officially belongs to Column 2 of the grid. Any other point that falls between 4.0 and 5.999 will also be quantized to Column 2.1# Quantizing Floating Point to Integers2def get_cell_coordinates(point, min_corner, cell_size):3 """Converts a float (1.234, 4.567) into an integer cell index (1, 4)."""45 # 1. Shift the point relative to the bottom-left corner of the grid6 local_x = point.x - min_corner.x7 local_y = point.y - min_corner.y8 local_z = point.z - min_corner.z910 # 2. Divide by the size of the cells11 # 3. Floor the result! (Chop off all decimal places)12 grid_x = math.floor(local_x / cell_size)13 grid_y = math.floor(local_y / cell_size)14 grid_z = math.floor(local_z / cell_size)1516 return grid_x, grid_y, grid_z
A standard 3D point has floating-point coordinates like X: 1.2345, Y: -4.9876. These numbers are infinitely precise. Grids, however, are made of solid, countable blocks. We must convert these floats into integers. This is called Quantization.
Flattening Space:
count_x * count_y). 3. We multiply that by our grid_z to skip all the layers below us. 4. We calculate how many boxes are in a single row (count_x) and multiply by our grid_y to skip all rows below us on this layer. 5. Finally, we add our grid_x position. 6. We have converted (2, 4, 3) into a single ID number like 342.1# Flat Array Indexing2def get_hash_key(grid_x, grid_y, grid_z, count_x, count_y):3 """Turns a 3D index into a single 1D array index."""45 # Imagine a bookshelf:6 # 1. Skip over all the completed 'shelves' below you (grid_z)7 # 2. Skip over all the completed 'rows' on your shelf (grid_y)8 # 3. Add your position in the current row (grid_x)910 index = grid_x + (grid_y * count_x) + (grid_z * count_x * count_y)1112 return index
Computer memory (RAM) is not 3D. RAM is a single, massive, 1-dimensional line of data. We cannot ask the computer to "store this in box 2, 4, 3". We have to convert our 3D grid coordinate into a 1D Index.
Filling the Buckets:
1# Creating the Storage Array2def insert_points_into_grid(points, total_cell_count):3 """Sorts all points into their respective boxes."""45 # 1. Create a giant empty list of lists (Buckets)6 grid_buckets = [ [] for _ in range(total_cell_count) ]78 # 2. Put every point into a bucket based on its Hash ID9 for pt in points:10 # Get coordinates11 gx, gy, gz = get_cell_coordinates(pt)1213 # Get ID14 bucket_id = get_hash_key(gx, gy, gz)1516 # Add the point to the bucket17 grid_buckets[bucket_id].append(pt)1819 return grid_buckets
Now that every possible 3D box has a unique 1D ID number, we create a giant List in the computer's memory. The List has exactly as many slots as there are boxes. Each slot is an empty list, waiting to act as a Bucket.
Skipping 99% of the Work:
1# Extremely Fast Searching2def find_points_in_radius(query_pt, radius, grid_buckets, cell_size):3 """How to search 1 million points without lagging."""45 found_points = []67 # 1. Which box is our query point in?8 qx, qy, qz = get_cell_coordinates(query_pt)910 # 2. How many boxes do we need to check? (Radius / CellSize)11 box_range = math.ceil(radius / cell_size)1213 # 3. Only look inside the nearby boxes!14 for x in range(qx - box_range, qx + box_range + 1):15 for y in range(qy - box_range, qy + box_range + 1):16 for z in range(qz - box_range, qz + box_range + 1):1718 bucket_id = get_hash_key(x, y, z)19 points_to_check = grid_buckets[bucket_id]2021 # Check actual distance (Math.sqrt) only for these few points22 for pt in points_to_check:23 if distance(query_pt, pt) <= radius:24 found_points.append(pt)2526 return found_points
Why did we do all of this math just to put points in boxes? Speed.If you have a 1-Million point cloud, and you want to find all points within a 2-meter radius of your mouse cursor, checking every single point would require 1 million distance calculations per frame. Your computer would freeze.
Thresholding:
1# Calculating Grid Density2def calculate_voxel_density(grid_buckets):3 """How full is each box?"""45 dense_voxels = []67 for bucket in grid_buckets:8 # Check how many points fell into this box9 point_count = len(bucket.points)1011 # If it has more than 5 points, we consider it "Solid"12 if point_count > 5:13 dense_voxels.append(bucket)1415 return dense_voxels
Spatial Grids are not just for searching. They are the first step in converting a Point Cloud (like LiDAR scan data) back into a solid 3D mesh. To do this, we need to know which parts of the grid are "Solid" and which are "Empty Air".
Volumetric Data:
1# Point Cloud Voxelization2def voxelize_bunny(bunny_points, resolution):3 """Voxelizes a 3D scan of the Stanford Bunny."""45 # 1. Create the grid based on the Bunny's bounding box6 grid = create_grid(bunny_points, resolution)78 # 2. Toss all the points into the buckets9 buckets = insert_points(bunny_points, grid)1011 # 3. Find the dense boxes12 solid_voxels = calculate_voxel_density(buckets)1314 return solid_voxels
When we combine Discretization, Quantization, Hash Mapping, and Density Thresholding, we get a completeVoxelization pipeline. We can take the Stanford Bunny scan and turn it into an organized volumetric block structure.
Related Concepts
2D Voronoi skeletons, 3D Medial Axis Transforms (MAT), and 3D voxel grid thinning rules.
Tangents, Normals, and Curvatures.
Parametric cubic Bézier curves, Bernstein basis derivatives, unit tangent vectors, and arc-length speed profiles.