#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 glow;
    float flicker;
    float wall;
    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 23 · Neon Sign
//
// @episode 23 Neon Sign
// @length 60
// @variation 30
// @variations 1,2,3,5,6,8,9,11
// @still 12.0
// @teaches 2D distance-field strokes, glow, flicker
// @category Fragment
// @tags signed distance fields, strokes, glow, flicker, neon, procedural sound
// @final Neon sign | The sign on a brick wall at night: each tube lights the bricks around it, casts a shadow below it, and the faulty ones stutter and buzz.
//
// A neon sign, the kiln's mark over the word OPEN, hung on a brick wall at night. Every tube is a stroke: the distance
// to a line, a ring or an arch, measured to its middle rather than its inside, so a shape's outline is all there is.
// Close to that line the tube is white-hot glass; farther out its colour falls away as a glow, a little light for every
// pixel however far. Each tube has a power that the clock switches on and off: the sign warms up when it starts, and
// the faulty tubes stutter in bursts. The wall is lit by the same glow, and the shadow of each tube falls on it below.
// 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 1.0 0.0 3.0 How fast the faults come and go
// @param glow 0.6 0.0 1.0 How far the light spreads from the tubes
// @param flicker 0.5 0.0 1.0 How faulty the sign is, from new to about to fail
// @param wall 0.5 0.0 1.0 How much street light falls on the brick wall
// @param volume 0.8 0.0 1.0 Loudness of the sound

// ---- Strokes: distances to a line, not to a shape's inside ------------------

// The distance to the segment from a to b: along it, the nearest point is
// clamped to the ends.
float sdSegment(float2 p, float2 a, float2 b) {
    float2 pa = p - a, ba = b - a;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    return length(pa - ba * h);
}

// The distance to the right half of a ring around c: the ring's own distance on
// its side, its two ends on the other.
float halfRing(float2 p, float2 c, float r) {
    p -= c;
    if (p.x > 0.0) return abs(length(p) - r);
    return length(float2(p.x, abs(p.y) - r));
}

// The distance to an arch's outline: two legs of height h, w either side of
// `base`, and a half ring over them.
float archLine(float2 p, float2 base, float w, float h) {
    p -= base;
    p.x = abs(p.x);
    if (p.y > h) return abs(length(p - float2(0.0, h)) - w);
    return length(p - float2(w, clamp(p.y, 0.0, h)));
}

// ---- The sign: seven tubes --------------------------------------------------

constant int tubeCount = 7;

// Tube i's distance at p. 0 to 3 are the letters O P E N, 4 the arch and its
// hearth, 5 the heated interior, 6 the rays.
float tube(int i, float2 p) {
    const float lo = -0.75, hi = -0.25, mid = -0.51;
    if (i == 0) {
        // O: a stadium's outline, the distance to a short upright line less the
        // letter's half width.
        float d = sdSegment(p, float2(-0.75, -0.58), float2(-0.75, -0.42));
        return abs(d - 0.17);
    }
    if (i == 1) {
        float x = -0.42;
        float d = sdSegment(p, float2(x, lo), float2(x, hi));
        d = min(d, sdSegment(p, float2(x, hi), float2(x + 0.19, hi)));
        d = min(d, sdSegment(p, float2(x, mid), float2(x + 0.19, mid)));
        float2 c = float2(x + 0.19, (hi + mid) * 0.5);
        return min(d, halfRing(p, c, (hi - mid) * 0.5));
    }
    if (i == 2) {
        float x = 0.08;
        float d = sdSegment(p, float2(x, lo), float2(x, hi));
        d = min(d, sdSegment(p, float2(x, hi), float2(x + 0.3, hi)));
        d = min(d, sdSegment(p, float2(x, -0.5), float2(x + 0.24, -0.5)));
        return min(d, sdSegment(p, float2(x, lo), float2(x + 0.3, lo)));
    }
    if (i == 3) {
        float x = 0.56;
        float d = sdSegment(p, float2(x, lo), float2(x, hi));
        d = min(d, sdSegment(p, float2(x, hi), float2(x + 0.32, lo)));
        return min(d, sdSegment(p, float2(x + 0.32, lo), float2(x + 0.32, hi)));
    }
    if (i == 4) {
        float d = archLine(p, float2(0.0, -0.05), 0.3, 0.32);
        return min(d, sdSegment(p, float2(-0.48, -0.05), float2(0.48, -0.05)));
    }
    if (i == 5) return archLine(p, float2(0.0, -0.05), 0.14, 0.26);
    // Five rays around the arch's top, each a short line pointing out from its
    // centre.
    float d = 1e9;
    for (int k = 0; k < 5; k++) {
        float a = (30.0 + 30.0 * float(k)) * 0.01745329;
        float2 dir = float2(cos(a), sin(a));
        float2 c = float2(0.0, 0.27);
        d = min(d, sdSegment(p, c + dir * 0.44, c + dir * 0.6));
    }
    return d;
}

