How it works

← back to Creating Symmetry

Every picture this app draws comes from one formula and one operation.

The curve

A point in the plane is a complex number. The curve is a finite sum of rotating complex exponentials:

μ(t) = Σk ck · exp(i · fk · t)    t from 0 to 2π

Each term is a point going round a circle: fk is how many times it goes round as t completes one turn, and ck is the radius and starting angle. Adding the terms is the same as mounting each circle on the rim of the one before it — a chain of arms, each spinning at its own rate. Three terms means three arms, and the tip of the last one traces the curve.

That is the whole construction. The frequencies field holds the fk; the coefficients field holds the ck.

Frequencies decide the shape

The integers control how many times each arm turns, so they set the curve's lobe structure. Here is a single copy of the curve, with no rotation applied:

Six curves, one per frequency set, showing different lobe counts
One curve at six frequency sets. Coefficients held at the default 0.3, 0.5, 0.25i.

Reading them: 2, 4, 6 is nearly a plain double loop, because the three arms turn at simple multiples and stay roughly in step. 1, 5, 9 opens into a six-lobed rosette. 1, 7, 13 is the busiest — widely separated frequencies mean the arms rarely align, so the curve wanders further before closing.

A useful rule: the differences between the frequencies matter more than their sizes. 2, 8, 14 and 1, 7, 13 are both spaced by 6 and produce related shapes; 2, 4, 6 is spaced by 2 and is far simpler.

Coefficients decide the proportions

Same frequencies, different coefficients — shown at 12 rotations so the structure stays visible:

Six coefficient sets at the same frequencies
Six coefficient sets, frequencies held at 2, 8, 14.

The magnitude of each coefficient is how long that arm is. Growing the first from 0.3 to 0.6 pushes the curve out until it reaches the center and the central hole closes. Shrinking the second from 0.5 to 0.1 does the reverse. Setting the third to 0 removes an arm entirely, and the curve becomes visibly simpler.

Coefficients may be complex. 0.3+0.3i instead of 0.3 starts that arm at a different angle, which rotates its contribution and shifts where the lobes fall without changing their number.

Rotation makes the symmetry

The curve alone is not symmetric. The app draws n copies, each turned by an equal fraction of a full turn:

copy k = exp(i · 2πk/n) · μ(t)    k = 0 … n−1

That is what rotations sets, and it is the only source of the symmetry — the curve contributes shape, the rotation contributes order.

The same curve drawn with 4, 8, 16, 32, 64 and 128 rotations
The same curve at 4, 8, 16, 32, 64 and 128 rotations.

At 4 you can still see the individual curve and how the copies interleave. By 32 they begin to fuse. At 128, the default, they overlap into what reads as a solid form with bright ridges. Those ridges are not drawn — they are where the density of overlapping lines happens to be highest.

Resolution

resolution is how many values of t are sampled along each curve. Too few and the curve shows as a polygon; past a few thousand it only costs time.

How the colors are chosen

Color is picked once per rotation, not per point — which is why a finished picture reads as bands rather than a smooth gradient. Each of the n copies is drawn in a single color, and the sequence of colors is produced by a small state machine.

A color scheme is a list of color families, each with a cumulative probability. Scheme 3, for instance, is:

0.80 red
0.82 black
0.87 green
0.95 blue
1.00 white

Before drawing each copy the app draws a random number t between 0 and 1 and compares it against those thresholds:

If t falls under the first threshold — 80% of the time in this scheme — the color does not jump. Instead the current color is perturbed slightly: hue, saturation, lightness and alpha each shift by a random amount up to ±5% of their range. The result is a slow drift, neighboring copies differing just enough to separate.

Otherwise it jumps to whichever family the number lands in — black between 0.80 and 0.82, green to 0.87, blue to 0.95, white above that. A jump discards the current color and starts a new run somewhere else in the spectrum. The narrow bands are deliberate: black gets a 2% chance, so it appears as an occasional dark accent rather than a region.

Each family is itself a range rather than a fixed color. Asking for red gives a hue between 349° and 371° (wrapping past 360° into warm pinks), saturation 0.6–0.8, lightness 0.3–0.6 and alpha 0.5–1.0. So two reds are never quite the same, and the translucency varies too — which is what lets overlapping copies build up density instead of hiding one another.

The H/S/L/A fields set the starting color, the one the first copy uses before any drift or jumping has happened.

The practical consequence: the same parameters do not give the same picture twice. Shape is deterministic and color is not, so pressing Generate again on settings you like is worth doing several times.

Acknowledgment

The construction follows Frank A. Farris, Creating Symmetry: The Artful Mathematics of Wallpaper Patterns (Princeton University Press, 2015).