diff --git a/flow-state/src/scenes/shader/synthwave-run.js b/flow-state/src/scenes/shader/synthwave-run.js index 342f3ba..eeb292c 100644 --- a/flow-state/src/scenes/shader/synthwave-run.js +++ b/flow-state/src/scenes/shader/synthwave-run.js @@ -1,18 +1,23 @@ // Ported from party-stage's "Synthwave Run". // // The original hardcoded magenta and cyan and drew a car silhouette with -// red taillights. The car is kept — it is the scene's whole identity — but the -// colours now come from the palette, and the elements (grid, mountains, sun, -// car) are individually switchable so the arc driver can strip it back to just -// the grid during a breakdown and bring the rest in for the drop. +// red taillights. The grid, mountains and sun keep their palette-driven +// forms and are individually switchable so the arc driver can strip the +// scene back to just the grid for a breakdown, but the car is gone: it +// was the same silhouette in every video, so a hexagonal track and a round +// track drove the same car. The thing on the road is now the song's own +// protagonist — the same solid every other stage in the video draws, seen +// here as a single hero turning slowly above the grid — so a hexagonal +// track drives a hexagonal form. See castSolid. export const synthwaveRun = { name: 'Synthwave Run', family: 'structural', kind: 'fragment', // Personality: see look/Personality.js. - consumes: ['ink'], - traits: ['space', 'camera', 'style'], + texture: 0.4, + consumes: ['form', 'ink', 'staging'], + traits: ['shape', 'space', 'camera', 'style'], params: { speed: { type: 'float', range: [0.3, 4], default: 1.5, uniform: 'u_speed', bias: 'motion', rate: true }, @@ -76,24 +81,36 @@ vec4 scene(vec2 uv, vec2 p) { } } - // --- car silhouette --- + // --- protagonist on the road --- + // The same solid every other stage in this video draws, now as the hero + // the camera is chasing down the grid. One instance, turning slowly so + // its outline changes across the song rather than being the same + // silhouette from every angle. The car param now gates how present it + // is, so a breakdown can drop to just the grid the same way it used to. if (u_car > 0.01) { - vec2 carPos = vec2(sin(t * 0.1) * 0.2, -0.78 + sin(t * 30.0) * 0.001); - vec2 cp = p - carPos; - float cpX = abs(cp.x); - - float body = step(cpX, 0.35) * step(abs(cp.y), 0.12); - float glass = step(cpX, 0.22) * step(abs(cp.y - 0.18), 0.07); - float wingTop = step(cpX, 0.32) * step(abs(cp.y - 0.20), 0.025); - float wingSide = step(abs(cpX - 0.30), 0.03) * step(abs(cp.y - 0.12), 0.1); - - if (body + glass + wingTop + wingSide > 0.0) { - col = mix(col, vec3(0.015), 0.95 * u_car); - float r = 0.025; - float l1 = smoothstep(r, r * 0.6, length(vec2(cpX - 0.21, cp.y - 0.03))); - float l2 = smoothstep(r, r * 0.6, length(vec2(cpX - 0.29, cp.y - 0.03))); - col = mix(col, pal(2) * (0.6 + beat * 1.4), sat(l1 + l2) * u_car); + float bob = sin(t * 0.6) * 0.012 + sin(t * 1.7 + u_seed) * 0.006; + vec2 heroPos = vec2(sin(t * 0.08) * 0.18, -0.68 + bob); + float base = 0.32 * stageScale(); + float size = base * (1.0 + beat * 0.18); + vec2 local = (p - heroPos) / max(size, 1e-3); + if (dot(local, local) < 1.7) { + mat3 turn = castTurn(t * 0.35 + u_seed * 1.1, sin(t * 0.28 + u_seed * 0.7) * 0.55); + vec3 n; + float hit = castSolid(local, turn, n); + if (hit > 0.0) { + vec3 lit = castLit(n, vec3(0.0, 0.0, 1.0)); + col = mix(col, lit, 0.92 * u_car); + } + float d = castSDF3(turn * vec3(local, 0.0)) * size; + col += pal(2) * inkStroke(d) * 0.65 * u_car; + col += pal(1) * exp(-dot(local, local) * 1.2) * 0.08 * u_car; } + // ground contact — a soft ellipse stretched along the grid, so the + // floating solid still reads as standing on something + vec2 shadowP = vec2((p.x - heroPos.x) / max(size * 1.4, 1e-3), + (p.y - heroPos.y + size * 0.45) / max(size * 0.22, 1e-3)); + float shadow = exp(-dot(shadowP, shadowP)) * 0.35 * u_car * sat(1.0 - size * 0.8); + col = mix(col, vec3(0.0), shadow); } // Horizon haze, scaled by the glow param.