#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 cells;
    float turns;
    float order;
    float line;
    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 17 · Kiln Floor Tiles
//
// @episode 17 Kiln Floor Tiles
// @length 60
// @variation 30
// @variations 2,3,4,6,7,8,9,12
// @sweeps cells, line, heat
// @still 20.0
// @teaches Hex grids, Truchet tiles, wallpaper symmetry
// @category Fragment
// @tags hex grid, truchet tiles, wallpaper symmetry, periodic noise, procedural sound
// @final Kiln floor | Clay tiles with bevelled edges, fire showing through the grout, the glowing channels lit by a light that wanders and wraps round the picture.
//
// The floor of a kiln laid with hexagonal tiles. Each tile carries three arcs of glowing glaze, and since every arc
// ends at the middle of an edge, the arcs join their neighbours' into lines that wind across the whole floor: Truchet
// tiles. Now and then a tile turns a sixth of the way round and the lines join up another way. The floor is a
// wallpaper: it fits a whole number of tiles in the picture, and everything on it, the tiles, the heat flowing along
// the channels and the light, repeats with the picture, so copies laid side by side 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 heat flows and the tiles turn
// @param cells 9.0 3.0 16.0 How many tiles across the picture
// @param turns 0.5 0.0 1.0 How often a tile turns a sixth of the way round
// @param order 0.0 0.0 1.0 From tiles laid at random to every tile laid the same way
// @param line 0.5 0.0 1.0 How wide the glowing channels are
// @param heat 0.5 0.0 1.0 Slides the glow from deep ember to white-hot
// @param volume 0.8 0.0 1.0 Loudness of the sound

constant float TAU = 6.2831853;
constant float SQ3 = 1.7320508;
// How long a turn takes.
constant float TURN = 0.8;

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

