Hội An Lanterns
by MusicBar, progress bar
A string of Hoi An silk lanterns glows to life up to the playhead, swaying in the night breeze and swinging harder on every beat.
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 1fec7795e1a6371df6fd6f05ee40daba3c680ea917e3aa1cd25219dfc27cfb1f
//! name: Hội An Lanterns
//! author: MusicBar
//! kind: progress
//! description: A string of Hoi An silk lanterns glows to life up to the playhead, swaying in the night breeze and swinging harder on every beat.
float hash1(float n)
{
n = frac(n * 0.1031);
n *= n + 33.33;
n *= n + n;
return frac(n);
}
// 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);
}
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);
}
// Signed distance to a rounded box (half size b, corner r).
float boxD(float2 p, float2 b, float r)
{
float2 d = abs(p) - b + r;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
}
// The silk of lantern i cycles through the palette.
float3 silkOf(float i)
{
float k = frac(i / 3.0) * 3.0;
return k < 0.5 ? colorA.rgb : (k < 1.5 ? colorB.rgb : colorC.rgb);
}
// Lantern i hangs from the cord: thread, top cap, ribbed silk body, bottom cap and tassel.
void lantern(inout float3 acc, inout float cover, float2 px, float i, float sp, float H, float head)
{
float cx = (i + 0.5) * sp;
float a = (0.05 + 0.07 * level) * sin(time * 1.2 + i * 1.7) + 0.16 * beat * sin(i * 2.3 + 1.0);
float2 q = rot(px - float2(cx, H * 0.96), -a);
// How lit it is: on behind the playhead, flickering while it catches.
float on = saturate((head - cx) / sp + 0.5);
on *= lerp(0.55 + 0.45 * sin(time * 23.0 + i) * sin(time * 37.0), 1.0, step(0.999, on));
float3 silk = silkOf(i);
float bright = 1.0 + 0.3 * beat + 0.12 * level;
// Body: an ellipse with flat ends where the caps sit.
float2 p = q - float2(0.0, -0.405 * H);
float2 r = float2(0.30, 0.27) * H;
float e = length(p / r);
float sd = max((e - 1.0) * r.y, abs(p.y) - 0.235 * H);
// The warm halo the silk throws around itself.
layer(acc, cover, lerp(silk, float3(1.0, 0.86, 0.6), 0.35), exp(-max(sd, 0.0) / (0.2 * H)) * 0.5 * on * bright);
// Ribs run pole to pole; the silk bulges between them and glows brightest in the middle.
float R = r.x * sqrt(saturate(1.0 - p.y * p.y / (r.y * r.y)));
float phi = asin(clamp(p.x / max(R, 0.001), -1.0, 1.0));
float s = phi * 2.546 + time * 0.12 + i * 0.37;
float dd = 0.5 - abs(frac(s) - 0.5);
float rib = smoothstep(0.9, 0.15, dd * 0.3927 * R * cos(phi));
float bulge = sin(dd * 3.14159);
float core = exp(-dot(p / r, p / r) * 1.7);
float3 lit = silk * (0.5 + 0.8 * core) * (0.7 + 0.3 * bulge) + float3(1.0, 0.92, 0.75) * core * core * core * 0.6;
float3 dark = silk * (0.1 + 0.28 * smoothstep(0.75, 1.0, e)) + 0.03;
float trim = smoothstep(1.2, 0.3, abs(abs(p.y) - 0.2 * H));
float3 body = lerp(dark, lit * bright, on);
body *= 1.0 - (0.6 * rib + 0.35 * trim) * (0.35 + 0.65 * on);
body += float3(1.0, 0.8, 0.4) * trim * (0.08 + 0.3 * on);
layer(acc, cover, body, smoothstep(0.5, -0.5, sd) * lerp(0.85, 1.0, on));
// Thread and lacquered caps, warmed by the light inside.
float3 wood = float3(0.2, 0.07, 0.035) + silk * 0.2 * on;
float thread = abs(q.x) - 0.5;
layer(acc, cover, wood, smoothstep(0.5, -0.5, thread) * step(-0.12 * H, q.y) * step(q.y, 0.0));
float capT = boxD(q - float2(0.0, -0.15 * H), float2(0.12, 0.035) * H, 0.025 * H);
float capB = boxD(q - float2(0.0, -0.665 * H), float2(0.1, 0.03) * H, 0.025 * H);
float capD = min(capT, capB);
float edge = smoothstep(1.0, 0.0, abs(capD + 0.6));
float band = smoothstep(0.6, 0.0, abs(frac(q.x / (0.06 * H)) - 0.5) - 0.3);
float3 capC = wood + float3(0.75, 0.48, 0.16) * (band * 0.25 + edge * 0.35) * (0.3 + 0.7 * on);
layer(acc, cover, capC, smoothstep(0.5, -0.5, capD));
// Tassel: swings a little behind the lantern, a cord then a flared fringe.
float2 t = rot(q - float2(0.0, -0.695 * H), -(0.6 * a + 0.12 * sin(time * 2.1 + i * 0.9)));
float down = -t.y / H;
float wid = lerp(0.012, 0.05, smoothstep(0.08, 0.26, down)) * H;
float tas = (abs(t.x) - max(wid, 0.6)) * step(0.0, down) * step(down, 0.27);
float strands = 0.75 + 0.25 * sin(t.x / max(wid, 0.6) * 9.0);
float3 tc = silk * lerp(0.25, 0.9 * strands, on) + 0.03;
layer(acc, cover, tc, smoothstep(0.5, -0.5, tas) * step(0.0, down) * step(down, 0.27) * smoothstep(0.27, 0.22, down));
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Pixels, y up the screen, so the lanterns always hang down.
float H = size.y;
float2 px = float2(uv.x * size.x, (1.0 - uv.y) * H);
float sp = 1.1 * H;
float head = progress * size.x;
float played = smoothstep(head + 0.5 * H, head - 0.5 * H, px.x);
float3 acc = float3(0, 0, 0);
float cover = 0;
// Far off along the river: out-of-focus lanterns over the played part, drifting slowly.
float2 bq = px / (0.5 * H) + float2(time * 0.15, 0.0);
float2 bc = floor(bq);
float bh = hash1(bc.x * 7.31 + bc.y * 13.7);
float2 bo = float2(hash1(bh * 91.0), hash1(bh * 57.0)) - 0.5;
float bd = length(frac(bq) - 0.5 - bo * 0.3);
float br = 0.16 + 0.14 * hash1(bh * 23.0);
float bok = smoothstep(br, br - 0.05, bd) * (0.7 + 0.3 * smoothstep(br - 0.1, br, bd));
layer(acc, cover, silkOf(floor(bh * 9.0)), bok * step(0.6, bh) * played * (0.14 + 0.08 * level));
// The cord: a catenary sag between the hanging points, warm where the lanterns are lit.
float f = frac(px.x / sp - 0.5);
float cordY = H * 0.96 - H * 0.09 * 4.0 * f * (1.0 - f);
float cord = smoothstep(max(0.6, 0.02 * H) + 0.5, max(0.6, 0.02 * H) - 0.5, abs(px.y - cordY));
layer(acc, cover, lerp(float3(0.22, 0.13, 0.08), float3(0.95, 0.7, 0.4), 0.6 * played), cord);
// The two nearest lanterns: the neighbor first, then this one on top.
float i = floor(px.x / sp);
float j = i + (frac(px.x / sp) > 0.5 ? 1.0 : -1.0);
lantern(acc, cover, px, j, sp, H, head);
lantern(acc, cover, px, i, sp, H, head);
return paint(acc / max(cover, 0.001), cover);
}