Blossom Branch

by MusicBar, progress bar

A gnarled Tet branch grows to the playhead and bursts into five-petal blossoms, petals drift off on the breeze with a flurry on every beat, and a yellow palette turns the peach blossom into apricot blossom.

27 downloads Download file, 7.9 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 c7dd314bea8cedb7adad7a45d3911dfe72b9ad7e095b3ca4935c8c43dbac38d9

//! name: Blossom Branch
//! author: MusicBar
//! kind: progress
//! description: A gnarled Tet branch grows to the playhead and bursts into five-petal blossoms, with petals drifting off on the breeze and a flurry on every beat.

// A steady random number for a cell.
float hash1(float n) { n = frac(n * 0.1031); n *= n + 33.33; n *= n + n; return frac(n); }

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

float2 rot(float2 p, float a)
{
    float c = cos(a);
    float s = sin(a);
    return float2(c * p.x + s * p.y, -s * p.x + c * p.y);
}

// The branch's height along the bar: slow bends plus angular kinks so it looks gnarled.
float branchY(float x, float u)
{
    float k = abs(frac(x / (u * 1.7)) - 0.5) * 4.0 - 1.0;
    return u * (0.42 + 0.11 * sin(x / u * 0.75 + 0.5) + 0.05 * sin(x / u * 2.1 + 1.3) + 0.045 * k);
}

// Distance to a twig from a to b that thins toward its tip.
float twig(float2 p, float2 a, float2 b, float r0, float r1)
{
    float2 pa = p - a;
    float2 ba = b - a;
    float h = saturate(dot(pa, ba) / max(dot(ba, ba), 0.001));
    return length(pa - ba * h) - lerp(r0, r1, h);
}

