Ollin

Kaleidoscope

one drawn arm copied into a sixteen-fold mandala by symmetry(8, mirrored: true), so every draw call is repeated through the mirrors

Run
swift run --package-path Examples Example-Patterns-Kaleidoscope
Source
Examples/Patterns/Kaleidoscope/Sketch.swift
import Ollin

/// A kaleidoscope mandala from one wedge of drawing. `symmetry(8, mirrored:
/// true)` replicates every draw call that follows into sixteen copies (eight
/// rotations, each with its mirror), so the sketch draws a single arm: a chain
/// of breathing dots, a swaying ribbon, and an orbiting ring, and the folds
/// complete the picture. The center medallion is drawn after `noSymmetry()`,
/// so it lands once.
///
/// All motion is phase-driven over one declared `loopDuration`, so
/// `--export-loop` writes a seamless lap.
@main
final class Kaleidoscope: Sketch {
    private let period = 8.0
    override var loopDuration: Double? { period }

    override func draw() {
        background(Color(hex: 0x0B0E14))
        let lap = loopProgress(over: period) * .tau

        translate(center)
        symmetry(8, mirrored: true)

        let teal = Color(hex: 0x2EC4B6)
        let ember = Color(hex: 0xF6511D)
        let cream = Color(hex: 0xFFF3D6)

        // A chain of dots along the arm, each breathing on its own phase; the
        // mirror doubles it into a petal outline.
        noStroke()
        for i in 0..<9 {
            let f = Double(i) / 8
            let radius = (90 + f * 380) * scale
            let swing = 0.34 - f * 0.16 + sin(lap + f * .tau) * 0.10
            fill(Color.mix(teal, ember, f, in: .oklch))
            drawCircle(cos(swing) * radius, sin(swing) * radius, (30 - f * 22) * scale)
        }

        // A ribbon swaying from near the center out to the rim (its phases are
        // offset so it's never straight).
        noFill()
        stroke(cream.withAlpha(0.85))
        strokeWeight(4 * scale)
        let reach = 470 * scale
        let bow = sin(lap + 2.1) * 130 * scale
        drawBezier(Vector2(70 * scale, 0),
                   Vector2(reach * 0.55, bow),
                   Vector2(reach, sin(lap * 2 + 1.3) * 40 * scale))

        // A small orbiting ring, melting hue against its neighbors.
        stroke(Color.mix(ember, teal, (sin(lap) + 1) / 2, in: .oklch))
        strokeWeight(3 * scale)
        let orbit = (250 + cos(lap) * 60) * scale
        drawCircle(cos(0.55) * orbit, sin(0.55) * orbit, 26 * scale)

        // The medallion draws once: symmetry off, back at the center.
        noSymmetry()
        fill(cream)
        noStroke()
        drawCircle(0, 0, 26 * scale)
        noFill()
        stroke(cream.withAlpha(0.6))
        strokeWeight(3 * scale)
        drawCircle(0, 0, 44 * scale)
    }
}

↑↓ move ↩ open