#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 rings;
    float warp;
    float veins;
    float cut;
    float tiling;
    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 21 · Wood & Marble
//
// @episode 21 Wood & Marble
// @length 60
// @variation 30
// @variations 1,2,3,4,5,6,7,9
// @still 27.0
// @teaches Solid procedural textures, noise that tiles
// @category Fragment
// @tags solid texture, value noise, fbm, wood grain, marble, turbulence, periodic noise, procedural sound
// @final Wood & Marble | A block, half wood and half marble, turning over a floor of marble slabs while a plane slices through it, the grain running on inside wherever the cut goes.
//
// A block carved from two solids: one half wood, one half marble. Neither is a picture wrapped round the block: each is a
// colour for every point in space, worked out from the point itself, so the block is cut out of it like a statue from
// stone. A plane slices through the block and sweeps back and forth, and the face it leaves shows the rings and veins
// carrying on inside. The floor is laid with slabs that are all the same slab, cut from marble whose noise repeats
// across each slab, so the slabs meet with no seam.
// 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 block turns and the cut sweeps
// @param rings 9.0 3.0 20.0 How many tree rings to a unit of wood
// @param warp 0.5 0.0 1.0 How far noise bends the rings and the grain
// @param veins 0.5 0.0 1.0 How turbulent the marble's veins are
// @param cut 0.6 0.0 1.0 How deep the slicing plane reaches into the block
// @param tiling 1.0 0.0 1.0 From floor slabs that show their seams to marble that tiles with none
// @param volume 0.8 0.0 1.0 Loudness of the sound

constant float TAU = 6.2831853;
// Half the block's size.
constant float HALF = 0.62;

// 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);
}

// Episode 17's wrap: x into 0 to m, below 0 too.
float wrap(float x, float m) {
    return x - m * floor(x / m);
}

// ---- Noise in three dimensions -----------------------------------------------------------------------------------

// Episode 8's hash: a random number from 0 to 1 for each whole-numbered
// cube of space.
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);
}

// Value noise in 3D: a random value at every corner of a lattice of cubes,
// blended smoothly across each cube. The lattice wraps every `period` cubes
// (a whole number), so the noise repeats; a huge period never shows it.
float tnoise(float3 q, float period) {
    float3 i = floor(q), f = fract(q);
    f = f * f * (3.0 - 2.0 * f);
    float3 a = float3(wrap(i.x, period), wrap(i.y, period),
                      wrap(i.z, period));
    float3 b = float3(wrap(i.x + 1.0, period), wrap(i.y + 1.0, period),
                      wrap(i.z + 1.0, period));
    float n000 = hash31(float3(a.x, a.y, a.z));
    float n100 = hash31(float3(b.x, a.y, a.z));
    float n010 = hash31(float3(a.x, b.y, a.z));
    float n110 = hash31(float3(b.x, b.y, a.z));
    float n001 = hash31(float3(a.x, a.y, b.z));
    float n101 = hash31(float3(b.x, a.y, b.z));
    float n011 = hash31(float3(a.x, b.y, b.z));
    float n111 = hash31(float3(b.x, b.y, b.z));
    float lo = mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y);
    float hi = mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y);
    return mix(lo, hi, f.z);
}

// Four octaves, each lattice twice as fine and its period twice as long,
// so it still repeats where the first one does. From 0 to 1.
float tfbm(float3 q, float period) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 4; i++) {
        v += a * tnoise(q, period);
        q *= 2.0;
        period *= 2.0;
        a *= 0.5;
    }
    return v / 0.9375;
}

// Turbulence: octaves folded at their middle, each leaving a sharp crease.
// From 0 to about 1.
float turbulence(float3 q, float period) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 4; i++) {
        v += a * abs(tnoise(q, period) * 2.0 - 1.0);
        q *= 2.0;
        period *= 2.0;
        a *= 0.5;
    }
    return v / 0.9375;
}

// ---- The two solids ----------------------------------------------------------------------------------------------

