Streamlines
evenly spaced streamlines through a noise flow field; each line stops when it comes close to one already drawn, so they fan into smooth currents that never cross (FlowField.streamlines)
- Run
swift run --package-path Examples Example-Patterns-Streamlines- Source
- Examples/Patterns/Streamlines/Sketch.swift
import Ollin
/// Streamlines through a flow field: a noise field gives a direction at every
/// point, and each line follows that flow. They're traced *evenly spaced* (a
/// line stops when it nears one already drawn), so they fan out into smooth,
/// non-crossing curves, the flow-field look.
///
/// The blue-noise seed set and the field are a pure function of the seed, so the
/// lines are computed once and held; what moves is a hue that drifts along the
/// field over time. Each streamline is an ordinary point list, so the same
/// line-work feeds stroking, the shape booleans, hatching, and SVG export for the
/// pen plotter.
@main
final class Streamlines: Sketch {
private var lines: [[Vector2]] = []
override func draw() {
if lines.isEmpty {
seed(7)
let field = flowField(scale: 0.0016)
let seeds = poissonDisk(radius: 10 * scale)
lines = field.streamlines(from: seeds, stepLength: 4 * scale, steps: 260,
bounds: bounds, separation: 14 * scale)
}
background(Color(hex: 0x0F1117))
strokeCap(.round)
strokeJoin(.round)
noFill()
for line in lines {
let mid = line[line.count / 2]
// A per-line value from the field's own noise sets the width and hue,
// so neighboring lines vary together; time drifts the hue along it.
let v = noise(mid.x * 0.0011, mid.y * 0.0011)
strokeWeight((2 + v * 5.5) * scale)
let hue = (0.52 + v * 0.34 + time * 0.02).truncatingRemainder(dividingBy: 1)
stroke(Color(hue: hue, saturation: 0.5, brightness: 0.96))
drawPolyline(line)
}
}
}