Night Train

by MusicBar, progress bar

A night train rides an elevated line to the playhead, its headlight cutting ahead over the dark rail, warm windows full of passengers, poles whipping past and the pantograph sparking on every beat.

27 downloads Download file, 9.5 KB
How it installs, its SHA-256 and its code

Click Apply: MusicBar downloads it, checks its SHA-256 and puts it on the taskbar, or just puts it on if it already has it. Needs MusicBar 1.8 or newer.

SHA-256 d964aa873b2ca92d688fb188ce166600770e7e17542292173bcf87eb3639d664

//! name: Night Train
//! author: MusicBar
//! kind: progress
//! description: A night train rides an elevated line to the playhead, its headlight cutting ahead over the dark rail, warm windows full of passengers, poles whipping past and the pantograph sparking on every beat.

float hash2(float2 p)
{
    float3 q = frac(float3(p.x, p.y, p.x) * 0.1031);
    q += dot(q, q.yzx + 33.33);
    return frac((q.x + q.y) * q.z);
}

// Lays a color over what is there (premultiplied light, and how much of the pixel it covers).
void layer(inout float3 acc, inout float cover, float3 rgb, float a)
{
    a = saturate(a);
    acc = rgb * a + acc * (1.0 - a);
    cover = a + cover * (1.0 - a);
}

float segD(float2 p, float2 a, float2 b)
{
    float2 pa = p - a, ba = b - a;
    return length(pa - ba * saturate(dot(pa, ba) / dot(ba, ba)));
}