// Wood: a log along z, its heart off to one side. The distance from the
// heart, bent by noise, counts the rings: pale growth darkening to its end.
float3 wood(float3 q, float rings, float warp) {
    float3 earlywood = float3(0.780, 0.500, 0.280);
    float3 latewood = float3(0.330, 0.150, 0.070);
    float2 heart = float2(-1.30, -0.90) + 0.06 * q.z;
    float bend = (tfbm(q * float3(1.6, 1.6, 0.5), 1e4) - 0.5) * warp * 0.9;
    float r = length(q.xy - heart) + bend / rings * 4.0;
    float ring = fract(r * rings);
    float late = smoothstep(0.55, 0.92, ring)
               * (1.0 - smoothstep(0.94, 1.0, ring));
    // Fine grain: noise stretched along the log.
    float grain = tnoise(q * float3(34.0, 34.0, 1.2), 1e4);
    float3 col = mix(earlywood, latewood, late * 0.85 + grain * 0.25 * warp);
    return col * (0.88 + 0.24 * tnoise(q * 3.0 + 7.0, 1e4));
}

// Marble: stripes from sin(v), pushed sideways by turbulence times `push`,
// with a thin gold vein where the sine crosses zero.
float3 marble(float3 q, float v, float push, float period) {
    float3 stone = float3(0.960, 0.930, 0.860);
    float3 cloud = float3(0.780, 0.700, 0.620);
    float3 vein = float3(0.880, 0.480, 0.150);
    float turb = turbulence(q, period);
    float s = sin(v + turb * push);
    float3 col = mix(cloud, stone, 0.3 + 0.7 * smoothstep(-0.4, 0.95, s));
    float line = 1.0 - smoothstep(0.0, 0.08, abs(s));
    col = mix(col, vein, line * 0.85);
    return col;
}

// The block: wood where its own x is below 0, marble above, with a dark
// seam of glue between. q is in the block's own space, so the texture turns
// with it.
float3 blockColour(float3 q, constant Params& p) {
    float3 m = marble(q * 2.6, (q.x * 1.7 + q.y * 1.1 - q.z * 0.6) * 6.0,
                      1.0 + 5.0 * p.veins, 1e4);
    float3 w = wood(q, p.rings, p.warp);
    float3 col = q.x < 0.0 ? w : m;
    return col * mix(0.35, 1.0, smoothstep(0.004, 0.012, abs(q.x)));
}

// The floor: slabs 1.25 wide, all the same slab, with grout between. Its
// noise wraps every 3 cubes, 3 to a slab, and its stripes make whole turns
// across it, so the edges match. With tiling at 0 the seams show.
float3 floorColour(float2 xz, float tiling, float veins) {
    float2 f = fract(xz / 1.25);
    float3 q = float3(f.x, 0.37, f.y) * 3.0;
    float v = TAU * (f.x + f.y);
    float push = 3.0 + 6.0 * veins;
    float3 tiled = marble(q, v, push, 3.0);
    float3 cut = marble(q, v, push, 1e4);
    float2 e = min(f, 1.0 - f);
    float grout = smoothstep(0.0, 0.008, min(e.x, e.y));
    return mix(cut, tiled, tiling) * float3(0.60, 0.53, 0.50)
         * (0.55 + 0.45 * grout);
}

// ---- The block, turned and cut -----------------------------------------------------------------------------------

// Into the block's own space: turned by `a` about the vertical, tipped by
// `tilt`. toWorld undoes it.
float3 toBlock(float3 v, float a, float tilt) {
    v.xz = rot(v.xz, a);
    v.yz = rot(v.yz, tilt);
    return v;
}

float3 toWorld(float3 v, float a, float tilt) {
    v.yz = rot(v.yz, -tilt);
    v.xz = rot(v.xz, -a);
    return v;
}

