Voronoi Diagrams
Imagine dropping several different colored dye drops onto a piece of paper. The dye spreads out evenly in all directions. Where two colors meet, they form a straight boundary line. When all the dyes stop spreading, you have a Voronoi Diagram.
Territories of Influence:
1# Distance-Based Partitioning2def get_voronoi_cell_for_point(target_point, all_points, resolution=100):3 """A brute-force way to understand Voronoi territories."""45 territory_pixels = []67 # Check every pixel on the screen8 for x in range(resolution):9 for y in range(resolution):10 pixel = Point(x, y)1112 # Find the closest "Seed" point to this pixel13 closest = find_closest_point(pixel, all_points)1415 # If the closest seed is OUR target point, this pixel belongs to us!16 if closest == target_point:17 territory_pixels.append(pixel)1819 return territory_pixels
Calculating "dye spreading" or pixel-by-pixel brute force is far too slow for computers. Instead, we use a math trick: The Voronoi Diagram is the mathematical Dual of the Delaunay Triangulation.
The Duality Trick:
1# Dual Transformation2def extract_voronoi_from_delaunay(delaunay_mesh):3 """The fast way to generate Voronoi."""45 voronoi_vertices = []67 # Every triangle circumcenter becomes a Voronoi Vertex!8 for triangle in delaunay_mesh.triangles:9 circumcenter = triangle.get_circumcenter()10 voronoi_vertices.append(circumcenter)1112 return voronoi_vertices
There is a problem with the Duality Trick. The Delaunay triangles on the outer edge of the point cloud have Voronoi boundaries that shoot off into infinity!
Clipping to Reality:
1# Infinite Ray Truncation2def cap_voronoi_rays(voronoi_edges, bounding_box):3 """Clips infinite edges against a bounding box."""45 clipped_edges = []67 for edge in voronoi_edges:8 if edge.is_infinite():9 # Find where the ray intersects the bounding box10 intersection = bounding_box.ray_intersect(edge.origin, edge.direction)1112 # Create a finite edge ending at the boundary13 finite_edge = create_edge(edge.origin, intersection)14 clipped_edges.append(finite_edge)15 else:16 clipped_edges.append(edge)1718 return clipped_edges
At this point in the algorithm, we have a giant list of disorganized line segments. Graphics engines cannot fill lines with color or calculate their area. We need closed Polygons.
Tip-to-Tail Sorting:
(A-B), (D-C), (B-C), (E-A), etc. 4. Write a sorting loop that connects them tip-to-tail: A-B-C-D-E-A. 5. Now you have a perfectly sorted array of vertices. You can finally create a Polygon mesh and fill it!1# Gathering the Polygon2def gather_voronoi_cell(seed_index, voronoi_edges):3 """Sorts disorganized lines into a closed polygon loop."""45 # Get all edges that border this specific seed's territory6 cell_edges = get_edges_for_seed(seed_index, voronoi_edges)78 # They are in random order. We must sort them so they connect tip-to-tail9 sorted_vertices = []1011 current_edge = cell_edges[0]12 sorted_vertices.append(current_edge.start)1314 while len(sorted_vertices) <= len(cell_edges):15 next_vertex = current_edge.end16 sorted_vertices.append(next_vertex)1718 # Find the next edge that starts at 'next_vertex'19 current_edge = find_connected_edge(next_vertex, cell_edges)2021 return sorted_vertices
If you generate a Voronoi diagram from purely random points, it looks messy. Some cells are huge, others are tiny slivers. In nature (like mud cracks, giraffe spots, or bee honeycombs), Voronoi patterns are highly regular. We can simulate this using Lloyd's Relaxation.
Centroid Shifting:
1# Lloyd's Relaxation Algorithm2def relax_voronoi(seeds, iterations=5):3 """Turns a random scattered Voronoi into a beautiful honeycomb."""45 for i in range(iterations):6 # 1. Calculate the Voronoi Diagram7 voronoi_cells = generate_voronoi(seeds)89 new_seeds = []10 for cell in voronoi_cells:11 # 2. Find the Center of Mass (Centroid) of the polygon cell12 centroid = calculate_polygon_centroid(cell.vertices)1314 # 3. Move the Seed to the Centroid!15 new_seeds.append(centroid)1617 seeds = new_seeds1819 return seeds
In 3D graphics (especially shaders and procedural textures), we often don't want hard polygon lines. Instead, we want soft, organic patterns. We achieve this by converting Voronoi logic into a Distance Field.
Smooth Voronoi:
1# Distance Fields2def evaluate_distance_field(pixel, seeds):3 """Calculates color based on distance to the closest seed."""45 # 1. Find the distance to the closest seed6 closest_dist = get_distance_to_closest(pixel, seeds)78 # 2. Map that distance to a color gradient (e.g. 0=White, Far=Black)9 color = map_distance_to_color(closest_dist)1011 return color
In modern rendering, calculating true polygon meshes for Voronoi cells is rarely done on the CPU. Instead, it is calculated per-pixel on the GPU using a Shader. This allows for millions of moving, shifting points to generate biological, alien, or magical glowing effects in real-time.
GPU Cellular Noise:
1# Realtime Voronoi Shader2def fragment_shader(pixel_uv, time):3 """How modern game engines render Voronoi effects instantly."""45 # Generate moving seed points based on Time6 seeds = generate_moving_points(time)78 # Calculate distance to closest seed9 dist = calculate_voronoi_distance(pixel_uv, seeds)1011 # Add a glowing edge effect at the borders12 edge_glow = smoothstep(0.9, 1.0, dist)1314 return edge_glow