Paw Trail

by MusicBar, progress bar

A little cat trots to the playhead leaving a trail of paw prints that fade behind it, and on every beat it hops and a heart pops up.

14 downloads Download file, 8.2 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 94d39cf30cdad24dcb0da6af34a1bad1029b72c76f4bc263ee8b4ff265a2c78f

//! name: Paw Trail
//! author: MusicBar
//! kind: progress
//! description: A little cat trots to the playhead leaving a trail of paw prints that fade behind it, and on every beat it hops and a heart pops up.

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

// Smooth minimum: melts the head into the body so there is a neck.
float smin(float a, float b, float k) { float h = saturate(0.5 + 0.5 * (b - a) / k); return lerp(b, a, h) - k * h * (1.0 - h); }

// Distance to a capsule from a to b with radius r.
float capsule(float2 p, float2 a, float2 b, float r)
{
    float2 pa = p - a;
    float2 ba = b - a;
    return length(pa - ba * saturate(dot(pa, ba) / dot(ba, ba))) - r;
}

// Distance to a triangle (negative inside).
float tri(float2 p, float2 p0, float2 p1, float2 p2)
{
    float2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2;
    float2 v0 = p - p0, v1 = p - p1, v2 = p - p2;
    float2 q0 = v0 - e0 * saturate(dot(v0, e0) / dot(e0, e0));
    float2 q1 = v1 - e1 * saturate(dot(v1, e1) / dot(e1, e1));
    float2 q2 = v2 - e2 * saturate(dot(v2, e2) / dot(e2, e2));
    float s = sign(e0.x * e2.y - e0.y * e2.x);
    float2 d = min(min(float2(dot(q0, q0), s * (v0.x * e0.y - v0.y * e0.x)), float2(dot(q1, q1), s * (v1.x * e1.y - v1.y * e1.x))), float2(dot(q2, q2), s * (v2.x * e2.y - v2.y * e2.x)));
    return -sqrt(d.x) * sign(d.y);
}

// One leg: hip to paw with a bend, the paw lifting while it swings forward.
float leg(float2 p, float2 hip, float phase, float bendX, float r)
{
    float2 paw = float2(hip.x + 0.02 + 0.085 * cos(phase), 0.035 + 0.07 * max(0.0, -sin(phase)));
    float2 knee = (hip + paw) * 0.5 + float2(bendX, 0.0);
    float d = min(capsule(p, hip, knee, r * 1.25), capsule(p, knee, paw, r));
    return min(d, length((p - paw - float2(0.015, -0.005)) / float2(1.0, 0.7)) * 0.7 - 0.03);
}

