Myriad
8,100 noise-driven circles on the SDF path, thousands of shapes at full speed, and a parameter swaps them for rounded rects (the corner radius sweeps from square to pill, and each rect turns by its own angle)
- Run
swift run --package-path Examples Example-Motion-Myriad- Source
- Examples/Motion/Myriad/Sketch.swift
import Ollin
/// Thousands of shapes at once: the headline for the SDF drawing path. A
/// 90×90 grid (8,100 circles) where each circle's radius and color ripple
/// from a `noise` field drifting with `time`. Every shape is a single
/// instanced quad whose fill and edge are computed analytically in the
/// shader, so per-shape CPU work is one struct write and the whole field
/// stays smooth.
///
/// The "Rounded rects" parameter swaps the circles for a field of rounded rects on
/// the SDF box path. Two more `noise` fields, decorrelated from the first by
/// an offset in the field's third coordinate, drive what a circle does not
/// have: the corner radius sweeps its whole range, so tiles morph from sharp
/// squares to fully rounded pills, and each tile turns by its own angle (a
/// per-instance `rotate`, carried by each quad's own transform).
@main
final class Myriad: Sketch {
let palette = CosinePalette.neon
@Param("Rounded rects") var rects = false
override func setup() {
noStroke()
}
override func draw() {
background(.black)
if rects {
drawRectField()
} else {
drawCircleField()
}
}
/// 8,100 circles, radius and color driven by one noise field.
private func drawCircleField() {
let g = grid(columns: 90, rows: 90)
for dot in g.points {
let n = noise(Double(dot.column) * 0.06, Double(dot.row) * 0.06, time * 0.25)
fill(palette.color(at: n + time * 0.05))
drawCircle(center: dot.position,
radius: map(n, 0, 1, g.cellWidth * 0.05, g.cellWidth * 0.7))
}
}
/// Thousands of rounded rects: size and color from the same kind of
/// field, corner radius and rotation each from a decorrelated one.
private func drawRectField() {
let g = grid(columns: 56, rows: 56)
let t = time * 0.2
for dot in g.points {
let n = noise(Double(dot.column) * 0.08, Double(dot.row) * 0.08, t)
let size = map(n, 0, 1, g.cellWidth * 0.25, g.cellWidth * 0.95)
// A second, decorrelated field drives the corner radius across
// its full range: 0 (sharp square) up to size/2 (a pill/circle).
let rn = noise(Double(dot.column) * 0.08, Double(dot.row) * 0.08, t + 40)
let radius = map(rn, 0, 1, 0, size / 2)
// A third field turns each tile by its own angle.
let angle = map(noise(Double(dot.column) * 0.05, Double(dot.row) * 0.05, t + 80),
0, 1, -0.5, 0.5)
fill(palette.color(at: n + time * 0.05))
withState {
translate(dot.position)
rotate(angle)
drawRect(center: .zero, width: size, height: size, cornerRadius: radius)
}
}
}
}