#include <metal_stdlib>
using namespace metal;

struct Uniforms {
    float2 resolution;
    float  time;
    float  timeDelta;
    float2 mouse;
    uint   frame;
    uint   pad;
};

struct Params {
    float speed;
    float scale;
    float crack;
    float bump;
    float light;
    float heat;
    float volume;
};

struct RKVertexOut {
    float4 position [[position]];
    float2 uv;
};

vertex RKVertexOut rk_vertex(uint vid [[vertex_id]]) {
    float2 p = float2((vid << 1) & 2, vid & 2);
    RKVertexOut o;
    o.position = float4(p * 2.0 - 1.0, 0.0, 1.0);
    o.uv = p;
    return o;
}

#line 1 "shader.metal"
// Ray Kiln · Episode 5 · Cracked Clay
//
// @episode 5 Cracked Clay
// @length 60
// @variation 30
// @variations 1,3,5,6,7,9,10,11
// @still 20.0
// @teaches Voronoi noise, cell edges, bump lighting
// @category Fragment
// @tags voronoi, cellular noise, bump mapping, lighting, procedural sound
// @final Fired clay | Colour the clay, let the fire glow through the cracks and onto their rims, and let them breathe.
//
// A floor of drying clay, split into cells by cracks with the fire glowing underneath, lit by a lamp that slowly
// circles it. The cells are Voronoi noise: every pixel belongs to the nearest of a scatter of points.
// Each @param line below becomes a slider in the host app and a row in the page's parameters table:
//   // @param name default min max description

// @param speed 0.5 0.0 2.0 How fast the clay drifts, the fire flows and the light circles
// @param scale 4.0 1.5 10.0 How many cells fit on the screen: higher makes smaller pieces
// @param crack 0.06 0.01 0.2 How wide the cracks open
// @param bump 1.0 0.0 3.0 How strongly the pieces bulge and catch the light
// @param light 0.15 0.0 1.0 Where the lamp stands, once around the clay
// @param heat 0.5 0.0 1.0 Slides the fire from deep ember to white-hot
// @param volume 0.8 0.0 1.0 Loudness of the sound

constant float TAU = 6.2831853;

// ---- Noise (episode 1) -----------------------------------------------------------------------------------------

float hash21(float2 p) {
    p = fract(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

float2 hash22(float2 p) {
    float3 q = fract(float3(p.xyx) * float3(0.1031, 0.1030, 0.0973));
    q += dot(q, q.yzx + 33.33);
    return fract((q.xx + q.yz) * q.zy);
}

float vnoise(float2 p) {
    float2 i = floor(p), f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i), b = hash21(i + float2(1, 0));
    float c = hash21(i + float2(0, 1)), d = hash21(i + float2(1, 1));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float fbm(float2 p) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 4; i++) {
        v += a * vnoise(p);
        p = p * 2.03 + float2(17.1, 9.2);
        a *= 0.5;
    }
    return v;
}

// ---- Voronoi ---------------------------------------------------------------------------------------------------
// Cut the plane into squares and put one point somewhere inside each. Every pixel belongs to the nearest point: that
// is its cell. The nearest point is always in this square or one of its eight neighbours.

// The point in square c: a random place, swinging back and forth in time.
float2 cellPoint(float2 c, float t) {
    float2 h = hash22(c);
    return 0.5 + 0.4 * sin(t * 0.3 + TAU * h);
}

// Returns the distance to the nearest edge between two cells, the distance to the nearest point, and a random number
// for the cell. The edge distance needs a second pass: the edge is halfway between the nearest point and another one,
// so measure along the line joining them.
float3 voronoi(float2 x, float t) {
    float2 n = floor(x), f = fract(x);
    float2 mg = 0.0, mr = 0.0;
    float md = 8.0;
    for (int j = -1; j <= 1; j++) {
        for (int i = -1; i <= 1; i++) {
            float2 g = float2(i, j);
            float2 r = g + cellPoint(n + g, t) - f;
            float d = dot(r, r);
            if (d < md) { md = d; mr = r; mg = g; }
        }
    }
    float near = sqrt(md);
    md = 8.0;
    for (int j = -2; j <= 2; j++) {
        for (int i = -2; i <= 2; i++) {
            float2 g = mg + float2(i, j);
            float2 r = g + cellPoint(n + g, t) - f;
            if (dot(mr - r, mr - r) > 0.00001) {
                md = min(md, dot(0.5 * (mr + r), normalize(r - mr)));
            }
        }
    }
    return float3(md, near, hash21(n + mg));
}