// Each tube's colour: the letters a red neon, the arch gold, the interior
// ember, the rays cream.
float3 tubeColour(int i) {
    if (i < 4) return float3(1.0, 0.16, 0.05);
    if (i == 4) return float3(1.0, 0.6, 0.16);
    if (i == 5) return float3(1.0, 0.36, 0.07);
    return float3(1.0, 0.86, 0.62);
}

// ---- Flicker: each tube's power from the clock ------------------------------

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

// How often a tube's state can change: 16 steps a second of fault time.
constant float flickSteps = 16.0;

// Tube i on (1) or off (0) in step k of fault time, `time` seconds after the
// sign was switched on: a stutter as it strikes, then now and then a fault,
// more often the higher `flicker`, in which each step is a coin toss.
float tubeOn(int i, float k, float time, float flicker) {
    float strike = 0.3 + 0.22 * float(i);
    if (time < strike) return 0.0;
    uint stutter = uint(time * 30.0) * 7u + uint(i) * 131u;
    if (time < strike + 0.4) return step(0.45, hashU(stutter));
    uint ki = uint(max(k, 0.0));
    float burst = hashU((ki / 32u) * 17u + uint(i) * 977u + 5u);
    float chance = flicker * (i == 3 ? 0.95 : 0.3);
    if (burst < chance) return step(0.5, hashU(ki * 31u + uint(i) * 7919u));
    return 1.0;
}

// ---- The wall ---------------------------------------------------------------

// Bricks of 0.36 by 0.15, every other row moved by half a brick: each brick's
// own shade from a hash of its number, a rough face, and mortar between them.
// Gives the brick's colour and how much of it is mortar.
float3 bricks(float2 p, thread float& mortar) {
    float2 size = float2(0.36, 0.15);
    float row = floor(p.y / size.y);
    float2 q = float2(p.x / size.x + 0.5 * fmod(abs(row), 2.0), p.y / size.y);
    float2 cell = floor(q), f = fract(q);
    float2 edge = min(f, 1.0 - f) * size;
    mortar = 1.0 - smoothstep(0.006, 0.012, min(edge.x, edge.y));
    uint2 n = uint2(cell + 512.0), g = uint2(p * 900.0 + 9000.0);
    float tone = hashU(n.x * 73u + n.y * 1361u);
    float grit = hashU(g.x * 3u + g.y * 7919u);
    float3 brick = mix(float3(0.42, 0.16, 0.1), float3(0.58, 0.26, 0.15), tone);
    brick *= 0.85 + 0.15 * grit;
    return mix(brick, float3(0.30, 0.27, 0.24), mortar);
}

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 + float2(0.0, 0.08);
    float k = floor(u.time * p.speed * flickSteps);

    // The tubes, each as glass and as light; the light on the wall, and the
    // tubes' shadow below and to the right.
    const float radius = 0.012;
    float3 light = float3(0.0), glass = float3(0.0), onWall = float3(0.0);
    float near = 1e9, shadow = 1e9;
    for (int i = 0; i < tubeCount; i++) {
        float d = tube(i, q);
        float on = tubeOn(i, k, u.time, p.flicker);
        float3 c = tubeColour(i);
        // The glass: a hot middle near white, its colour at the edge, a dull
        // grey tube when it is off.
        float body = 1.0 - smoothstep(radius * 0.5, radius + px, d);
        float hot = 1.0 - smoothstep(0.0, radius * 0.6, d);
        glass += body * mix(float3(0.10, 0.09, 0.09), c * 1.6 + hot * 1.2, on);
        // The glow: an inverse square of the distance, so it never quite ends,
        // scaled by `glow`.
        float g = radius * radius / (d * d + radius * radius);
        float wide = exp(-d * (14.0 - 10.0 * p.glow));
        light += on * c * (0.6 * g + p.glow * 0.35 * wide);
        onWall += on * c * exp(-d * 3.0) * (0.15 + 0.35 * p.glow);
        near = min(near, d);
        shadow = min(shadow, tube(i, q + float2(-0.012, 0.03)));
    }

    // The wall, lit by a little street light from above and by the sign; darker
    // in the tubes' shadow.
    float mortar;
    float3 wall = bricks(q, mortar);
    float street = p.wall * (0.12 + 0.1 * smoothstep(-1.2, 1.2, q.y));
    float unshaded = mix(0.35, 1.0, smoothstep(0.0, 0.025, shadow - radius));
    float3 col = wall * (street + onWall) * unshaded;

    // The glass over the wall, then the glow added on top of both: light, not
    // paint.
    float cover = 1.0 - smoothstep(radius * 0.5, radius + px, near);
    col = mix(col, glass, cover) + light;

    // Bright light goes to white rather than clipping its colour.
    col = 1.0 - exp(-col * 1.4);
    return float4(col, 1.0);
}