// One blossom: five round petals with a notch at each tip, opening from a bud as open goes 0 to 1.
void blossom(inout float3 acc, inout float cover, float2 p, float R, float open, float spin)
{
    float2 q = rot(p, spin);
    float r = length(q);
    float a = atan2(q.y, q.x);
    float k = floor(a / 1.2566 + 0.5);
    float pa = a - k * 1.2566;
    float2 pq = r * float2(cos(pa), sin(pa));
    float pr = R * lerp(0.17, 0.5, open);
    float pc = R * lerp(0.08, 0.5, open);
    float d = max(length(pq - float2(pc, 0)) - pr, R * 0.09 - length(pq - float2(pc + pr * 1.02, 0)));
    float body = smoothstep(0.6, -0.6, d);
    float3 c = lerp(colorA.rgb * lerp(0.7, 0.85, open), lerp(colorB.rgb, float3(1, 1, 1), 0.3), smoothstep(0.1, 1.0, r / R) * open);
    c += float3(1, 1, 1) * smoothstep(R * 0.1, 0.0, length(q - float2(-0.08, 0.1) * R)) * (1.0 - open) * 0.6;
    c *= 1.0 - 0.3 * smoothstep(R * 0.03, 0.0, abs(abs(pa) - 0.6283) * r) * step(R * 0.25, r) * open;
    c *= 1.0 - 0.25 * smoothstep(-2.0, 0.0, d);
    layer(acc, cover, c, body);

    // The heart: a colorC center ringed with tiny stamen dots.
    float sa = frac(a / 0.6283) - 0.5;
    float stamen = smoothstep(R * 0.07, R * 0.03, length(float2(sa * 0.6283 * r, r - R * 0.3)));
    float heart = smoothstep(R * 0.17, R * 0.1, r);
    layer(acc, cover, lerp(colorC.rgb, float3(1, 1, 1), 0.2), (heart * 0.9 + stamen) * smoothstep(0.35, 0.7, open));
}

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 sway = sin(time * 1.4) * u * 0.03 * (1.0 + level);  // the young end sways in the breeze

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

    // The main branch, thick and old at the start, thin at the growing tip.
    float by = branchY(px.x, u) + sway * exp(-max(head - px.x, 0.0) / (u * 2.0));
    float slope = (branchY(px.x + 1.0, u) - branchY(px.x - 1.0, u)) * 0.5;
    float th = u * (0.028 + 0.05 * saturate((head - px.x) / (u * 7.0)));
    float2 tipP = float2(head, branchY(head, u) + sway);
    float bd = px.x > head ? length(px - tipP) - u * 0.028 : abs(px.y - by) / sqrt(1.0 + slope * slope) - th;

    // Ahead of the playhead a faint dotted ghost of the branch to come; behind it a soft blossom glow.
    float ghost = smoothstep(1.2, 0.2, abs(px.y - by)) * step(head + u * 0.3, px.x) * step(frac(px.x / (u * 0.18)), 0.5);
    layer(acc, cover, lerp(colorA.rgb, float3(1, 1, 1), 0.3), ghost * 0.22);
    float glow = exp(-abs(px.y - by) / (u * 0.25)) * smoothstep(head + u * 0.4, head - u * 0.6, px.x);
    layer(acc, cover, colorA.rgb, glow * (0.1 + 0.12 * level));

    // Twigs: one per cell, reaching forward and up or down from the branch, each tipped with a flower.
    float cw = u * 1.15;
    float cell = floor(px.x / cw);
    float twigD = 1e4;
    float2 tips[2];                                           // each twig's tip and how open its flower is
    float tipOpen[2];
    [unroll] for (int i = 0; i < 2; i++)
    {
        float k = cell - i;
        float x0 = (k + 0.15 + 0.5 * hash1(k * 3.3)) * cw;
        float grow = saturate((head - x0) / (u * 1.2));
        float ang = (0.55 + 0.45 * hash1(k * 1.9)) * (hash1(k * 7.7 + 1.0) < 0.55 ? 1.0 : -1.0);
        float len = u * (0.28 + 0.16 * hash1(k * 5.1)) * grow;
        float2 a0 = float2(x0, branchY(x0, u) + sway * exp(-max(head - x0, 0.0) / (u * 2.0)));
        tips[i] = a0 + float2(cos(ang), sin(ang)) * len;
        twigD = min(twigD, twig(px, a0, tips[i], u * 0.03, u * 0.012) + step(grow, 0.0) * 1e4);
        tipOpen[i] = saturate((head - x0) / (u * 2.5) - 0.3) * step(u * 0.05, len);
    }

    // The bark: dark wood with a warm rim of light along the top and fine grain.
    float wood = min(bd, twigD);
    float woodA = smoothstep(0.6, -0.6, wood);
    float grain = 0.85 + 0.15 * sin(px.x / u * 40.0 + sin(px.y / u * 9.0) * 2.0);
    float rimLit = smoothstep(-th * 0.2, th * 1.2, px.y - by) * step(bd, twigD);
    layer(acc, cover, float3(0.2, 0.12, 0.09) * grain + float3(0.35, 0.22, 0.15) * rimLit, woodA);
    layer(acc, cover, float3(0.75, 0.55, 0.42), smoothstep(1.0, 0.0, abs(wood + 0.6)) * 0.45 * woodA);

    // Blossoms along the branch: newest near the tip are still buds, older ones wide open,
    // breathing a little with the music.
    float fw = u * 0.6;
    float fc = floor(px.x / fw);
    float pulse = 1.0 + 0.1 * level + 0.12 * beat;
    [unroll] for (int j = -1; j <= 1; j++)
    {
        float k = fc + j;
        float h = hash1(k * 4.1 + 2.0);
        float fx = (k + 0.2 + 0.6 * hash1(k * 9.3)) * fw;
        float side = (frac(k * 0.5) < 0.25 ? 1.0 : -1.0) * (0.06 + 0.12 * hash1(k * 6.7));
        float2 fp = float2(fx, branchY(fx, u) + sway * exp(-max(head - fx, 0.0) / (u * 2.0)) + side * u);
        float R = u * (0.17 + 0.06 * hash1(k * 2.3)) * pulse;
        if (h < 0.78 && fx < head - u * 0.05) blossom(acc, cover, px - fp, R, saturate((head - fx) / (u * 2.2) - 0.15), h * 6.28 + time * 0.1);
    }
    if (tipOpen[0] > 0.0) blossom(acc, cover, px - tips[0], u * 0.18 * pulse, tipOpen[0], cell);
    if (tipOpen[1] > 0.0) blossom(acc, cover, px - tips[1], u * 0.18 * pulse, tipOpen[1], cell + 2.0);

    // The growing tip carries a fat bud that swells and glows on the beat.
    float2 budP = tipP + float2(u * 0.07, u * 0.01);
    float2 bq = rot(px - budP, 0.25);
    layer(acc, cover, colorC.rgb, exp(-length(px - budP) / (u * 0.12)) * (0.25 + 0.45 * beat));
    float3 budC = colorA.rgb * 0.8 + smoothstep(u * 0.04, 0.0, length(bq - float2(u * 0.01, u * 0.03))) * 0.5;
    layer(acc, cover, budC, smoothstep(0.6, -0.6, length(float2(bq.x * 0.75, bq.y)) - u * (0.065 + 0.02 * beat)));
    layer(acc, cover, float3(0.25, 0.32, 0.12), smoothstep(0.6, -0.6, length(px - tipP - float2(u * 0.02, 0.0)) - u * 0.035));

    // Petals let go and drift back and down; more of them fly when the music is loud or on the beat.
    float2 dp = px + float2(time * u * 0.55, time * u * 0.22);
    dp.y += sin(dp.x / u * 1.3 + time) * u * 0.08;
    float2 dc = floor(dp / (u * 0.55));
    float dh = hash1(dc.x * 13.7 + dc.y * 3.1);
    float2 dq = dp - (dc + 0.25 + 0.5 * float2(hash1(dc.x + dc.y * 7.0), hash1(dc.y * 3.0 - dc.x))) * u * 0.55;
    dq = rot(dq, time * (1.0 + 2.0 * dh) + dh * 6.0);
    dq.x /= max(abs(cos(time * 2.0 + dh * 9.0)), 0.3);
    float petal = smoothstep(0.6, -0.6, length(float2(dq.x * 0.8, dq.y * 1.4)) - u * 0.055);
    float drift = step(dh, 0.12 + 0.25 * level + 0.45 * beat) * smoothstep(head + u * 0.4, head - u * 0.4, px.x);
    layer(acc, cover, lerp(colorA.rgb, colorB.rgb, 0.5 + 0.4 * dq.y / (u * 0.05)), petal * drift * 0.9);

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