Voronoi
a field of sites drifting on noise, divided into Voronoi cells, evened out with Lloyd relaxation, and colored by a sweeping focal point; the cursor is a live site, so the cells form around the mouse (voronoi/lloyd)
- Run
swift run --package-path Examples Example-Patterns-Voronoi- Source
- Examples/Patterns/Voronoi/Sketch.swift
import Ollin
/// Voronoi tessellation — the "stochastic crystallization" look. A field of
/// sites is partitioned into cells, each the region of the canvas closest to its
/// site. The sites drift on a slow noise flow so the crystal field breathes, and
/// the cursor is a live site too, so the cells crystallize around it as you move
/// the mouse.
///
/// Each cell is an ordinary convex `Shape`, so it fills, strokes, and would
/// export or hatch like anything drawn by hand:
///
/// ```swift
/// for cell in voronoi(sites).cells { fill(color); drawShape(cell) }
/// ```
///
/// The starting sites are evened out with a few rounds of Lloyd relaxation
/// (`lloyd`), which nudges each site to its cell's centroid for a calmer, more
/// organic spacing than raw random scatter.
@main
final class Voronoi_Example: Sketch {
private var sites: [Vector2] = []
override func draw() {
if sites.isEmpty {
seed(7)
let scattered = (0..<150).map { _ in randomVector(in: canvasRectangle) }
sites = lloyd(scattered, iterations: 6)
}
background(Color(white: 0.06))
// Drift each site on a slow, bounded noise flow.
var live = sites.map { s -> Vector2 in
let dx = signedNoise(s.x * 0.0015, s.y * 0.0015, time * 0.12)
let dy = signedNoise(s.y * 0.0015, s.x * 0.0015, time * 0.12 + 17)
return s + Vector2(dx, dy) * 30
}
// A live site under the cursor, so cells crystallize around the mouse.
let cursor = mouse
if canvasRectangle.contains(cursor) { live.append(cursor) }
// A focal point sweeping the field gives the colors a living radial flow.
let focus = canvasRectangle.center
+ Vector2(cos(time * 0.3), sin(time * 0.4)) * (width * 0.32)
let maxDistance = Vector2(width, height).length * 0.5
for cell in voronoi(live).cells {
guard let center = cell.contours.first?.points.centroid else { continue }
let t = min(center.distance(to: focus) / maxDistance, 1)
fill(Colormap.turbo.color(at: t))
stroke(Color(white: 0.06)); strokeWeight(1.5 * scale)
drawShape(cell)
}
// The sites themselves, as faint dots.
noStroke(); fill(Color(white: 0.95, alpha: 0.45))
drawPoints(live, size: 3 * scale)
}
}