// Where a ray in the block's space meets the block, cut by dot(q, n) = c.
// A box is three pairs of planes, the cut one more: the ray is in once past
// every near side, out at the first far side. xyz: the face; w: how far
// along the ray, -1 for a miss.
float4 hitBlock(float3 ro, float3 rd, float3 n, float c) {
    float3 r = rd;
    r.x = abs(r.x) < 1e-5 ? 1e-5 : r.x;
    r.y = abs(r.y) < 1e-5 ? 1e-5 : r.y;
    r.z = abs(r.z) < 1e-5 ? 1e-5 : r.z;
    float3 t0 = (-HALF - ro) / r, t1 = (HALF - ro) / r;
    float3 lo = min(t0, t1), hi = max(t0, t1);
    float near = max(max(lo.x, lo.y), lo.z);
    float far = min(min(hi.x, hi.y), hi.z);
    float3 face = near == lo.x ? float3(-sign(r.x), 0.0, 0.0)
                : near == lo.y ? float3(0.0, -sign(r.y), 0.0)
                : float3(0.0, 0.0, -sign(r.z));
    float dn = dot(r, n), tp = (c - dot(ro, n)) / (abs(dn) < 1e-5 ? 1e-5 : dn);
    if (dn < 0.0 && tp > near) { near = tp; face = n; }
    if (dn > 0.0) far = min(far, tp);
    if (near > far || near < 0.0) return float4(0.0, 0.0, 0.0, -1.0);
    return float4(face, near);
}

// ---- The picture -------------------------------------------------------------------------------------------------

float4 shade(float2 uv, constant Uniforms& u, constant Params& p) {
    float tt = u.time * p.speed;

    // The pointer's offset from the middle turns the camera and tips it.
    float2 steer = u.mouse / u.resolution - 0.5;
    float yaw = 0.5 + steer.x * TAU * 0.5;
    float pitch = clamp(0.40 + steer.y * 1.6, 0.08, 1.3);
    float3 target = float3(0.0, 0.75, 0.0);
    float3 ro = target + 4.7 * float3(cos(pitch) * sin(yaw), sin(pitch),
                                      cos(pitch) * cos(yaw));
    float3 fw = normalize(target - ro);
    float3 rt = normalize(cross(fw, float3(0.0, 1.0, 0.0)));
    float3 up = cross(rt, fw);
    float side = min(u.resolution.x, u.resolution.y);
    float2 s = (uv - 0.5) * u.resolution / side * 2.0;
    float3 rd = normalize(s.x * rt + s.y * up + 2.8 * fw);

    // The block floats, turning and tipped, and the cut sweeps in and out:
    // at c = 1.15 it misses the block, lower slices deeper.
    float3 centre = float3(0.0, 1.0 + 0.05 * sin(tt * 0.9), 0.0);
    float a = tt * 0.45, tilt = 0.45;
    float3 n = normalize(float3(0.55, 0.75, 0.6));
    float c = 1.15 - p.cut * 1.55 * (0.5 - 0.5 * cos(tt * 0.7));

    float3 light = normalize(float3(-0.55, 0.85, 0.45));
    float3 lightCol = float3(1.00, 0.90, 0.78);
    float3 glow = float3(0.90, 0.36, 0.10);

    // Behind everything, the kiln's dark with heat low down.
    float3 col = mix(float3(0.035, 0.022, 0.024), glow * 0.35,
                     exp(-max(rd.y + 0.05, 0.0) * 9.0));

    float3 lro = toBlock(ro - centre, a, tilt);
    float3 lrd = toBlock(rd, a, tilt);
    float4 hb = hitBlock(lro, lrd, n, c);
    float tf = rd.y < 0.0 ? -ro.y / rd.y : -1.0;

    if (hb.w > 0.0 && (tf < 0.0 || hb.w < tf)) {
        float3 q = lro + lrd * hb.w;
        float3 nrm = toWorld(hb.xyz, a, tilt);
        bool isCut = all(hb.xyz == n);
        // A fresh cut is a little paler than the faces the air has darkened.
        float3 base = blockColour(q, p) * (isCut ? 1.08 : 1.0);
        // Wood is satin, marble polished; the fresh cut is rougher.
        bool isMarble = q.x >= 0.0;
        float shine = isMarble ? 60.0 : 14.0;
        float gloss = (isMarble ? 0.55 : 0.18) * (isCut ? 0.4 : 1.0);
        float diff = max(dot(nrm, light), 0.0);
        float3 h = normalize(light - rd);
        float spec = pow(max(dot(nrm, h), 0.0), shine) * gloss;
        float fres = pow(1.0 - max(dot(nrm, -rd), 0.0), 4.0);
        // The kiln's heat lights the faces turned down and away.
        float bounce = max(-nrm.y, 0.0) * 0.5 + 0.25;
        col = base * (lightCol * diff * 1.05 + glow * bounce * 0.45 + 0.16)
            + lightCol * spec + glow * fres * 0.25;
    } else if (tf > 0.0) {
        float3 pos = ro + rd * tf;
        float3 base = floorColour(pos.xz, p.tiling, p.veins);
        // The block's shadow: does the way to the light meet it?
        float4 sh = hitBlock(toBlock(pos - centre, a, tilt),
                             toBlock(light, a, tilt), n, c);
        float lit = sh.w > 0.0 ? 0.3 : 1.0;
        // Darker straight under the block, where little light reaches.
        float under = 1.0 - 0.45 * exp(-length(pos.xz) * 1.6);
        float3 h = normalize(light - rd);
        float spec = pow(max(h.y, 0.0), 80.0) * 0.35 * lit;
        col = base * (lightCol * light.y * lit * 0.9 + 0.12) * under
            + lightCol * spec;
        // The floor fades into the kiln's dark far off.
        col = mix(col, float3(0.035, 0.022, 0.024) + glow * 0.12,
                  smoothstep(4.0, 13.0, tf));
    }

    col = col / (1.0 + col * 0.25);
    return float4(col, 1.0);
}

