Thurston's discrete Riemann map: a live circle packing warps a picture into your blob.
The blob is filled with a hexagonal lattice of equal circles, and only the pattern of which circles touch is kept. Collins and Stephenson's iteration then finds new radii for the same pattern so it packs a rectangle: every interior circle's neighbors must wrap exactly 2 pi around it, boundary circles pi and the four corner circles pi / 2, and each circle is resized in closed form as if its neighbors were all equal until every angle sum is right. Laying the triangles out from those radii gives the packing on the left, and mapping each triangle of centers onto its twin in the blob is a discrete conformal map that converges to the Riemann map (Rodin and Sullivan). A tiled picture rides through it, so its squares stay square and its dots stay round in the small while the whole picture bends to fit, and the corners are nudged until the rectangle comes out square.
Try it. Drag the round handles to reshape the blob and watch the radii re-solve. C toggles circles, P the picture, + and - change the density, R hands the shape back to the autopilot.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a live circle packing solver that maps a picture conformally into a shape, with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Draw a blob as a closed polygon through a few points around the middle of the screen. Fill it with a hexagonal lattice: keep the lattice points inside the blob and every lattice triangle whose three corners are kept.
- Give every point a radius of 1 and an ordered list of its triangles. A point is on the boundary if its triangles do not close up into a full fan.
- For each point, add up the angle at its center in each triangle of touching circles: with radii r, a, b the angle is acos(1 - 2ab / ((r + a)(r + b))). Interior points want 2 pi, boundary points want pi, and four corner points you pick want pi / 2.
- Collins-Stephenson update: with k triangles and angle sum theta, set beta = sin(theta / 2k), delta = sin(target / 2k), rhat = beta r / (1 - beta), and the new radius is rhat (1 - delta) / delta. Sweep over all points many times, rescaling so the radii do not drift.
- Lay the circles out: place one triangle, then keep placing the third corner of any triangle with two placed corners using the law of cosines. Draw the circles. They should kiss and fill a rectangle.
Once that works, make it beautiful:
- Draw a checkerboard with a dot in each square in the rectangle, then fill the blob by mapping each lattice triangle to its packed triangle with barycentric coordinates. Squares stay square in the small: it is a conformal map.
- Let the mouse drag the blob's points and re-solve every frame, starting from the previous radii.
- Color the packed circles by size.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as packing into a disk with hyperbolic radii, choosing the corners so the rectangle is square, or mapping a photo.