#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 height;
    float turbulence;
    float thickness;
    float glow;
    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 8 · Kiln Fire
//
// @episode 8 Kiln Fire
// @length 60
// @variation 30
// @variations 1,2,3,5,6,7,10,12
// @sweeps turbulence, thickness, height
// @still 20.0
// @teaches Volume raymarching through noise, emission
// @category Fragment
// @tags volume rendering, raymarching, fractal noise, emission, absorption, procedural sound
// @final Kiln fire | Shape the noise into a flame, hottest at its root, rising and flickering over a glowing floor.
//
// A fire with no surface: each pixel's ray walks through a cloud of 3D noise in small steps, and at every step the
// cloud gives off light by how hot it is there and hides a little of what lies behind it.
// 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 fire rises and the camera sways
// @param height 1.0 0.5 1.6 How tall the flame stands
// @param turbulence 0.6 0.0 1.0 How much the noise tears at the flame's edge
// @param thickness 0.5 0.1 1.0 How much light the flame swallows: thin and glassy, or dense and solid
// @param glow 0.5 0.0 1.0 How brightly the flame lights the floor around it
// @param heat 0.5 0.0 1.0 Slides the palette from deep ember to white-hot
// @param volume 0.8 0.0 1.0 Loudness of the sound

constant float TAU = 6.2831853;

// ---- Noise in three dimensions ---------------------------------------------------------------------------------
// Episode 1's value noise, one dimension up: a random value at each corner of a grid of cubes, blended smoothly
// across each cube. Eight corners instead of four.

// A random number from 0 to 1 for each whole-numbered point in 3D, mixed with integer arithmetic so neighbouring
// cubes look unrelated.
float hash31(float3 p) {
    uint3 v = uint3(int3(floor(p))) * 1664525u + 1013904223u;
    v.x += v.y * v.z; v.y += v.z * v.x; v.z += v.x * v.y;
    v ^= v >> 16u;
    v.x += v.y * v.z; v.y += v.z * v.x; v.z += v.x * v.y;
    return float(v.x) * (1.0 / 4294967295.0);
}

float vnoise3(float3 p) {
    float3 i = floor(p), f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash31(i),                     b = hash31(i + float3(1, 0, 0));
    float c = hash31(i + float3(0, 1, 0)),   d = hash31(i + float3(1, 1, 0));
    float e = hash31(i + float3(0, 0, 1)),   g = hash31(i + float3(1, 0, 1));
    float h = hash31(i + float3(0, 1, 1)),   k = hash31(i + float3(1, 1, 1));
    float lo = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
    float hi = mix(mix(e, g, f.x), mix(h, k, f.x), f.y);
    return mix(lo, hi, f.z);
}

// Fractal noise: octaves of vnoise3, each about twice as fine and half as strong. Around 0.5 on average.
float fbm3(float3 p) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 5; i++) {
        v += a * vnoise3(p);
        p = p * 2.02 + float3(17.1, 9.2, 4.7);
        a *= 0.5;
    }
    return v;
}

// Episode 3's rotation.
float2 rot(float2 p, float a) {
    float c = cos(a), s = sin(a);
    return float2(c * p.x - s * p.y, s * p.x + c * p.y);
}

// ---- The flame -------------------------------------------------------------------------------------------------
// The noise rises: the point is looked up lower in the noise as time goes on, so its shapes move up through the
// flame. The sound reads the same noise.

float riseNoise(float3 p, float t) {
    float3 q = p * float3(3.0, 1.8, 3.0) - float3(0.0, t * 3.0, 0.0);
    q.xz = rot(q.xz, p.y * 0.8);
    return fbm3(q);
}

// How far inside the flame a point is: positive inside, negative outside, like a distance with the sign turned.
// The flame is a column that is widest near the floor and narrows to nothing at the top. Two slow noises sway it
// from side to side, more the higher up, and the rising noise pushes its edge in and out, so the top tears into
// tongues.
float flameInside(float3 p, float t, float height, float turbulence) {
    float h = clamp(p.y / (2.6 * height), 0.0, 1.0);
    float3 s = float3(p.x, p.y - t * 1.5, p.z) * 0.9;
    float2 sway = float2(vnoise3(s), vnoise3(s + float3(5.2, 1.3, 8.1)));
    sway -= 0.5;
    float2 xz = p.xz + sway * h * 1.4 * turbulence;
    float root = smoothstep(-0.05, 0.15, p.y / height);
    float radius = 0.7 * pow(1.0 - h, 0.9) * root;
    float n = riseNoise(p, t) - 0.5;
    return radius - length(xz) + n * turbulence * (0.45 + 1.6 * h);
}

// How bright the fire's root is right now, from the noise flowing past it: the floor's light and the roar follow it.
float flicker(float t) {
    return 0.7 + 0.9 * (fbm3(float3(0.0, -t * 3.0, 0.0)) - 0.5);
}