// ---- Sound ------------------------------------------------------------------
// The sign's transformer hums at 120 Hz with its overtones, louder the more
// tubes are lit, and a tube that switches on or off crackles, heard from where
// it is on the sign. A tube in a fault buzzes. The hum hears each tube as the
// average of four moments 1.5 ms apart, so a switch fades over 4.5 ms and never
// clicks.

// Tube i on or off at `t` seconds, as the picture has it.
float tubeAt(int i, float t, constant Params& p) {
    return tubeOn(i, floor(t * p.speed * flickSteps), t, p.flicker);
}

float2 sound(float t, constant Params& p) {
    const float tau = 6.2831853;
    float tt = t * p.speed * flickSteps;
    float k = floor(tt);
    // Seconds since this step of fault time began (a stopped clock has none).
    float age = p.speed > 0.0 ? fract(tt) / (p.speed * flickSteps) : 1.0;
    float noise = hashU(uint(t * 44100.0)) * 2.0 - 1.0;

    float lit = 0.0;
    float2 crackle = float2(0.0);
    for (int i = 0; i < tubeCount; i++) {
        float now = tubeOn(i, k, t, p.flicker);
        float before = tubeOn(i, k - 1.0, max(t - age, 0.0), p.flicker);
        for (int j = 0; j < 4; j++) {
            lit += tubeAt(i, max(t - 0.0015 * float(j), 0.0), p) * 0.25;
        }
        // A switch: a burst of noise dying away in about 15 ms, from the tube's
        // side of the sign.
        float pan = i < 4 ? 0.125 + 0.25 * float(i) : 0.5;
        float burst = abs(now - before) * exp(-age * 70.0);
        crackle += burst * float2(1.0 - pan, pan);
    }
    lit /= float(tubeCount);

    float hum = 0.0;
    for (int n = 1; n <= 6; n++) {
        hum += sin(tau * 120.0 * float(n) * t) / float(n * n);
    }
    float buzz = clamp(sin(tau * 120.0 * t) * 3.0, -1.0, 1.0);
    float faulty = 0.0;
    for (int j = 0; j < 4; j++) {
        faulty += (1.0 - tubeAt(3, max(t - 0.0015 * float(j), 0.0), p)) * 0.25;
    }

    float2 s = float2(hum * 0.16 * lit + buzz * 0.03 * lit * p.flicker);
    s += crackle * noise * 0.22;
    s += faulty * float2(0.03, 0.05) * sin(tau * 60.0 * t);
    return s * 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);
}