// Episode 1's hash: a random number from a point.
float hash21(float2 p) {
    p = fract(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

// Wrap x into 0 to m, below 0 too.
float wrap(float x, float m) {
    return x - m * floor(x / m);
}

// ---- The hex grid ----------------------------------------------------------------------------------------------

// Whole tiles across, whole pairs of rows down: the picture holds whole
// tiles both ways.
float2 floorSize(float2 res, float cells) {
    float n = floor(cells + 0.5);
    float pairs = max(floor(n * res.y / (res.x * SQ3) + 0.5), 1.0);
    return float2(n, pairs);
}

// Two grids of rectangles, one shifted by half: a point belongs to the
// nearer centre. xy is the point from its centre, zw the centre itself.
float4 hexCell(float2 p) {
    float2 r = float2(1.0, SQ3), h = r * 0.5;
    float2 a = p - r * floor(p / r) - h;
    float2 b = (p - h) - r * floor((p - h) / r) - h;
    float2 gv = dot(a, a) < dot(b, b) ? a : b;
    return float4(gv, p - gv);
}

// A tile's number, wrapped: the tile leaving one edge is the one coming
// in at the other.
float2 tileKey(float2 centre, float2 size) {
    float col = floor(centre.x * 2.0 + 0.5);
    float row = floor(centre.y * 2.0 / SQ3 + 0.5);
    return float2(wrap(col, size.x * 2.0), wrap(row, size.y * 2.0));
}

// Distance in the hexagon's own measure: 0.5 on its edges.
float hexDist(float2 gv) {
    gv = abs(gv);
    return max(gv.x, dot(gv, float2(0.5, SQ3 * 0.5)));
}

// ---- Truchet arcs ----------------------------------------------------------------------------------------------

// Three arcs round every other corner of the tile. Each joins the middles
// of the two edges beside its corner, and meets them square, so it carries
// straight on into the neighbour's arc. Turned a sixth, the other three
// corners take them.
float truchet(float2 gv) {
    float R = 1.0 / SQ3;
    float d = 1e3;
    for (int k = 0; k < 3; k++) {
        float a = TAU / 12.0 + float(k) * TAU / 3.0;
        float2 corner = R * float2(cos(a), sin(a));
        d = min(d, abs(length(gv - corner) - R * 0.5));
    }
    return d;
}

// How far a tile has turned, in sixths: 0 or 1 by its hash, then one
// more every `every` seconds, eased over TURN.
float tileTurn(float2 key, float tt, float order, float every) {
    float base = step(hash21(key + 3.1), 0.5 * (1.0 - order));
    if (every <= 0.0) return base;
    float offset = hash21(key + 7.7);
    float k = floor(tt / every - offset);
    float since = tt - (k + offset) * every;
    float e = smoothstep(0.0, TURN, since);
    return base + k + e;
}

// Seconds between a tile's turns; none for turns 0.
float turnEvery(float turns) {
    return turns > 0.01 ? 6.0 / turns : 0.0;
}

// ---- Heat that repeats with the floor --------------------------------------------------------------------------

// Value noise on a lattice that wraps every `period` cells (whole numbers),
// so it repeats exactly. Moving it keeps it repeating.
float pnoise(float2 q, float2 period) {
    float2 i = floor(q), f = fract(q);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(float2(wrap(i.x, period.x), wrap(i.y, period.y)));
    float b = hash21(float2(wrap(i.x + 1.0, period.x), wrap(i.y, period.y)));
    float c = hash21(float2(wrap(i.x, period.x), wrap(i.y + 1.0, period.y)));
    float d = hash21(float2(wrap(i.x + 1.0, period.x),
                            wrap(i.y + 1.0, period.y)));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

// Three octaves, each lattice twice as fine and still whole: it repeats too.
float pfbm(float2 uv, float2 period, float2 drift) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 3; i++) {
        v += a * pnoise(uv * period + drift, period);
        period *= 2.0;
        drift *= 2.0;
        a *= 0.5;
    }
    return v / 0.875;
}

// ---- The floor -------------------------------------------------------------------------------------------------

float4 shade(float2 uv, constant Uniforms& u, constant Params& p) {
    float2 size = floorSize(u.resolution, p.cells);
    float2 sp = uv * float2(size.x, size.y * SQ3);
    float4 cell = hexCell(sp);
    float2 key = tileKey(cell.zw, size);
    float tt = u.time * p.speed;

    // Lifted a little while it turns, so the grout opens round it.
    float turn = tileTurn(key, tt, p.order, turnEvery(p.turns));
    float lift = sin(3.14159 * fract(turn)) * step(0.001, fract(turn));
    float2 gv = rot(cell.xy, turn * TAU / 6.0) * (1.0 + 0.1 * lift);

    float aa = 1.5 * size.x / u.resolution.x;
    float edge = 0.5 - hexDist(gv);
    float d = truchet(gv);

    // The heat under the floor, flowing, on a lattice of whole tiles.
    float2 period = float2(size.x, size.y * 2.0);
    float flow = pfbm(uv, period, float2(tt * 0.6, tt * 0.25));
    float glowT = 0.12 + p.heat * 0.5 + (flow - 0.35) * 0.8;

    // The clay: a shade of its own for each tile, a bevel lit from the top
    // left, and grout between the tiles where the fire shows through.
    float shadeOf = hash21(key + 11.3);
    float3 clay = mix(float3(0.20, 0.11, 0.08), float3(0.36, 0.22, 0.15),
                      shadeOf);
    float2 nrm = abs(gv.x) > dot(abs(gv), float2(0.5, SQ3 * 0.5))
        ? float2(sign(gv.x), 0.0) : sign(gv) * float2(0.5, SQ3 * 0.5);
    float bevel = 1.0 - smoothstep(0.0, 0.07, edge);
    clay *= 1.0 + bevel * 0.9 * dot(nrm, normalize(float2(-0.6, 0.8)));

    // A light wandering over the floor. Its distance is measured round the
    // picture's edges, as on a torus, so it repeats with the floor too.
    float2 at = float2(0.5 + 0.3 * sin(tt * 0.21), 0.5 + 0.3 * cos(tt * 0.17));
    float2 dl = uv - at;
    dl -= floor(dl + 0.5);
    dl.x *= u.resolution.x / u.resolution.y;
    float light = 0.45 + 0.9 * exp(-dot(dl, dl) * 5.0);
    float3 col = clay * light;

    // The channels: glaze glowing with the heat, spilling light on the clay.
    // Hottest down the middle of the channel.
    float w = mix(0.02, 0.09, p.line);
    float channel = 1.0 - smoothstep(w - aa, w + aa, d);
    float3 hot = kiln(glowT);
    col += hot * exp(-max(d - w, 0.0) * 22.0) * 0.3;
    float core = 1.0 - d / max(w, 1e-3);
    col = mix(col, hot * (0.8 + 0.9 * core), channel);

    // Grout: dark, with the fire glowing deep in it.
    float gap = 0.012 + 0.03 * lift;
    float grout = 1.0 - smoothstep(gap, gap + aa, edge);
    float3 deep = kiln(glowT * 0.75) * 0.5;
    col = mix(col, deep, grout);

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

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

// Episode 5's sound hash: a random number from a whole number.
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.
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) {
    // The floor as the 16:9 picture lays it: the same tiles, the same turns.
    float2 size = floorSize(float2(16.0, 9.0), p.cells);
    float tt = t * p.speed;
    float every = turnEvery(p.turns);
    float s = max(p.speed, 0.01);

    // Each tile set down after its turn clinks: a ceramic ring, three
    // partials not in tune with each other, its pitch from the tile's number
    // on a pentatonic scale, from where it lies on the floor.
    float2 clinks = float2(0.0);
    if (every > 0.0 && p.speed > 0.001) {
        for (float row = 0.0; row < size.y * 2.0; row += 1.0) {
            for (float col = wrap(row, 2.0); col < size.x * 2.0; col += 2.0) {
                float2 key = float2(col, row);
                float offset = hash21(key + 7.7);
                float k = floor(tt / every - offset);
                float age = (tt - (k + offset) * every - TURN) / s;
                if (k < 0.0 || age < 0.0 || age > 1.2) continue;
                float note = floor(hash21(key + 11.3) * 10.0);
                float semis = floor(note / 5.0) * 12.0
                    + float(int(note) % 5) * 2.4;
                float f = 660.0 * exp2(floor(semis + 0.5) / 12.0);
                float ring = sin(TAU * f * age) * exp(-age * 9.0)
                           + 0.5 * sin(TAU * f * 2.76 * age) * exp(-age * 16.0)
                           + 0.25 * sin(TAU * f * 5.4 * age) * exp(-age * 28.0);
                ring *= smoothstep(0.0, 0.002, age);
                float pan = col / (size.x * 2.0);
                clinks += ring * float2(1.0 - pan, pan);
            }
        }
    }
    clinks *= 0.26;

    // The kiln under the floor: a low drone, a fifth above it, swelling with
    // the heat, and a roar of slow noise.
    float swell = 0.6 + 0.4 * sin(tt * 0.6);
    float drone = sin(TAU * 73.4 * t) + 0.5 * sin(TAU * 110.0 * t + 0.4);
    drone *= 0.24 * (0.4 + 0.6 * p.heat) * swell;
    float roar = noise1(t, 140.0, 1u) * 0.6 + noise1(t, 420.0, 2u) * 0.4;
    roar *= 0.19 * (0.5 + 0.5 * p.heat);

    // The glaze fizzing in its channels: a little bright hiss, more for
    // wider and hotter channels.
    float fizz = noise1(t, 5200.0, 3u) * 0.03 * p.line * (0.3 + p.heat);

    float2 mixdown = float2(drone + roar) + float2(fizz, -fizz) + clinks;
    float fadeIn = smoothstep(0.0, 1.5, t);
    return tanh(mixdown * 1.6) * 0.66 * 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);
}