// ---- The kiln palette from episode 1 ---------------------------------------------------------------------------

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 t = u.time * p.speed;

    // Episode 3's camera, looking at the flame from a little above its root and swaying slowly from side to side. It
    // stands further back for a taller flame.
    float side = min(u.resolution.x, u.resolution.y);
    float2 q = (uv - 0.5) * u.resolution / side * 2.0;
    float a = 0.35 * sin(t * 0.21);
    float back = 2.6 + 0.8 * p.height;
    float3 target = float3(0.0, 0.25 + 0.85 * p.height, 0.0);
    float3 ro = float3(back * sin(a), 0.6 + 0.6 * p.height, back * cos(a));
    float3 fw = normalize(target - ro);
    float3 rt = normalize(cross(fw, float3(0.0, 1.0, 0.0)));
    float3 up = cross(rt, fw);
    float3 rd = normalize(q.x * rt + q.y * up + 2.0 * fw);

    // As in episode 3, everything adds up to one level that picks a colour from the palette at the end.
    // Behind everything: the dark of the kiln, a touch warmer low down.
    float level = 0.05 + 0.05 * smoothstep(0.25, -0.3, rd.y);

    // The floor, y = 0: where the ray meets it, if it does. The march stops there. The fire lights it, brightest at
    // the root and fading with distance, and it breathes with the flicker.
    float floorDist = rd.y < 0.0 ? -ro.y / rd.y : 1e9;
    if (floorDist < 1e8) {
        float3 fp = ro + rd * floorDist;
        level += p.glow * flicker(t) * 0.9 / (1.0 + 1.5 * dot(fp.xz, fp.xz));
    }

    // March through the flame's bounds, a cylinder of radius 1.4 around the column, in even steps. Each step adds the
    // light the fire gives off there, dimmed by how much fire the ray has already passed through (the transmittance),
    // then dims that further. A fixed random start per pixel hides the steps as fine grain instead of bands.
    float aa = dot(rd.xz, rd.xz);
    float b = dot(ro.xz, rd.xz);
    float c = dot(ro.xz, ro.xz) - 1.4 * 1.4;
    float disc = b * b - aa * c;
    if (disc > 0.0) {
        float enter = max((-b - sqrt(disc)) / aa, 0.0);
        float leave = min((-b + sqrt(disc)) / aa, floorDist);
        const int STEPS = 72;
        float dt = (leave - enter) / float(STEPS);
        float dist = enter + dt * hash31(float3(uv * u.resolution, 3.7));
        float transmit = 1.0;
        float light = 0.0;
        for (int i = 0; i < STEPS; i++) {
            if (dist > leave || transmit < 0.01) break;
            float3 pos = ro + rd * dist;
            float inside = flameInside(pos, t, p.height, p.turbulence);
            if (inside > 0.0) {
                // The fire thickens over the first stretch inside its edge.
                float density = smoothstep(0.0, 0.12, inside);
                // Hottest deep inside and near the root. Hotter fire gives off more light.
                float h = clamp(pos.y / (2.6 * p.height), 0.0, 1.0);
                float temp = clamp(inside * 2.5 + 0.45 * (1.0 - h), 0.0, 1.0);
                light += transmit * density * temp * temp * dt * 3.5;
                transmit *= exp(-density * dt * 6.0 * p.thickness);
            }
            dist += dt;
        }
        level = level * transmit + light;
    }

    return float4(kiln(level + (p.heat - 0.5) * 0.5), 1.0);
}

// ---- Sound -----------------------------------------------------------------------------------------------------
// A fire sounds like noise, so the sound is built of noise too: a roar of three bands of it, its loudness following
// the same flicker the floor shows, and crackles, short bursts of fine noise at random moments, more of them the more
// turbulent the flame. Under it a low fifth hums, brighter as the flame burns higher.

float hashU(uint n) {
    n = (n << 13u) ^ n;
    n = n * (n * n * 15731u + 789221u) + 1376312589u;
    return float(n & 0x7fffffffu) / float(0x7fffffff);
}

// Episode 5's sound noise: random values joined `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;
    float fl = flicker(ts);

    // The roar: low, middle and high noise, the same octaves-of-noise idea as fbm3, but in time. Each ear gets its
    // own noise, so the fire sounds wide.
    float2 roar = float2(0.0);
    for (int ch = 0; ch < 2; ch++) {
        uint s = uint(ch) * 31u;
        float r = 0.6 * noise1(t, 140.0, s + 1u) + 0.3 * noise1(t, 420.0, s + 2u) + 0.12 * noise1(t, 1300.0, s + 3u);
        roar[ch] = r;
    }
    roar *= 0.32 * (0.35 + 0.9 * fl) * (0.5 + 0.5 * p.height);

    // Crackles: twenty slots a second, each with a chance of a burst, at a random moment inside its slot.
    float2 crackle = float2(0.0);
    const float slot = 0.05;
    float chance = 0.08 + 0.3 * p.turbulence;
    float now = floor(t / slot);
    for (int back = 0; back < 3; back++) {
        float idx = now - float(back);
        if (idx < 0.0) continue;
        uint s = uint(idx);
        if (hashU(s * 5u) > chance) continue;
        float age = t - (idx + 0.8 * hashU(s * 5u + 1u)) * slot;
        if (age < 0.0) continue;
        float env = (1.0 - exp(-age * 3000.0)) * exp(-age * (90.0 + 120.0 * hashU(s * 5u + 2u)));
        float grit = noise1(t, 5000.0 + 6000.0 * hashU(s * 5u + 3u), s);
        float pan = 0.15 + 0.7 * hashU(s * 5u + 4u);
        float loud = 0.4 + 0.6 * hashU(s * 5u + 5u);
        crackle += float2(1.0 - pan, pan) * grit * env * loud * 0.9;
    }

    // The hum: A1 and the E above it, a few cents apart in each ear, its overtones growing with the flicker.
    float bright = 0.2 + 0.5 * clamp(fl, 0.0, 1.2);
    float2 hum = float2(sin(TAU * 55.0 * 1.002 * t) + 0.6 * sin(TAU * 82.41 * t) + bright * 0.4 * sin(TAU * 110.0 * t),
                        sin(TAU * 55.0 * 0.998 * t) + 0.6 * sin(TAU * 82.41 * t + 0.4) + bright * 0.4 * sin(TAU * 110.0 * t + 0.8))
                 * 0.09;

    float fadeIn = smoothstep(0.0, 2.0, t);
    return tanh((roar + crackle + hum) * 2.0) * 0.58 * 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);
}