// A heart, about one unit tall from its tip at the origin.
float heart(float2 p)
{
    p.x = abs(p.x);
    float2 a = p - float2(0.25, 0.75);
    float2 b = p - 0.5 * max(p.x + p.y, 0.0);
    float2 c = p - float2(0.0, 1.0);
    return p.y + p.x > 1.0 ? length(a) - 0.3536 : sqrt(min(dot(c, c), dot(b, b))) * sign(p.x - p.y);
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float2 px = float2(uv.x * size.x, (1.0 - uv.y) * size.y);  // pixels: along the bar, and up the screen so gravity holds on a top taskbar too
    float u = size.y;
    float head = progress * size.x;
    float aa = 0.6 / u;                                       // half a pixel, in bar heights
    float stepT = time * 9.0;
    float hop = 0.17 * 4.0 * beat * (1.0 - beat);
    float2 p = (px - float2(head, u * 0.1)) / u - float2(0.0, hop + 0.012 * cos(stepT * 2.0));

    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // The trail: paw prints alternating left and right, toes forward, fading with age and pulsing with the music.
    float ps = u * 0.36;
    float n = floor(px.x / ps);
    float side = frac(n * 0.5) < 0.25 ? 1.0 : -1.0;
    float pulse = 1.0 + 0.3 * spectrum(frac(n * 0.618) * 0.5);
    float2 pq = (px - float2((n + 0.5) * ps, u * (0.27 + 0.08 * side))) / (u * pulse);
    float padD = length(pq * float2(1.15, 0.95)) - 0.055;
    float toes = 1e4;
    [unroll] for (int t = 0; t < 4; t++)
    {
        float ang = -0.95 + t * 0.633;
        toes = min(toes, length(pq - float2(cos(ang), sin(ang)) * 0.1) - 0.026);
    }
    float printD = min(padD, toes);
    float printA = smoothstep(aa, -aa, printD) * step((n + 0.5) * ps, head - u * 0.75);
    float age = saturate((head - px.x) / (u * 16.0));
    float3 ink = lerp(colorB.rgb, colorA.rgb, 0.3 + 0.3 * age);
    ink = lerp(ink, colorA.rgb * 0.5, smoothstep(-2.5 * aa, 0.0, printD) * 0.7);
    layer(acc, cover, ink, printA * lerp(0.92, 0.28, age));

    // The rest of the song: a faint dotted path ahead.
    float dots = smoothstep(1.3, 0.3, length(float2((frac(px.x / ps) - 0.5) * ps, px.y - u * 0.27)));
    layer(acc, cover, lerp(colorB.rgb, colorA.rgb, 0.5), dots * step(head + u * 0.45, px.x) * 0.3);

    // The far legs, a shade darker, behind the body.
    float farD = min(leg(p, float2(-0.1, 0.27), stepT + 3.1416, 0.012, 0.036), leg(p, float2(-0.38, 0.28), stepT, -0.035, 0.04));

    // The cat: body, head, ears, near legs and a curling tail that swishes.
    float body = capsule(p, float2(-0.4, 0.3), float2(-0.08, 0.32), 0.12);
    float headD = (length((p - float2(0.07, 0.44)) / float2(0.15, 0.13)) - 1.0) * 0.13;
    float earB = tri(p, float2(-0.06, 0.52), float2(0.07, 0.55), float2(-0.02, 0.67)) - 0.012;
    float earF = tri(p, float2(0.07, 0.55), float2(0.19, 0.52), float2(0.16, 0.66)) - 0.012;
    float tail = 1e4;
    float2 tp = float2(-0.5, 0.34);
    float ta = 2.3;
    [unroll] for (int i = 0; i < 4; i++)
    {
        ta -= 0.42 - 0.28 * sin(time * 3.4 - i * 0.9 + 0.6 * level);
        float2 tn = tp + float2(cos(ta), sin(ta)) * 0.1;
        tail = min(tail, capsule(p, tp, tn, 0.042 - i * 0.004));
        tp = tn;
    }
    float nearD = min(leg(p, float2(-0.08, 0.27), stepT, 0.012, 0.038), leg(p, float2(-0.4, 0.28), stepT + 3.1416, -0.035, 0.042));
    float cat = min(min(smin(body, headD, 0.06), min(earB, earF)), min(tail, nearD));

    // A soft light behind the cat so it holds up on a dark taskbar, and a shadow on the ground.
    layer(acc, cover, colorA.rgb, exp(-max(min(cat, farD), 0.0) / 0.07) * 0.22 * step(0.0, min(cat, farD)));
    layer(acc, cover, float3(0, 0, 0), smoothstep(0.02, -0.02, length((p + float2(0.17, hop + 0.012)) / float2(0.32, 0.035)) - 1.0) * 0.25);

    float3 fur = colorA.rgb, outline = colorA.rgb * 0.32;
    layer(acc, cover, fur * 0.62, smoothstep(aa, -aa, farD));
    layer(acc, cover, outline, smoothstep(aa, -aa, farD) * smoothstep(-2.0 * aa, 0.0, farD) * 0.8);

    // Fur shading: lit along the back, a lighter belly and muzzle, tabby stripes across the back.
    float belly = smoothstep(0.31, 0.2, p.y) * step(p.x, 0.0) + smoothstep(0.1, 0.17, p.x) * smoothstep(0.47, 0.4, p.y);
    float3 c = lerp(fur, lerp(fur, float3(1, 0.97, 0.92), 0.65), saturate(belly));
    float stripe = smoothstep(0.24, 0.1, abs(frac(p.x * 7.0 + p.y * 1.8) - 0.5)) * smoothstep(0.35, 0.42, p.y) * step(p.x, 0.0) * step(0.0, tail);
    c = lerp(c, fur * 0.68, stripe * 0.8);
    c *= 0.88 + 0.22 * smoothstep(0.25, 0.5, p.y);
    layer(acc, cover, c, smoothstep(aa, -aa, cat));

    // Inner ears, cheek blush, eye (it blinks now and then), nose and whiskers.
    float inner = min(tri(p, float2(-0.03, 0.55), float2(0.05, 0.56), float2(-0.015, 0.63)), tri(p, float2(0.1, 0.555), float2(0.17, 0.54), float2(0.155, 0.62)));
    layer(acc, cover, lerp(colorC.rgb, float3(1, 0.75, 0.8), 0.5), smoothstep(aa, -aa, inner) * 0.85);
    layer(acc, cover, colorC.rgb, smoothstep(0.035, 0.0, length(p - float2(0.11, 0.395))) * 0.45);
    float blink = lerp(1.0, 0.15, step(0.95, frac(time * 0.31)));
    float eye = length((p - float2(0.135, 0.465)) / float2(0.026, 0.034 * blink)) - 1.0;
    layer(acc, cover, float3(0.07, 0.05, 0.05), smoothstep(aa, -aa, eye * 0.028));
    layer(acc, cover, float3(1, 1, 1), smoothstep(aa * 1.6, 0.0, length(p - float2(0.142, 0.478)) - 0.006) * blink);
    layer(acc, cover, lerp(colorC.rgb, float3(1, 0.6, 0.7), 0.5), smoothstep(aa, -aa, length((p - float2(0.218, 0.43)) / float2(1.0, 0.75)) - 0.018));
    float wh = min(capsule(p, float2(0.2, 0.415), float2(0.34, 0.44), 0.0), capsule(p, float2(0.2, 0.405), float2(0.33, 0.38), 0.0));
    layer(acc, cover, float3(1, 1, 1), smoothstep(aa * 1.6, 0.0, wh) * 0.7);
    layer(acc, cover, outline, smoothstep(aa, -aa, cat) * smoothstep(-2.2 * aa, -0.2 * aa, cat) * 0.85);

    // On the beat a heart pops up in front of the face and floats away.
    float hs = 0.22 * (0.6 + 0.4 * sqrt(beat));
    float2 hq = ((px - float2(head, 0.0)) / u - float2(0.47, 0.4 + 0.2 * (1.0 - beat))) / hs;
    float hd = heart(hq) * hs;
    float hon = saturate(beat * 3.0);
    layer(acc, cover, colorC.rgb, exp(-max(hd, 0.0) / 0.05) * beat * 0.5);
    layer(acc, cover, colorC.rgb * 0.45, smoothstep(aa, -aa, hd - 1.6 * aa) * hon);
    float3 hc = lerp(colorC.rgb, float3(1, 1, 1), 0.45 * smoothstep(0.03, 0.0, length(hq - float2(-0.22, 0.72)) * hs));
    layer(acc, cover, hc, smoothstep(aa, -aa, hd + 0.4 * aa) * hon);

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