CrumpledFoil
a whole canvas of crumpled holographic foil that breathes: a FacetField lifted every frame by a drifting crumple and a rolling wave, under .holographicFoil, whose stepped rainbow breaks at every crease, with the light drifting and the bands sweeping on parameters, and a switch that inks the sheet's creases over the fill
- Run
swift run --package-path Examples Example-Effects-CrumpledFoil- Source
- Examples/Effects/CrumpledFoil/Sketch.swift
import Ollin
/// A whole canvas of crumpled holographic foil that breathes. The sheet is a
/// `FacetField` over a jittered lattice, triangulated once and lifted again
/// every frame by a crumple that drifts plus a slow wave rolling across it, so
/// the creases move. `.holographicFoil` takes that sheet: a stepped rainbow
/// runs across it and breaks at every crease (each facet shifts the bands by
/// its tilt), the creases light up or darken as the light turns, and the
/// facets that face the light hardest glint at their corners. The light drifts
/// around the sheet and the bands sweep across it on their own clocks.
///
/// The crease switch draws the sheet's creases in geometry over the filter's
/// fill: they land on the filter's own creases, since the GPU reads the same
/// facets the sketch holds, so a plotter gets the same sheet as lines.
@main
final class CrumpledFoil: Sketch {
@Param("Crinkle", 0 ... 1.5, icon: "scribble") var crinkle = 0.7
@Param("Spacing", 28 ... 120, icon: "square.grid.3x3") var spacing = 64.0
@Param("Band angle", -3.14 ... 3.14, icon: "line.diagonal") var bandAngle = -0.6
@Param("Band width", 12 ... 80, icon: "rectangle.split.3x1") var bandWidth = 36.0
@Param("Light drift", 0 ... 1.5, icon: "sun.max") var lightDrift = 0.25
@Param("Sweep", 0 ... 3, icon: "wind") var sweep = 0.7
@Param("Creases in ink", icon: "pencil.line") var drawsCreases = false
private var lattice: Delaunay?
private var latticeSpacing = 0.0
override func setup() {
noiseSeed(7)
}
override func draw() {
// The lattice once per spacing: the points and the triangles hold
// still, and only the heights move.
if lattice == nil || latticeSpacing != spacing {
lattice = Delaunay(FacetField.latticePoints(in: canvasRectangle, spacing: spacing, seed: 7))
latticeSpacing = spacing
}
guard let lattice else { return }
let t = time
let swing = crinkle * spacing
let broad = spacing * 2.8, fine = spacing * 0.9
let sheet = FacetField(lattice) { p in
let low = signedNoise(p.x / broad, p.y / broad, t * 0.1)
let high = signedNoise(p.x / fine + 31, p.y / fine + 17, t * 0.16)
// A slow wave rolling across the sheet, so it breathes as one.
let wave = sin(p.x / 170 + p.y / 260 - t * 0.8) * 0.4
return swing * (1.1 * low + 0.45 * high + wave)
}
let foil = makeRenderTarget()
withTarget(foil) { background(.white) }
drawImage(foil.filtered(.holographicFoil(facets: sheet, bandWidth: bandWidth, bandAngle: bandAngle,
lightAngle: -2.2 + t * lightDrift,
phase: t * sweep)).image, 0, 0)
if drawsCreases {
noFill()
stroke(Color(white: 0.08, alpha: 0.7))
strokeWeight(1.2 * scale)
for edge in sheet.edges { drawLine(edge) }
}
}
}