// Rounded box with its own radius per corner (top right, bottom right, top left, bottom left).
float boxD(float2 p, float2 b, float4 r)
{
    r.xy = p.x > 0.0 ? r.xy : r.zw;
    r.x = p.y > 0.0 ? r.x : r.y;
    float2 q = abs(p) - b + r.x;
    return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    // Pixels, y up the screen; the world slides back at the train's speed.
    float H = size.y;
    float2 px = float2(uv.x * size.x, (1.0 - uv.y) * H);
    float head = progress * size.x;
    float played = smoothstep(head - 0.2 * H, head - 1.2 * H, px.x);
    float run = px.x + time * 7.0 * H;
    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // City lights far behind, out of focus, drifting slowly back.
    float2 bq = (px + float2(time * 0.3 * H, 0.0)) / (0.42 * H);
    float2 bc = floor(bq);
    float bh = hash2(bc + 3.0);
    float bd = length(frac(bq) - 0.5 - (float2(hash2(bc + 7.0), hash2(bc + 11.0)) - 0.5) * 0.35);
    float br = 0.14 + 0.14 * hash2(bc + 13.0);
    float3 bcol = bh < 0.6 ? lerp(float3(1.0, 0.78, 0.5), colorC.rgb, 0.3) : (bh < 0.8 ? colorA.rgb : colorB.rgb);
    layer(acc, cover, bcol, smoothstep(br, br - 0.07, bd) * step(0.5, hash2(bc + 17.0)) * played * smoothstep(0.25 * H, 0.5 * H, px.y) * 0.24);

    // Catenary poles whipping past behind the train, and the contact wire they carry.
    float Pp = 2.7 * H;
    float lx = frac(run / Pp) * Pp - 0.5 * Pp;
    float pole = smoothstep(0.07 * H, 0.015 * H, abs(lx)) * step(0.13 * H, px.y);
    float arm = smoothstep(0.035 * H + 0.5, 0.035 * H - 0.5, segD(float2(lx, px.y), float2(0.0, 0.84 * H), float2(0.45 * H, 0.93 * H)));
    layer(acc, cover, float3(0.2, 0.21, 0.26), max(pole, arm) * played * 0.85);
    float wy = 0.935 * H - 0.03 * H * sin(3.14159 * frac(run / Pp + 0.5));
    layer(acc, cover, float3(0.45, 0.45, 0.52), smoothstep(max(0.5, 0.012 * H) + 0.5, max(0.5, 0.012 * H) - 0.5, abs(px.y - wy)) * played);

    // The headlight beam ahead of the nose, and the rail it lights.
    float ahead = px.x - head;
    float beam = exp(-ahead / (5.5 * H)) * smoothstep(0.05 * H + ahead * 0.13, 0.0, abs(px.y - 0.3 * H + ahead * 0.035)) * smoothstep(0.0, 0.3 * H, ahead);
    float3 lamp = lerp(float3(1.0, 0.95, 0.8), colorC.rgb, 0.25);
    layer(acc, cover, lamp, beam * (0.32 + 0.1 * level));

    // The elevated deck: a concrete beam on piers sliding past, the rail glinting under the light.
    float pier = step(px.y, 0.06 * H) * smoothstep(0.17 * H, 0.15 * H, abs(frac(run / (3.3 * H)) - 0.5) * 3.3 * H);
    float joint = smoothstep(0.03 * H, 0.0, abs(frac(run / (1.1 * H)) - 0.5) * 1.1 * H);
    float deck = step(0.05 * H, px.y) * step(px.y, 0.13 * H);
    float lit = exp(-max(ahead, 0.0) / (6.0 * H)) * step(0.0, ahead) + played * 0.35;
    float3 concrete = float3(0.12, 0.12, 0.15) * (1.0 - 0.35 * joint) + lamp * 0.25 * lit;
    layer(acc, cover, concrete * 0.8, max(pier, deck));
    float rail = smoothstep(1.0, 0.0, abs(px.y - 0.14 * H) - 0.012 * H);
    layer(acc, cover, lerp(float3(0.3, 0.31, 0.36), lamp, saturate(lit)), rail);

    // The train: a locomotive with its nose at the playhead and four carriages behind.
    float b = head - px.x;
    float Lh = 2.7 * H, Lc = 2.4 * H, gap = 0.14 * H;
    float n = b < Lh + 0.5 * gap ? 0.0 : 1.0 + floor((b - Lh - 0.5 * gap) / (Lc + gap));
    float loco = step(n, 0.5);
    float hl = lerp(0.5 * Lc, 0.5 * Lh, loco);
    float cb = loco > 0.5 ? hl : Lh + gap + (n - 1.0) * (Lc + gap) + hl;
    float tx = cb - b;
    float yb = 0.012 * H * sin(time * 8.0 + n * 1.7) + 0.02 * H * beat * sin(n * 2.1 + 1.0);
    float y = px.y - yb;
    float exists = step(n, 4.5) * step(0.0, b + 0.5);
    float4 radii = loco > 0.5 ? float4(0.42, 0.13, 0.07, 0.05) * H : float4(0.07, 0.05, 0.07, 0.05) * H;
    float sd = boxD(float2(tx, y - 0.47 * H), float2(hl, 0.25 * H), radii);

    // Bellows between the cars, then bogies and wheels.
    float train = step(b, Lh + 4.0 * (Lc + gap) - gap - 0.1 * H) * step(0.5 * Lh, b);
    layer(acc, cover, float3(0.07, 0.07, 0.09), train * step(0.3 * H, y) * step(y, 0.64 * H));
    float bx = tx - sign(tx) * (hl - 0.45 * H);
    float wx = bx - sign(bx) * 0.15 * H;
    float wheel = length(float2(wx, px.y - 0.2 * H)) - 0.058 * H;
    float glint = pow(saturate(cos(atan2(px.y - 0.2 * H, wx) + time * 25.0)), 6.0);
    layer(acc, cover, float3(0.18, 0.18, 0.22), exists * smoothstep(0.5, -0.5, boxD(float2(bx, px.y - 0.23 * H), float2(0.28, 0.035) * H, 0.02 * H)));
    layer(acc, cover, float3(0.06, 0.06, 0.08) + 0.35 * glint * smoothstep(-0.03 * H, 0.0, wheel) + lamp * 0.2 * smoothstep(-1.5, 0.0, wheel), exists * smoothstep(0.5, -0.5, wheel));

    // The body: dark painted steel lit from above, a glowing palette stripe and a thin pin line.
    float3 body = float3(0.11, 0.12, 0.16) + 0.1 * smoothstep(0.3 * H, 0.72 * H, y) + 0.3 * smoothstep(1.6, 0.0, -sd);
    body = lerp(body, colorA.rgb * 1.1, smoothstep(0.6, -0.4, abs(y - 0.34 * H) - 0.025 * H));
    body = lerp(body, colorB.rgb, smoothstep(0.6, -0.4, abs(y - 0.665 * H) - 0.008 * H - 0.3));

    // Windows down the side, warm, a passenger in most; the locomotive has its cab glass instead.
    float pitch = 0.37 * H, wEnd = hl - lerp(0.2, 1.1, loco) * H;
    float wN = floor((wEnd + hl - 0.2 * H) / pitch);
    float wl = tx - (wEnd - hl + 0.2 * H) * 0.5 + wN * pitch * 0.5;
    float wcell = floor(wl / pitch), wlx = (frac(wl / pitch) - 0.5) * pitch;
    float wvalid = step(0.0, wl) * step(wl, wN * pitch);
    float wd = boxD(float2(wlx, y - 0.545 * H), float2(0.115, 0.085) * H, 0.03 * H);
    float ph = hash2(float2(n * 7.0 + 1.0, wcell));
    float px0 = wlx - (ph - 0.5) * 0.12 * H;
    float person = min(length(float2(px0, y - 0.56 * H)) - 0.042 * H, boxD(float2(px0, y - 0.48 * H), float2(0.075, 0.045) * H, 0.035 * H));
    float3 cabin = lerp(float3(1.0, 0.8, 0.52), colorC.rgb, 0.3) * (0.8 + 0.25 * smoothstep(0.46 * H, 0.63 * H, y)) * (0.9 + 0.15 * level);
    cabin = lerp(cabin, float3(0.16, 0.09, 0.07), smoothstep(0.5, -0.5, person) * step(0.3, ph));
    body = lerp(body, cabin, smoothstep(0.5, -0.5, wd) * wvalid);
    float gd = max(max(sd + 0.035 * H, 0.53 * H - y), hl - 0.6 * H - (y - 0.53 * H) * 0.9 - tx);
    float streak = smoothstep(0.02 * H, 0.0, abs(tx - hl + 0.45 * H + (y - 0.53 * H) * 0.9 - 0.06 * H) - 0.02 * H);
    body = lerp(body, float3(0.05, 0.07, 0.12) + lerp(colorB.rgb, float3(1, 1, 1), 0.5) * 0.4 * streak, loco * smoothstep(0.5, -0.5, gd));
    layer(acc, cover, body, exists * smoothstep(0.5, -0.5, sd));

    // Roof units on the carriages.
    float roof = boxD(float2(frac((tx + hl) / (0.8 * H)) * 0.8 * H - 0.4 * H, y - 0.725 * H), float2(0.24, 0.035) * H, 0.02 * H);
    layer(acc, cover, float3(0.16, 0.17, 0.21) + 0.15 * smoothstep(0.73 * H, 0.76 * H, y), exists * (1.0 - loco) * step(abs(tx), hl - 0.3 * H) * smoothstep(0.5, -0.5, roof));
    layer(acc, cover, cabin, exists * wvalid * exp(-max(wd, 0.0) / (0.05 * H)) * 0.25 * step(0.0, sd));

    // Pantograph on the locomotive's roof reaching up to the wire, sparking on the beat.
    float pb = head - Lh * 0.5 - 0.5 * H;
    float2 C = float2(pb - 0.05 * H, 0.93 * H);
    float pan = min(segD(px, float2(pb - 0.18 * H, 0.74 * H + yb), float2(pb + 0.1 * H, 0.84 * H)), segD(px, float2(pb + 0.1 * H, 0.84 * H), C));
    pan = min(pan, segD(px, C - float2(0.12 * H, 0.0), C + float2(0.1 * H, 0.0)));
    layer(acc, cover, float3(0.4, 0.4, 0.46), smoothstep(max(0.6, 0.016 * H) + 0.5, max(0.6, 0.016 * H) - 0.5, pan) * step(0.0, head - Lh * 0.5));
    float3 arc = lerp(float3(0.7, 0.85, 1.0), colorB.rgb, 0.3);
    layer(acc, cover, arc, exp(-length(px - C) / (0.1 * H)) * (0.12 + 1.1 * beat));
    layer(acc, cover, float3(1, 1, 1), smoothstep(0.05 * H, 0.0, length(px - C)) * beat);
    for (int k = 0; k < 6; k++)
    {
        float age = frac(time * 2.2 + k / 6.0);
        float hk = hash2(float2(k, floor(time * 2.2 + k / 6.0)));
        float2 s = C + float2(-(0.6 + 1.4 * hk) * age, (0.6 * hk - 0.1) * age - 1.4 * age * age) * H;
        float spk = length(px - s);
        layer(acc, cover, lerp(float3(1, 1, 1), float3(1.0, 0.75, 0.35), age), (smoothstep(max(0.9, 0.03 * H) + 0.5, 0.0, spk) + exp(-spk / (0.04 * H)) * 0.4) * (1.0 - age) * saturate(beat * 2.5));
    }

    // Headlight on the nose and a red tail lamp on the last car.
    float hlD = length(px - float2(head - 0.08 * H, 0.31 * H + yb));
    layer(acc, cover, lamp, exp(-hlD / (0.09 * H)) * 0.7 * step(0.0, head - 0.1 * H));
    layer(acc, cover, float3(1, 1, 1), smoothstep(0.045 * H + 0.5, 0.045 * H - 0.5, hlD));
    float tl = length(px - float2(head - Lh - 4.0 * (Lc + gap) + gap + 0.07 * H, 0.31 * H));
    layer(acc, cover, float3(1.0, 0.12, 0.08), exp(-tl / (0.07 * H)) * 0.8 + smoothstep(0.035 * H + 0.5, 0.035 * H - 0.5, tl));

    return paint(acc / max(cover, 0.001), cover);
}