Truchet quarter-circles that weave over and under one another through draw-order shuffling.
How the weave works
Tiles are drawn directly to the main canvas, row by row, left to right. For each tile the code:
- Picks a corner —
(0,0), (0,T), (T,0), or (T,T) — and builds a list of paired arcs, one family anchored there and one point-reflected family from the opposite corner. - Shuffles the full arc list with
rShuffle. - Draws each arc twice: once with
borderPaint (dark, full width), then with curvePaint (colored, narrower) on top.
The shuffle is what makes crossing arcs read as over-and-under rather than stacking: whichever arc is drawn later wins the crossing, and the colored layer of the winner covers the dark border of the arc drawn earlier. Shuffle order is random per tile, so a strand passes over at one crossing and under at the next — exactly the behavior of a real woven surface.
A fifth of the field.
Draw-order and the weave illusion
At any crossing, two arcs overlap. The one drawn second has its colored layer painted over the dark border of the first, breaking that border and hiding one strand behind the other. Because the shuffle is random, this alternates across tiles and crossings, and the result reads as a continuous three-dimensional weave rather than a flat layering.
No bitmaps, no compositing, no masking — just stroke order and a dark border.
Two fifths. Look at any crossing: one strand is broken, the other continues — the later draw wins.
Three fifths. Following a single strand across tiles, it passes over at one crossing and under at the next, which is what a real woven surface does.
Four fifths.
The full field.
Five generators
var rForm = new InterleavedSeededRandom("form:" + Seed);
var rScramble = new InterleavedSeededRandom("scramble:" + Seed);
var rScramble2 = new InterleavedSeededRandom("scramble2:" + Seed);
var rShuffle = new InterleavedSeededRandom("shuffle:" + Seed);
var rTwist = new InterleavedSeededRandom("twist:" + Seed);
Five independent streams, each seeded from a string made of a label and the preset’s seed rather than from an integer. That is a deliberate technique: separate concerns get separate streams, so changing how many random values the form consumes cannot shift what the twist gets.
Without it, adding one draw anywhere would reshuffle everything downstream — the usual reason a small change to generative code produces a completely different image.
Five presets
| Preset | Tile | Curves | Structural variation | Seed |
|---|
| Neon Intertwine | 80 | 7 | none (regular) | slartibartfast |
| Gold Lattice | 50 | 9 | none (regular) | slartibartfast |
| Scarlet Tangle | 100 | 7 | Scramble=0.35 | xenomachina |
| Frost Drift | 100 | 5 | Twist=0.45 | permafrost |
| Amber Bloom | 160 | 3 | CurveThickness=1.4 | solstice |
CurveThickness against CurvesPerTile decides whether the weave reads as rope or as lattice, and Scramble breaks the regularity of the strand assignment.
Gallery presentation
The lead gallery image presents Neon Intertwine the way a gallery would — on a wall, behind a mat, inside a frame, with a soft drop shadow. The presentation is the MuseumFrame from SkiaSharp.Extensions: a wrapper that knows nothing about arc geometry, drawn into a plate whose opening keeps the piece’s aspect ratio. The underlying tile generation is identical to the sample renders.
Determinism
All five generators are seeded from labelled strings derived from the preset’s seed, and consumed in a fixed order during tile construction. The draw-order shuffle consumes one stream; the form, scramble, and twist each consume their own. No generator’s sequence is affected by how many values another has consumed.