A walk that lays down a circle at every step, shrinking as it goes and turning away from whatever it would have hit.
A walk, not a pack
Most circle packings are placement algorithms: throw a circle somewhere, test it, keep
it or discard it. This one is a walk. There is a current circle, and the next one is
placed tangent to it in whatever direction the heading currently points:
var headingNew = currentCircle.Heading + angleInc1;
var d = radiusOld + radiusNew + minRad;
var xNew = currentCircle.X + d * (float)Math.Cos(headingNew);
var yNew = currentCircle.Y + d * (float)Math.Sin(headingNew);
Every circle in the drawing is therefore in contact with the one placed before it, and
the composition is a single connected trail rather than a field of independent discs.
A tenth of the circles.
Shrinking, and getting it back
var radiusNew = radiusOld * ratio;
radiusNew = Math.Max(radiusNew, minRad);
The radius multiplies by ratio at every step and is floored at MinimumRadius. So the
trail tapers as it goes, and once it reaches the floor it stays there.
The interesting part is what happens when the walk fails — when the proposed circle is
out of bounds or overlaps something already placed. It does not stop. It turns by a
second, larger angle increment and tries again, and the shrink ratio is adjusted so the
walk can grow back once it is out of the tight spot.
That is why the drawing fills its frame instead of jamming after twenty circles. The
failure path is the algorithm; the success path is just the trail.
A third.
Half. Collision testing is exhaustive — every candidate is compared against every circle
already placed:
foreach (var c in circleArray)
{
if (!add) break;
add &= xd + yd >= (c.Radius + c2.Radius) * (c.Radius + c2.Radius);
}
Squared distances throughout, so no square roots, and an early exit as soon as one
overlap is found. That is quadratic in the circle count and perfectly adequate at this
scale.
Four fifths.
The full wander. The stages above are counted in circles placed, not in steps
taken — the walk keeps proposing and failing long after the frame is full, so the step
budget is a poor measure of how far along the drawing is.
Five renderings
| Rendering | Initial radius | Minimum | Ink |
|---|
| Magenta Packed Circles | 7.0 | 1.73 | magenta on near-black |
| Cyan Packed Dense | 5.0 | 1.73 | cyan on near-black |
| Lime Packed Wide | 9.0 | 1.73 | lime on near-black |
| Amber Packed Coarse | 13.0 | 3.5 | amber on near-black |
| White Packed Fine | 3.0 | 0.6 | off-white on near-black |
The ratio between the initial and minimum radii is what decides the character: a large
ratio gives a trail that tapers dramatically and fills the gaps left by its own early
steps, a small one gives a nearly uniform packing. Amber Packed Coarse sits at one
extreme — the floor is high relative to the start, so circles stay bold throughout and
the trail barely tapers. White Packed Fine sits at the other — the floor is far below
the start, so early circles are large rings and the trail thins to near-dots before it
stops shrinking.
Gallery presentation
The museum-framed rendering presents Magenta Packed Circles as a finished piece: the
600×600 walk is set inside a dark mat with a thin magenta accent rule at the artwork
boundary, surrounded by a narrow outer field. The mat and walk are generated
independently — the framing is a compositing layer that wraps the walk without touching
its parameters.
Determinism
One Random is created per draw, seeded from the preset, and consumed on the failure
path. The success path consumes none of it, so the walk’s randomness is entirely in how
it recovers rather than in where it goes. The generator is System.Random, which the
project’s determinism policy flags as not guaranteed stable across .NET versions.