// ---- The clay --------------------------------------------------------------------------------------------------

// The width of the cracks: they slowly open and close, as if the clay breathes in the heat.
float crackWidth(float t, constant Params& p) {
    return p.crack * (0.75 + 0.25 * sin(t * 0.4));
}

// The height of the clay: a rounded piece for each cell, sinking to nothing at its cracks.
float clayHeight(float2 x, float t, float w,
                 thread float& edge, thread float& id) {
    // Bend the space a little first, so the cracks wander.
    float2 warp = float2(fbm(x * 0.9), fbm(x * 0.9 + float2(5.2, 1.3))) - 0.5;
    float3 v = voronoi(x + warp * 0.5, t);
    edge = v.x;
    id = v.z;
    float h = 0.3 * (1.0 - 0.6 * v.y * v.y);
    h *= smoothstep(w * 0.5, w * 1.6 + 0.12, v.x);
    // Finer cracks, only where a slow noise lets them through.
    float3 fine = voronoi(x * 2.6 + warp + float2(31.7, 12.4), t);
    float mask = smoothstep(0.5, 0.65, fbm(x * 0.6 + float2(3.0, 8.0)));
    h -= 0.1 * mask * (1.0 - smoothstep(0.0, max(w * 1.2, 0.04), fine.x));
    float2 turned = float2(x.x * 0.8 - x.y * 0.6, x.x * 0.6 + x.y * 0.8);
    h += 0.006 * fbm(turned * 9.0 + id * 50.0);
    return h;
}

float3 kiln(float t) {
    float3 c0 = float3(0.020, 0.012, 0.020);
    float3 c1 = float3(0.280, 0.040, 0.030);
    float3 c2 = float3(0.880, 0.260, 0.050);
    float3 c3 = float3(1.000, 0.680, 0.200);
    float3 c4 = float3(1.000, 0.970, 0.840);
    t = clamp(t, 0.0, 1.0);
    float3 c = mix(c0, c1, smoothstep(0.00, 0.22, t));
    c = mix(c, c2, smoothstep(0.22, 0.50, t));
    c = mix(c, c3, smoothstep(0.50, 0.76, t));
    return mix(c, c4, smoothstep(0.76, 1.00, t));
}

float4 shade(float2 uv, constant Uniforms& u, constant Params& p) {
    float px = 2.4 / min(u.resolution.x, u.resolution.y);
    float2 q = (uv - 0.5) * u.resolution * px;
    float t = u.time * p.speed;
    // The clay drifts left.
    float2 x = q * p.scale * 0.5 + float2(t * 0.15, 0.0);
    float w = crackWidth(t, p);

    // Bump lighting, as in stage 4: the slope of the height gives the normal.
    float edge, id, e2, i2;
    float e = 0.01;
    float h = clayHeight(x, t, w, edge, id);
    float hx = clayHeight(x + float2(e, 0.0), t, w, e2, i2);
    float hy = clayHeight(x + float2(0.0, e), t, w, e2, i2);
    float2 slope = float2(hx - h, hy - h) / e;
    float3 n = normalize(float3(-slope * p.bump, 1.0));

    // A lamp circles the clay. Diffuse light is how squarely the surface faces it.
    float la = TAU * p.light + t * 0.2;
    float3 l = normalize(float3(cos(la), sin(la), 0.8));
    float diffuse = max(dot(n, l), 0.0);
    // A soft sheen where the surface turns the lamp's light up towards the viewer.
    float3 halfway = normalize(l + float3(0.0, 0.0, 1.0));
    float sheen = pow(max(dot(n, halfway), 0.0), 24.0);

    // Each piece is its own terracotta, darker where it sinks.
    float3 clay = mix(float3(0.34, 0.15, 0.09), float3(0.56, 0.30, 0.17), id);
    clay *= 0.55 + 1.5 * h;
    float3 col = clay * (0.12 + 0.95 * diffuse);
    col += float3(1.0, 0.9, 0.75) * sheen * 0.12;

    // The fire flows under the cracks.
    float fire = fbm(x * 1.2 + float2(-t * 0.5, t * 0.2));
    float3 ember = kiln(0.5 + 0.45 * fire + (p.heat - 0.5) * 0.6);
    float inCrack = 1.0 - smoothstep(w * 0.25, w * 1.1, edge);
    col = mix(col, ember * (0.6 + 0.6 * fire), inCrack);
    // Its light spills onto their rims.
    float spill = exp(-max(edge - w * 0.5, 0.0) / (w * 1.5 + 0.02));
    col += kiln(0.45 + p.heat * 0.3) * 0.45 * spill * (1.0 - inCrack);

    // Darker towards the corners.
    col *= 1.0 - 0.4 * smoothstep(0.6, 1.9, length(q));
    return float4(col, 1.0);
}

