Ollin

MultiScaleTuring

McCabe's multi-scale Turing patterns: five scales compete at each pixel and organize themselves from noise into a relief that looks like a diatom. You can fold the result into a rosette (makeSimField(.multiScaleTuring(...)))

Run
swift run --package-path Examples Example-Simulation-MultiScaleTuring
Source
Examples/Simulation/MultiScaleTuring/Sketch.swift
import Ollin

/// **Multi-scale Turing patterns**, McCabe's elaboration of Turing's morphogenesis
/// model, on the GPU through the effects substrate. A `SimField` running
/// `.multiScaleTuring()` holds one substance; every step each pixel compares the
/// field's average over a small disc against its average over a larger one, at each
/// of five magnifications at once, and lets only the magnification whose two averages
/// agree most closely move it. Broad lobes and fine stippling settle into the same
/// picture, and the result reads like an electron micrograph of a diatom.
///
/// The field starts as noise and organizes itself, so nothing seeds it. Drag to
/// disturb the pattern and watch it heal. Turn `symmetry` up to fold every scale
/// around the center into a rosette.
@main
final class MultiScaleTuring_Example: Sketch {

    @Param(0 ... 12, icon: "circle.hexagongrid", group: "Form") var symmetry = 0
    @Param(icon: "paintpalette", group: "Look") var relief = true

    private var field: SimField!

    override func setup() {
        field = makeSimField(.multiScaleTuring(seed: Double(variation)), scale: 0.5)
    }

    override func draw() {
        background(.black)
        // Retuning the rule live keeps the evolved field and reshapes it from here on.
        field.sim = .multiScaleTuring(scales: symmetry < 2 ? .ladder : .rosette(symmetry),
                                      seed: Double(variation))

        withField(field) {
            noStroke()
            if mouseIsPressed {
                fill(Color(white: keyIsPressed ? 0 : 1, alpha: 0.9))
                drawCircle(mouseX, mouseY, 60)
            }
        }
        // The field is grayscale, so it takes either a color ramp or relief shading.
        // The lit-from-above look is an accident of a flat 2D rule, and it reads as depth.
        let shaded = relief ? field.filtered(.relight(height: 0.35))
                            : field.filtered(.gradientMap(.magma))
        drawImage(shaded.image, 0, 0)
        drawCaption("Multi-scale Turing · five scales competing per pixel · drag to disturb, hold a key to darken")
    }
}

↑↓ move ↩ open