Kleinian
Kleinian limit sets drawn as one ordered closed curve, with a bright head that runs the whole lap on each loop; click for the next preset (kleinianLimitSet, KleinianPreset)
- Run
swift run --package-path Examples Example-Patterns-Kleinian- Source
- Examples/Patterns/Kleinian/Sketch.swift
import Ollin
/// Kleinian limit sets, traced live along their own curve.
///
/// Two complex traces pick a Möbius group; the depth-first walk returns its
/// limit set as one *ordered* closed curve, and this sketch spends the loop
/// proving it: a bright head runs the whole Jordan curve once per lap while
/// the full lace waits underneath. The gasket, the spirals, and the cusps
/// are all the same recipe with different traces. Click or press a key for
/// the next preset.
@main
final class Kleinian: Sketch {
override var loopDuration: Double? { 16 }
private let presets: [(name: String, preset: KleinianPreset)] = [
("the Apollonian gasket", .gasket),
("a spiral pair", .spiralPair),
("lace", .lace),
("the 1/15 double cusp", .doubleCusp),
("a deep cusp", .cusp),
("symmetric cusps", .symmetricCusps),
]
private var index = 2
private var curve: [Vector2] = []
override func setup() {
traceCurve()
}
override func draw() {
background(Color(hex: 0x0B0D12))
guard curve.count > 3 else { return }
// The whole limit set, faint.
noFill()
stroke(Color(hex: 0x3D4A63))
strokeWeight(1.2)
drawPolyline(curve, closed: true)
// The head: a window of the ordered curve, run once per loop.
let head = loopProgress(over: 16) * Double(curve.count)
let span = max(curve.count / 24, 8)
var window: [Vector2] = []
window.reserveCapacity(span)
for i in 0 ..< span {
window.append(curve[(Int(head) + i) % curve.count])
}
stroke(Color(hex: 0xF2D398))
strokeWeight(2.5)
drawPolyline(window)
drawCaption("\(presets[index].name), one connected curve; click or press a key for the next")
}
override func mousePressed() { advance() }
override func keyPressed() { advance() }
private func advance() {
index = (index + 1) % presets.count
traceCurve()
}
private func traceCurve() {
let traced = kleinianLimitSet(presets[index].preset, epsilon: 0.0035)
curve = fitted(traced.points, in: canvasRectangle.inset(by: 110))
}
}