From 3a3022327c4e1bd87843fb897f1e067b1f781084 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Mon, 21 Sep 2026 19:03:54 +0200 Subject: [PATCH] Ride sky plays the viz so the paint mirrors something true Shared sky dome gains an optional viz overlay (off by default; chamber/stage untouched): the running video mixed over the gradient, masked to the upper sky so the horizon stays fog-matched. Same mirrored-sphere projection as the bodywork reflection, so paint and sky agree. Ride binds its vizTarget and holds 0.55. --- flow-state/src/venues/ride.js | 16 +++++++++++++++- flow-state/src/venues/sky.js | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/flow-state/src/venues/ride.js b/flow-state/src/venues/ride.js index 9f2f634..e3a65f1 100644 --- a/flow-state/src/venues/ride.js +++ b/flow-state/src/venues/ride.js @@ -44,8 +44,12 @@ function vizEnvMaterial(mat, instance, strength) { { vec3 vDir = normalize( vViewPosition ); vec3 rDir = reflect( vDir, normalize( normal ) ); + // Mirrored azimuth shared with the sky dome (see sky.js): + // folds ±π onto the same texel so neither surface shows a seam. + float sU = atan( rDir.z, rDir.x ) * 0.15915494 + 0.5; + sU = 1.0 - abs( 1.0 - 2.0 * sU ); vec2 sUv = vec2( - atan( rDir.z, rDir.x ) * 0.15915494 + 0.5, + sU, asin( clamp( rDir.y, -1.0, 1.0 ) ) * 0.31830988 + 0.5 ); totalEmissiveRadiance += texture2D( u_vizEnv, sUv ).rgb * u_vizEnvStrength; }` @@ -107,6 +111,16 @@ export const ride = { m.userData.vizEnv.value = texture; } } + // The sky wears the same video, so the paint mirrors something true. + // Same mirrored-sphere projection as the bodywork (see sky.js). + if (instance.skyMat) { + if (instance.skyMat.uniforms.u_viz.value !== texture) { + instance.skyMat.uniforms.u_viz.value = texture; + } + if (instance.skyMat.uniforms.u_vizAmount.value <= 0) { + instance.skyMat.uniforms.u_vizAmount.value = 0.55; + } + } }, // Radio-glow wash for the viz composite — arcs with venueState.light. diff --git a/flow-state/src/venues/sky.js b/flow-state/src/venues/sky.js index edbbf0e..eb3f249 100644 --- a/flow-state/src/venues/sky.js +++ b/flow-state/src/venues/sky.js @@ -31,6 +31,11 @@ export function buildSky(THREE, { radius = 90 } = {}) { u_bot: { value: new T.Color(0.02, 0.02, 0.03) }, u_bands: { value: 0.0 }, u_seed: { value: 0.0 }, + // The running video, composited elsewhere, overlaid on the dome + // so mirrored bodywork has something true to mirror. Off (0) + // unless a venue binds a texture — chamber and stage never do. + u_viz: { value: null }, + u_vizAmount: { value: 0.0 }, }, vertexShader: ` varying vec3 vPos; @@ -45,7 +50,19 @@ export function buildSky(THREE, { radius = 90 } = {}) { uniform vec3 u_bot; uniform float u_bands; uniform float u_seed; + uniform sampler2D u_viz; + uniform float u_vizAmount; varying vec3 vPos; + // Mirrored spherical projection shared with the ride's paint + // (see vizEnvMaterial): direction to sphere UVs, with the azimuth + // folded into a triangle so the ±π wrap lands on the same texel + // instead of a visible seam. + vec2 skyVizUv(vec3 dir) { + float u = atan(dir.z, dir.x) * 0.15915494 + 0.5; + u = 1.0 - abs(1.0 - 2.0 * u); + float v = asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988 + 0.5; + return vec2(u, v); + } void main() { float h = normalize(vPos).y; // -1 .. 1 vec3 col = h > 0.0 @@ -58,6 +75,15 @@ export function buildSky(THREE, { radius = 90 } = {}) { b = smoothstep(0.75, 0.95, b) * u_bands; col += u_top * b * 0.6; } + // The viz as the sky itself, mixed over the gradient amount. + // Masked to the upper sky: the horizon band stays gradient so + // the ground keeps melting into it (fog-matched), while the + // zenith plays the video. + if (u_vizAmount > 0.001) { + vec3 viz = texture2D(u_viz, skyVizUv(normalize(vPos))).rgb; + float m = smoothstep(0.02, 0.5, h) * clamp(u_vizAmount, 0.0, 1.0); + col = mix(col, viz, clamp(m, 0.0, 1.0)); + } gl_FragColor = vec4(col, 1.0); } `,