How a simulated pool of water creates perfectly tessellating geometric patterns
Imagine a perfectly square pool of water on a sunny day. You drop a stone in, and ripples spread out. When those ripples hit the walls, they bounce back. All these bouncing waves overlap and create a shimmering pattern of bright lines on the bottom of the pool — those wiggly, glowing lines you see in real swimming pools.
Those bright lines are called caustics. They happen because the wavy water surface acts like a bumpy lens, bending sunlight and focusing it into bright curves.
Here's the trick: the way waves bounce off the pool walls is exactly the same math as mirroring a picture. So if you take the pattern from one pool and mirror-flip copies of it next to each other, the edges line up perfectly. No seams. It tiles forever.
Left: waves bouncing in a square pool. Right: the bright lines (caustics) those waves create on the pool floor.
A pool can vibrate in different ways, just like a guitar string can play different notes. Each way of vibrating is called a mode. A simple mode might be one big wave sloshing back and forth. A more complicated mode has many smaller waves.
Each mode has two numbers: (m, n). Think of m as "how many bumps side to side" and n as "how many bumps top to bottom."
Three different modes: (1,0) sloshes left-right, (0,1) sloshes up-down, (1,1) has a checkerboard pattern.
When you click a cell in the mode grid, you're adding that vibration to the pool. You can mix many modes together, like mixing notes to make a chord. Different mixes make different patterns.
The mode grid is an 8×8 table. Each cell represents one possible vibration of the water. The two numbers (m, n) tell you:
How many wave peaks fit side to side. m=1 is one gentle hill. m=4 is four bumps packed in the same space.
How many wave peaks fit top to bottom. Same idea, but in the vertical direction.
Click any cell below to see what that single mode looks like as a wave surface (left) and as the caustic pattern it produces (right):
Click a cell
▲ peaks ▼ valleys
Light pattern on pool floor
Notice the pattern: low numbers make big, simple shapes. High numbers make fine, detailed textures. Here are some intuitions:
The simplest waves — one bump in one direction. They create broad, sweeping caustic lines.
One bump in each direction at once — a single dome in the center. Creates a diamond/circle caustic.
Unequal numbers create elongated patterns — stretched in one direction. With 4-fold symmetry on, both get added together, creating a balanced design.
High numbers create dense, intricate detail. These are the "fine filigree" that adds complexity to a pattern.
Think of each mode like a musical note. One note by itself sounds simple. But combine several notes and you get a chord — something richer and more complex. The same thing happens with water vibrations:
Click cells below to add/remove modes and see how they combine. The left shows individual waves added together, the right shows the resulting caustic pattern:
0 modes selected
▲ peaks ▼ valleys
Light pattern on pool floor
Try this: Click (1,0), then (0,1), then (1,1). Watch how each new mode adds a new layer of detail to the pattern. This is exactly what the presets do — "Ripple" is just those three modes plus a couple more.
How deep the "pool" is. Deeper water means the light has more room to bend, so caustic lines get sharper and more dramatic.
How bright the caustic lines appear. Turn it up to make the lines pop, turn it down for a subtler effect.
Whether the lines are thin and crisp or soft and glowing. High sharpness = thin precise lines. Low = softer halos.
When on, if you add mode (2,3), it also adds (3,2). This guarantees the pattern looks the same if you rotate it 90°, giving it that kaleidoscope quality.
Each wave mode uses cosines: cos(m · x) · cos(n · y). A cosine wave is like a smooth hill that's the same on the left side as the right side — it's symmetric. So if you mirror-flip the tile, the wave at the left edge matches the wave at the right edge of the next tile. It's automatic. No matter what combination of modes you pick, the tiling always works.
A cosine wave (top) is symmetric around its peak. Mirror a tile, and the edges always match (bottom).
The tool computes caustic envelopes of a light field refracted through a parametric water surface, where the surface is constructed as a superposition of eigenmodes of the 2D wave equation on a square domain with Neumann boundary conditions.
For a square pool with side length L and perfectly reflecting (Neumann) walls, the eigenmodes of the wave equation ∇²h = (1/c²) ∂²h/∂t² are:
hmn(x, y, t) = Amn cos(mπx/L) cos(nπy/L) cos(ωmn t)
where ωmn = πc/L · √(m² + n²)
The Neumann condition ∂h/∂n = 0 at the walls is satisfied automatically by the cosine basis. The total surface height is:
h(x, y, t) = Σm,n Amn cos(mπx/L) cos(nπy/L) cos(ωmn t)
The amplitude normalization A_internal = A_slider · 0.15 / (m² + n²) compensates for the fact that higher-order modes have larger second derivatives per unit amplitude, ensuring roughly equal visual contribution to the caustic pattern across modes.
For a planar light source (sun) at infinity, rays passing through a refractive surface with height field h(x,y) are deflected proportionally to the surface gradient. A ray entering at surface position (x, y) lands on the pool floor at:
x' = x + d · ∂h/∂x y' = y + d · ∂h/∂y where d is the effective depth (proportional to pool depth and refraction index)
The caustic intensity is the reciprocal of the Jacobian determinant of this ray mapping (x, y) → (x', y'). Where the Jacobian vanishes, rays converge to a point — infinite intensity in the geometric optics limit — producing a caustic.
J = | ∂x'/∂x ∂x'/∂y | = | 1 + d · hxx d · hxy |
| ∂y'/∂x ∂y'/∂y | | d · hxy 1 + d · hyy |
det(J) = (1 + d · hxx)(1 + d · hyy) - (d · hxy)²
Caustic intensity I = 1 / |det(J)|
The Hessian components are computed analytically from the modal decomposition:
hxx = -Σ Amn m² cos(mx) cos(ny) cos(ωt)
hxy = Σ Amn m·n sin(mx) sin(ny) cos(ωt)
hyy = -Σ Amn n² cos(mx) cos(ny) cos(ωt)
(coordinates rescaled so L = π)
I = 1/|det(J)| - 1, clamped to [0, ∞), so only the caustic peaks remain visible. The "brightness" parameter scales I, and "sharpness" applies a power-law remapping I1/γ where γ = 0.5 + 3s to control line width.
This is the central insight connecting wave physics to tessellation geometry. Neumann boundary conditions on a square domain produce eigenfunctions in the cosine basis, which has even symmetry about each wall. Formally:
h(x, y) = h(-x, y) = h(x, -y) (even reflection at each boundary)
Mirror tiling — reflecting the fundamental domain across its edges — produces exactly the same extension as the even periodic extension of the cosine series. The function and all its derivatives are continuous across tile boundaries, guaranteeing C∞ seamlessness.
This is equivalent to saying the pattern lives in the p4m wallpaper group (when 4-fold symmetry is enabled) or pmm (when it's not). The fundamental domain is one tile; the group generators are reflections across the tile edges.
The entire caustic computation runs in a WebGL 2 fragment shader. Each pixel independently evaluates the modal sums for hxx, hxy, hyy, computes the Jacobian determinant, and maps the result to intensity. This is embarrassingly parallel and runs at 60fps even with 20+ active modes.
// Fragment shader pseudocode for each mode (m, n, A, ω): a = A · 0.15 / (m² + n²) · cos(ω · t) Hxx += -a · m² · cos(mx) · cos(ny) Hxy += a · m·n · sin(mx) · sin(ny) Hyy += -a · n² · cos(mx) · cos(ny) det = (1 + d·Hxx)(1 + d·Hyy) - (d·Hxy)² I = clamp( (1/|det| - 1)1/γ · brightness, 0, 1 )
The shader outputs to a 512×512 tile canvas with preserveDrawingBuffer: true. A separate Canvas 2D context then copies this tile into a 4×4 grid using scale(-1, 1) and scale(1, -1) transforms on alternating tiles to achieve mirror reflection.
When the 4-fold symmetry toggle is active, every mode (m, n) is paired with (n, m) at the same amplitude. Since swapping m and n in cos(mx)cos(ny) is equivalent to a 90° rotation (x ↔ y), this guarantees the surface — and therefore the caustic pattern — is invariant under the dihedral group D4. The resulting tessellation then belongs to the wallpaper group p4m rather than pmm.
Traditional Islamic geometric patterns are constructed from circles and lines within a fundamental domain, then reflected and rotated to fill the plane — the same symmetry operations as our mirror tiling. The caustic lines produced by standing wave superposition are visually analogous to the interlacing lines of girih patterns: both are families of smooth curves governed by the same wallpaper group symmetries. The wave mode grid provides a physical parameterization of these symmetry-constrained line patterns.