Marbling
paper marbling in closed form: bull's-eye drops feathered by combs, a stylus pull, and a stirred vortex, with every ink kept as a vector outline; a click drops ink, and a drag pulls the stylus (Marbling, drawMarbling)
- Run
swift run --package-path Examples Example-Patterns-Marbling- Source
- Examples/Patterns/Marbling/Sketch.swift
import Ollin
/// Paper marbling in closed form.
///
/// A `Marbling` bath composes the classic moves: bull's-eyes of alternating
/// ink dropped onto the bath, a comb pulled down through them (the feathered
/// nonpareil), a finer comb pulled back up, and a vortex stirred into one
/// corner. Every operation is an exact point transform, so the finished
/// paper is pure vector geometry:
///
/// ```sh
/// swift run Example-Patterns-Marbling --export-svg /tmp/marbling.svg
/// ```
///
/// Click to drop fresh ink; drag and release to pull a stylus through the
/// bath. Each variation seeds a different paper.
@main
final class Marbling_Example: Sketch {
private let paper = Color(hex: 0xEFE7D6)
private let inks = [Color(hex: 0x1F2A44), Color(hex: 0xA43B2A),
Color(hex: 0xC8912F), Color(hex: 0xEFE7D6),
Color(hex: 0x3A6B5C)]
private var bath = Marbling()
private var pressPoint = Vector2.zero
override func setup() {
bath = Marbling()
// Bull's-eyes: concentric drops of alternating ink, the base every
// raked pattern starts from.
for column in 0 ..< 3 {
let center = Vector2(width * (0.25 + 0.25 * Double(column)),
height * random(0.35, 0.65))
let rings = 12
for ring in 0 ..< rings {
let radius = map(Double(ring), 0, Double(rings - 1), 230, 30)
bath.drop(at: center, radius: radius + random(-8, 8),
color: inks[(column + ring) % inks.count])
}
}
// A loose scatter of stones between the eyes.
for _ in 0 ..< 14 {
bath.drop(at: randomVector(in: canvasRectangle.inset(by: 90)),
radius: random(14, 44), color: randomChoice(inks))
}
// The nonpareil rake: a comb pulled down, a finer comb pulled back
// up half a tooth over, feathering every boundary. Falloffs well
// under the tooth spacing keep the drops legible between teeth.
bath.comb(through: Vector2(0, height / 2), direction: .unitY,
spacing: 110, strength: 260, falloff: 30)
bath.comb(through: Vector2(55, height / 2), direction: -.unitY,
spacing: 110, strength: 170, falloff: 22)
// One stirred curl for asymmetry.
bath.swirl(at: Vector2(width * random(0.3, 0.7),
height * random(0.3, 0.7)),
strength: random(320, 480), falloff: 150)
}
override func draw() {
background(paper)
noStroke()
drawMarbling(bath)
drawCaption("click drops ink, a drag pulls the stylus")
}
override func mousePressed() {
pressPoint = mouse
}
override func mouseReleased() {
let release = mouse
let pull = release - pressPoint
if pull.length > 24 {
bath.tine(through: pressPoint, direction: pull,
strength: pull.length, falloff: 60)
} else {
bath.drop(at: release, radius: random(24, 64),
color: randomChoice(inks))
}
}
}