Catmull Clark
In 1978, Ed Catmull (founder of Pixar) and Jim Clark needed a way to render perfectly smooth 3D characters without forcing artists to manage millions of polygons. They invented a way to mathematically "smooth" a blocky, low-poly cage into a perfect curve.
Corner Cutting:
1# Chaikin's Curve Algorithm (1D Subdivision)2def subdivide_line(points):3 """Cut off the corners to make it smooth."""45 new_points = []67 for i in range(len(points) - 1):8 p1 = points[i]9 p2 = points[i+1]1011 # Instead of 1 line, make 2 new points at 25% and 75%12 q = p1 * 0.75 + p2 * 0.2513 r = p1 * 0.25 + p2 * 0.751415 new_points.extend([q, r])1617 return new_points
To subdivide a 3D mesh, the Catmull-Clark algorithm runs exactly 4 steps. The very first step is to find the absolute center of every single polygon face in the object.
Centroid Averaging:
1# Step 1: The Face Point2def calculate_face_points(mesh):3 """The average of all vertices making up the face."""45 face_points = []67 for face in mesh.faces:89 # Add up all the X, Y, Z coordinates10 total_x = sum([v.x for v in face.vertices])11 total_y = sum([v.y for v in face.vertices])12 total_z = sum([v.z for v in face.vertices])1314 # Divide by how many vertices there are (usually 4 for a Quad)15 count = len(face.vertices)1617 face_point = Point(total_x/count, total_y/count, total_z/count)18 face_points.append(face_point)1920 return face_points
Step 2 of the Catmull-Clark algorithm creates a new point in the middle of every single Edge. But it doesn't just split the edge blindly; it uses the Face Points we just calculated to ensure the surface curves smoothly.
The 4-Point Average:
1# Step 2: The Edge Point2def calculate_edge_points(edge, connected_faces):3 """Averages the edge itself AND the faces touching it."""45 # 1. The two vertices making up the original edge6 v1 = edge.vertex_A7 v2 = edge.vertex_B89 # 2. The Face Points of the two polygons sharing this edge10 f1 = connected_faces[0].face_point11 f2 = connected_faces[1].face_point1213 # 3. The Edge Point is the average of all 4!14 total_x = v1.x + v2.x + f1.x + f2.x15 total_y = v1.y + v2.y + f1.y + f2.y16 total_z = v1.z + v2.z + f1.z + f2.z1718 edge_point = Point(total_x/4, total_y/4, total_z/4)19 return edge_point
Step 3 is the most important step in the entire algorithm. We have created new points on the Faces and Edges, but we must also move the original vertices. If we leave the original sharp corners where they are, the mesh won't actually become smooth!
The Barycentric Formula:
F: The average of the 4 Face Points surrounding it. 3. Calculate R: The average of the midpoints of the 4 edges touching it. 4. We combine them using a special weighted average formula. 5. This mathematically "drags" the sharp corner inward toward the center of the mesh. The sharper the corner, the further it gets dragged!1# Step 3: Moving the Original Vertices2def update_vertex_point(vertex, connected_faces, connected_edges):3 """The math that physically shrinks and smooths the mesh."""45 n = len(connected_faces) # e.g., 4 faces meet at this vertex67 # Average of the Face Points touching this vertex8 F = average(connected_faces)910 # Average of the Edge Midpoints touching this vertex11 R = average_midpoints(connected_edges)1213 # The original Vertex position14 P = vertex.position1516 # The Catmull-Clark Barycentric Formula:17 # (F + 2R + (n - 3)P) / n18 new_x = (F.x + 2*R.x + (n-3)*P.x) / n1920 return Point(new_x, new_y, new_z)
We now have a cloud of new points (Face Points, Edge Points) and our original points have been moved. The final step of the algorithm is to draw new lines connecting them all together!
Quad Topology:
1# Step 4: Connecting the New Dots2def split_quad(face, face_point, edge_points, new_vertices):3 """1 Quad becomes 4 smaller Quads."""45 new_quads = []67 # We loop through all 4 corners of the original Quad8 for i in range(4):910 # We build a new Quad using:11 # 1. The moved Original Vertex12 # 2. The new Edge Point on one side13 # 3. The new Face Point in the middle14 # 4. The new Edge Point on the other side1516 v1 = new_vertices[i]17 v2 = edge_points[i]18 v3 = face_point19 v4 = edge_points[(i - 1) % 4]2021 new_quads.append(Quad(v1, v2, v3, v4))2223 return new_quads
If you run Catmull-Clark 1 time, you get a smoother mesh. If you run it 100 times, you get an incredibly smooth mesh, but your computer will run out of RAM because you just generated 10 billion polygons.
Skipping to Infinity:
1# Infinite Smoothing (The Limit Surface)2def get_limit_position(vertex, connected_faces, connected_edges):3 """Where does the point end up after infinite subdivisions?"""45 n = len(connected_faces)67 # Calculate the same F and R averages as before8 F = average(connected_faces)9 R = average_midpoints(connected_edges)10 P = vertex.position1112 # The Limit Formula (Notice the slightly different weights!)13 # (F + 2R + (n^2 - 3)P) / (n^2)14 limit_x = (F.x + 2*R.x + (n*n - 3)*P.x) / (n*n)1516 return Point(limit_x, limit_y, limit_z)
When we run the 4 steps of Catmull-Clark on a standard cube, the sharp 90-degree corners get pulled inward, and the flat faces bulge outward slightly.
Subdivision Levels:
1# The Full Pipeline2def catmull_clark_subdivision(mesh, iterations):3 """How Pixar smooths Woody and Buzz."""45 current_mesh = mesh67 for i in range(iterations):8 # 1. Calculate Face Points9 f_pts = calculate_face_points(current_mesh)1011 # 2. Calculate Edge Points12 e_pts = calculate_edge_points(current_mesh, f_pts)1314 # 3. Update Original Vertices15 new_v = update_vertex_points(current_mesh, f_pts, e_pts)1617 # 4. Split Quads and reconnect18 current_mesh = split_all_quads(current_mesh, f_pts, e_pts, new_v)1920 return current_mesh