Ollin

NoiseKinds

the noise kinds side by side on one seeded field: simplexNoise, signedSimplexNoise split at zero, worley with its feature and jitter exposed as parameters, and turbulence

Run
swift run --package-path Examples Example-Randomness-NoiseKinds
Source
Examples/Randomness/NoiseKinds/Sketch.swift
import Ollin

/// The CPU noise kinds past `noise()`, side by side on the same coordinates.
/// Top left: `simplexNoise`, the even-grained cousin of the classic field.
/// Top right: `signedSimplexNoise`, the same field read as -1...1, shown split
/// at zero (warm above, cool below, dark right on the zero line the 0...1
/// reading flattens away). Bottom left: `worley`, cellular distance, with its
/// `feature` reading and `jitter` on parameters. Bottom right: `turbulence`, fbm
/// over folded octaves, the creased billows behind clouds and marble.
///
/// Every panel samples the identical (x, y) under one `noiseSeed`, so the
/// grains compare directly; only the reading changes. The pictures rebuild
/// when a parameter moves and are cached between frames.
@main
final class NoiseKinds: Sketch {
    /// The three `WorleyFeature` readings, as a menu the inspector can show.
    enum Feature: String, CaseIterable, ParamOption { case nearest, second, border }

    @Param(0 ... 1, icon: "circle.hexagongrid") var jitter = 1.0
    @Param(icon: "slider.horizontal.3") var feature = Feature.nearest

    let tile = 256          // pixels per panel picture
    let frequency = 5.0     // the shared coordinate scale
    var panels: [Image] = []
    var builtWith: (jitter: Double, feature: Feature)?

    override func setup() {
        noiseSeed(7)
    }

    override func draw() {
        rebuildIfNeeded()
        background(Color(hex: 0x121316))
        textSize(24)
        fill(Color(white: 0.85))
        textAlign(.center)

        let jitterShown = (jitter * 100).rounded() / 100
        let labels = ["simplexNoise", "signedSimplexNoise",
                      "worley(feature: .\(feature.rawValue), jitter: \(jitterShown))",
                      "turbulence"]
        let side = 400.0
        for (i, image) in panels.enumerated() {
            let x = i % 2 == 0 ? 100.0 : 580.0
            let y = i < 2 ? 70.0 : 560.0
            drawImage(image, x, y, side, side)
            drawText(labels[i], x + side / 2, y + side + 32)
        }

        drawCaption("four readings of one seeded field: every panel samples the same (x, y), scaled by \(Int(frequency))")
    }

    /// Rebuild the four pictures when a parameter has moved (and once at the start).
    func rebuildIfNeeded() {
        if let built = builtWith, built.jitter == jitter, built.feature == feature { return }
        let worleyFeature: WorleyFeature = switch feature {
        case .nearest: .nearest
        case .second: .second
        case .border: .border
        }
        panels = [
            picture { x, y in
                let g = self.simplexNoise(x, y)
                return (g, g, g)
            },
            picture { x, y in
                let v = self.signedSimplexNoise(x, y)
                return v >= 0 ? (v, 0.72 * v, 0.3 * v) : (0.3 * -v, 0.62 * -v, 0.85 * -v)
            },
            picture { x, y in
                let g = self.worley(x, y, feature: worleyFeature, jitter: self.jitter)
                return (g, g, g)
            },
            picture { x, y in
                let g = self.turbulence(x, y)
                return (g, g, g)
            },
        ]
        builtWith = (jitter, feature)
    }

    /// A square picture from a function of the shared coordinates: the closure
    /// receives the same (x, y) whichever kind it reads.
    func picture(_ shade: (Double, Double) -> (Double, Double, Double)) -> Image {
        var bytes = [UInt8](repeating: 255, count: tile * tile * 4)
        for py in 0 ..< tile {
            for px in 0 ..< tile {
                let x = (Double(px) + 0.5) / Double(tile) * frequency
                let y = (Double(py) + 0.5) / Double(tile) * frequency
                let (r, g, b) = shade(x, y)
                let i = (py * tile + px) * 4
                bytes[i] = byte(r)
                bytes[i + 1] = byte(g)
                bytes[i + 2] = byte(b)
            }
        }
        return Image(width: tile, height: tile, premultipliedRGBA: bytes)!
    }

    func byte(_ v: Double) -> UInt8 { UInt8((clamp(v, 0, 1) * 255).rounded()) }
}

↑↓ move ↩ open