// ---- Sound -------------------------------------------------------------------------------------------------------

// A random number from a whole number.
float hash11(float n) {
    return fract(sin(n * 12.9898) * 43758.5453);
}

// One note of a pattern of 16 steps. Wood knocks low and dies fast, its
// fourth harmonic tuned as a marimba bar's is; marble rings high and long,
// with partials out of tune with each other, as stone does.
float2 note(float age, float key) {
    if (hash11(key * 3.1 + 0.7) < 0.35) return float2(0.0);
    bool stone = hash11(key * 5.3 + 1.9) < 0.4;
    float deg = floor(hash11(key * 7.7 + 4.2) * 5.0);
    float semis = deg < 1.0 ? 0.0 : deg < 2.0 ? 3.0 : deg < 3.0 ? 5.0
                : deg < 4.0 ? 7.0 : 10.0;
    float f = (stone ? 880.0 : 146.8) * exp2(semis / 12.0);
    float v;
    if (stone) {
        v = 0.45 * sin(TAU * f * age) * exp(-age * 5.0)
          + 0.25 * sin(TAU * f * 2.32 * age) * exp(-age * 9.0)
          + 0.15 * sin(TAU * f * 4.17 * age) * exp(-age * 15.0);
    } else {
        v = sin(TAU * f * age) * exp(-age * 7.0)
          + 0.35 * sin(TAU * f * 4.0 * age) * exp(-age * 22.0);
    }
    v *= smoothstep(0.0, 0.003, age);
    float pan = 0.5 + 0.35 * (hash11(key * 9.1) - 0.5) * 2.0;
    return v * float2(1.0 - pan, pan);
}

float2 sound(float t, constant Params& p) {
    // Steps faster with speed. With tiling up, the pattern's 16 steps repeat
    // like the floor's slabs; with it down, every step is new.
    float rate = 2.5 + 2.0 * p.speed;
    float x = t * rate;
    float2 knocks = float2(0.0);
    // A note rings on for four steps, by when even stone has died away.
    for (int k = 0; k < 4; k++) {
        float i = floor(x) - float(k);
        if (i < 0.0) continue;
        float age = (x - i) / rate;
        float key = p.tiling > 0.5 ? wrap(i, 16.0) : i;
        knocks += note(age, key);
    }

    // Under it, a hum of the marble's own two notes, swelling as the cut
    // goes in and out.
    float swell = 0.5 + 0.5 * cos(t * p.speed * 0.7);
    float hum = sin(TAU * 110.0 * t) + 0.6 * sin(TAU * 164.8 * t + 0.3)
              + 0.25 * sin(TAU * 220.0 * t * 1.003);
    hum *= 0.14 * (0.4 + 0.6 * swell * p.cut);

    float2 mixdown = knocks * 0.8 + float2(hum);
    float fadeIn = smoothstep(0.0, 1.5, t);
    return tanh(mixdown * 1.4) * 0.7 * 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);
}