Combinators
SDF combinators: drawSDF and smoothUnion { } compose signed-distance fields, so 2D shapes merge instead of stacking
- Run
swift run --package-path Examples Example-Shapes-Combinators- Source
- Examples/Shapes/Combinators/Sketch.swift
import Ollin
/// SDF combinators: compose signed-distance *fields* so shapes **merge** instead of
/// just stacking. A smooth union melts two shapes into one blob (their colors blending
/// across the seam), subtraction carves one out of another, and morph blends between
/// two shapes. Domain ops (`repeated`, `mirrored`) tile or fold the whole field.
///
/// Build a field with the `SDF` value type and draw it with `drawSDF`. Unlike the
/// immediate `drawCircle`/`drawRect` calls (each its own shape), a composed `SDF`
/// fills as a single region, and a stroke would trace the *merged* outline.
///
/// Try it: change each `k` (how much the shapes melt), swap `.smoothUnion` for
/// `.smoothSubtract`, the `.morph` amount, or `.repeated(...)` for `.mirrored()`.
@main
final class Combinators_Example: Sketch {
override func draw() {
background(Color(white: 0.07))
noStroke()
let t = time
// 1) Melt: a circle and a rounded rect smooth-union; their colors blend at the
// seam. A second circle is smoothly *subtracted* to carve a hole. The melt
// amount breathes.
let k = 20 + unipolar(sin(t)) * 80
let melt = SDF.circle(radius: 130).colored(Color(hex: 0xff5470))
.smoothUnion(
SDF.rect(width: 230, height: 130, cornerRadius: 24)
.colored(Color(hex: 0x3a86ff))
.at(120, 0),
k: k)
.smoothSubtract(SDF.circle(radius: 50).at(-80, -10), k: 18)
.at(width * 0.30, height * 0.36)
drawSDF(melt)
// 2) Morph: a six-point star into a circle and back, scaled by a breath, spinning.
let amount = unipolar(sin(t * 0.8))
let morph = SDF.star(outerRadius: 120, innerRadius: 54, points: 6)
.colored(Color(hex: 0xffd166))
.morph(SDF.circle(radius: 110).colored(Color(hex: 0x06d6a0)), amount: amount)
.scaled(0.85 + 0.15 * unipolar(sin(t * 1.3)))
.rotated(t * 0.5)
.at(width * 0.70, height * 0.36)
drawSDF(morph)
// 3) Scoped block sugar: bare draw calls inside a block merge (the same fields
// as the value type, layered over `drawSDF`). Here a `repeated` block tiles
// a little melted cell (a rounded square + a bump) into a row.
withState {
translate(width * 0.5, height * 0.74)
fill(Color(hex: 0xbdb2ff))
let bump = 30 + sin(t * 2) * 14
repeated(spacing: Vector2(165, 0), count: 3) {
smoothUnion(k: 14) {
drawRect(-30, -30, 60, 60, cornerRadius: 16)
drawCircle(bump, 0, 22)
}
}
}
drawCaption("SDF combinators: smooth union + color melt · morph · scoped block + tiling")
}
}