// ---- Sound -----------------------------------------------------------------------------------------------------
// Voronoi in one dimension: cut time into short slots and put one moment at a random place in each, the way the
// picture puts one point in each square. Each moment is a crack: a burst of noise and a small dull ring, somewhere
// between the ears. Under them, the roar of the kiln, breathing with the cracks.

// Integer hash, so noise stays exact for minutes of sound.
float hashU(uint n) {
    n = (n << 13u) ^ n;
    n = n * (n * n * 15731u + 789221u) + 1376312589u;
    return float(n & 0x7fffffffu) / float(0x7fffffff);
}

// Noise made by joining random values `rate` times a second: a lower rate sounds darker.
float noise1(float t, float rate, uint seed) {
    float x = t * rate;
    float i = floor(x), f = fract(x);
    f = f * f * (3.0 - 2.0 * f);
    uint k = uint(i) + seed * 7919u;
    return mix(hashU(k), hashU(k + 1u), f) * 2.0 - 1.0;
}

float2 sound(float t, constant Params& p) {
    float ts = t * p.speed;
    // How open the cracks are now, from 0.5 to 1.
    float w = crackWidth(ts, p) / max(p.crack, 0.0001);

    // More cells, more cracks: about three a second at the default scale.
    float rate = 0.8 * p.scale;
    float slot = t * rate;
    float2 snaps = 0.0;
    for (int back = 0; back < 3; back++) {
        float s = floor(slot) - float(back);
        if (s < 0.0) { continue; }
        uint k = uint(s);
        // Not every slot cracks: more of them do while the cracks are wide open.
        if (hashU(k * 3u + 1u) > 0.35 + 0.5 * w) { continue; }
        float at = (s + 0.1 + 0.8 * hashU(k * 3u)) / rate;
        float since = t - at;
        if (since < 0.0) { continue; }
        float loud = 0.4 + 0.6 * hashU(k * 3u + 2u);
        // The noise of a wider crack lasts longer.
        float decay = 70.0 - 250.0 * p.crack;
        float attack = 1.0 - exp(-since * 3000.0);
        float burst = noise1(t, 9000.0, k) * attack * exp(-since * decay);
        float f = 420.0 + 600.0 * hashU(k * 5u + 4u);
        float ring = sin(TAU * f * since) * attack * exp(-since * 30.0);
        float c = (burst * 0.8 + ring * 0.5) * loud;
        float pan = hashU(k * 7u + 3u);
        snaps += c * float2(1.0 - 0.7 * pan, 0.3 + 0.7 * pan);
    }

    // The roar: dark noise, brighter with heat, swelling as the cracks widen.
    float roarRate = mix(90.0, 400.0, p.heat);
    float hi = roarRate * 2.1;
    float2 roar = float2(noise1(t, roarRate, 11u) + 0.5 * noise1(t, hi, 12u),
                         noise1(t, roarRate, 13u) + 0.5 * noise1(t, hi, 14u));
    roar *= 0.16 * (0.6 + 0.6 * (w - 0.5));
    // A low hum on A1 and E2 under it all.
    float hum = sin(TAU * fract(55.0 * t)) + 0.5 * sin(TAU * fract(82.41 * t));
    float2 bed = roar + float2(hum, hum) * 0.06;

    float fadeIn = smoothstep(0.0, 2.0, t);
    return tanh((snaps * 0.45 + bed) * 1.4) * fadeIn * p.volume;
}

#line 1 "rk_host"
fragment float4 rk_fragment(RKVertexOut in [[stage_in]],
                                constant Uniforms& u [[buffer(0)]],
                                constant Params& p [[buffer(1)]]) {
    return shade(in.uv, u, p);
}

struct SoundUniforms { float start; float rate; uint count; uint pad; };
kernel void rk_sound(device float2* out [[buffer(0)]],
                         constant SoundUniforms& su [[buffer(1)]],
                         constant Params& p [[buffer(2)]],
                         uint i [[thread_position_in_grid]]) {
    if (i >= su.count) return;
    out[i] = clamp(sound(su.start + float(i) / su.rate, p), -1.0, 1.0);
}