GeneratorCatalog
the procedural Generator catalog: the basic patterns, the animated design set, the closed-form fields, and chains where a generated layer passes into filters and blends
- Run
swift run --package-path Examples Example-Effects-GeneratorCatalog- Source
- Examples/Effects/GeneratorCatalog/Sketch.swift
import Ollin
/// The procedural `Generator` catalog on one switchable contact sheet: the
/// **basic** patterns (checkers, grid lines, bars, noise), the **design** set
/// (nine animated designer sources, each a single `generate` call), the
/// closed-form **fields** (quasicrystal, moire, gyroid slice, phyllotaxis, hex
/// pulses), and the **chains**, where a generated layer flows on into the rest
/// of the effect graph: noise mapped through a colormap, a grid multiplied over
/// that field, and the gyroid blurred and relit as liquid.
///
/// A `Generator` is a source filled from math alone, no input layer;
/// `generate(_:)` hands back a `RenderTarget` you draw, filter, blend, or feed
/// into a combine like any other layer. Every animated source takes a `phase`
/// you feed `time`. Each tile is generated at its cell's own size, so nothing
/// stretches. (The mesh gradient has its own example, `Effects/MeshGradient`.)
@main
final class GeneratorCatalog_Example: Sketch {
enum Family: String, CaseIterable, ParamOption { case basic, design, fields, chains }
@Param(style: .segmented, icon: "circle.grid.3x3") var family = Family.basic
private let labelFont = OutlineFont.system
/// A tile is a layer, plus an optional second layer multiplied over it in
/// place (a filtered layer resolves on the canvas, so the multiply happens
/// there rather than inside another target).
private typealias Tile = (layer: RenderTarget, multiplied: RenderTarget?)
/// The near-square layout `drawSheet` picks for `count` items, so each tile
/// can be generated at its cell's own size.
private func sheetCell(for count: Int) -> (columns: Int, width: Int, height: Int) {
let cols = Int(Double(count).squareRoot().rounded(.up))
let rows = (count + cols - 1) / cols
let space = width * 0.01
return (cols, Int((width - space * Double(cols + 1)) / Double(cols)),
Int((height - space * Double(rows + 1)) / Double(rows)))
}
override func draw() {
background(Color(white: 0.06))
let t = time
let s = 4 + sin(t * 0.4) * 2 // the basics breathe their feature scale
let items: [(String, Tile)]
let columns: Int
switch family {
case .basic:
let (cols, w, h) = sheetCell(for: 5)
columns = cols
items = [
("checkers", (generate(.checkers(scale: s * 2, foreground: Color(hex: 0xF2C14E),
background: Color(hex: 0x222B3A)),
width: w, height: h), nil)),
("gridLines", (generate(.gridLines(scale: s * 3, weight: 0.12,
foreground: Color(hex: 0x55D6BE),
background: Color(hex: 0x12161F)),
width: w, height: h), nil)),
("bars", (generate(.bars(scale: s * 3, foreground: Color(hex: 0xE85D75),
background: Color(hex: 0x1B1F2A)),
width: w, height: h), nil)),
("noise", (generate(.noise(scale: s, sharpness: 0), width: w, height: h), nil)),
("gaborNoise", (generate(.gaborNoise(wavelength: 120 / s, bandwidth: 0.3,
angle: t * 0.2, spread: 0.2,
phase: t * 2, seed: 3,
foreground: Color(hex: 0xF2E8DC),
background: Color(hex: 0x1B1F2A)),
width: w, height: h), nil)),
]
case .design:
let (cols, w, h) = sheetCell(for: 9)
columns = cols
items = [
("filaments", (generate(.filaments(phase: t), width: w, height: h), nil)),
("smokeRing", (generate(.smokeRing(colors: [.white, Color(hex: 0x6FD9FF)],
phase: t), width: w, height: h), nil)),
("colorPanels", (generate(.colorPanels(phase: t * 30), width: w, height: h), nil)),
("spiral", (generate(.spiral(phase: t * 0.4), width: w, height: h), nil)),
("waves", (generate(.waves(shape: 1.2, phase: t * 0.5), width: w, height: h), nil)),
("dotOrbit", (generate(.dotOrbit(phase: t), width: w, height: h), nil)),
("grainGradient", (generate(.grainGradient(shape: .blob, phase: t),
width: w, height: h), nil)),
("pulsingBorder", (generate(.pulsingBorder(phase: t), width: w, height: h), nil)),
("godRays", (generate(.godRays(phase: t), width: w, height: h), nil)),
]
case .fields:
let (cols, w, h) = sheetCell(for: 5)
columns = cols
items = [
("quasicrystal", (generate(.quasicrystal(phase: t * 0.6), width: w, height: h), nil)),
("moire", (generate(.moire(phase: t), width: w, height: h), nil)),
("gyroid", (generate(.gyroid(phase: t * 0.5), width: w, height: h), nil)),
("phyllotaxis", (generate(.phyllotaxis(phase: t * 0.05), width: w, height: h), nil)),
("hexPulse", (generate(.hexPulse(phase: t * 2), width: w, height: h), nil)),
]
case .chains:
let (cols, w, h) = sheetCell(for: 3)
columns = cols
let mapped = generate(.noise(scale: s), width: w, height: h)
.filtered(.gradientMap(.turbo))
let grid = generate(.gridLines(scale: s * 4, weight: 0.08,
foreground: Color(white: 0.1), background: .white),
width: w, height: h)
let relit = generate(.gyroid(foreground: .white, background: .black,
thickness: 0.5, phase: t * 0.5),
width: w, height: h)
.filtered(.gaussianBlur(radius: 6))
.filtered(.relight(.liquid, height: 3, color: Color(hex: 0x2B6C8C)))
items = [
("noise → turbo", (mapped, nil)),
("noise × grid", (mapped, grid)),
("gyroid + relight", (relit, nil)),
]
}
textFont(labelFont)
drawSheet(items, columns: columns) { tile, cell in
drawImage(tile.layer.image, in: cell)
if let over = tile.multiplied {
withState { blendMode(.multiply); drawImage(over.image, in: cell) }
}
}
}
}