From eef13e301cf922a5543eaa4bc81fbccb1c734b4f Mon Sep 17 00:00:00 2001 From: Dejvino Date: Mon, 5 Jan 2026 21:48:02 +0000 Subject: [PATCH] Feature: blackout raises opacity of projection screen to simulate strong relative light --- party-stage/src/scene/projection-screen.js | 53 ++++++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/party-stage/src/scene/projection-screen.js b/party-stage/src/scene/projection-screen.js index c8a6777..9dd54df 100644 --- a/party-stage/src/scene/projection-screen.js +++ b/party-stage/src/scene/projection-screen.js @@ -55,7 +55,7 @@ void main() { float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, dist); mask = mix(mask, 1.0, blurFactor); - gl_FragColor = vec4(color.rgb, mask); + gl_FragColor = vec4(color.rgb, mask * u_opacity); } `; @@ -117,7 +117,7 @@ void main() { finalColor = hsv2rgb(vec3(hue, 0.8, val)); } - gl_FragColor = vec4(finalColor, mask); + gl_FragColor = vec4(finalColor, mask * u_opacity); } `; @@ -129,6 +129,7 @@ export class ProjectionScreen extends SceneFeature { projectionScreenInstance = this; this.isVisualizerActive = false; this.screens = []; + this.blackoutLerp = 0; this.colorBuffer = new Float32Array(16 * 3); this.standbyState = { @@ -158,6 +159,7 @@ export class ProjectionScreen extends SceneFeature { }; state.originalScreenIntensity = 2.0; state.screenOpacity = 0.5; + state.screenOpacityBlackout = 1.0; // Ensure video element exists if (!state.videoElement) { @@ -245,6 +247,22 @@ export class ProjectionScreen extends SceneFeature { update(deltaTime) { updateScreenEffect(); + // --- Blackout Transition Logic --- + const targetBlackout = state.blackoutMode ? 1.0 : 0.0; + const enterBlackoutSpeed = 0.5; // slow light up + const exitBlackoutSpeed = 5.0; // quick fade out + + if (this.blackoutLerp < targetBlackout) { + this.blackoutLerp = Math.min(targetBlackout, this.blackoutLerp + deltaTime * enterBlackoutSpeed); + } else if (this.blackoutLerp > targetBlackout) { + this.blackoutLerp = Math.max(targetBlackout, this.blackoutLerp - deltaTime * exitBlackoutSpeed); + } + + const normalOpacity = state.screenOpacity !== undefined ? state.screenOpacity : 0.7; + const blackoutOpacity = state.screenOpacityBlackout !== undefined ? state.screenOpacityBlackout : 0.2; + const currentOpacity = THREE.MathUtils.lerp(normalOpacity, blackoutOpacity, this.blackoutLerp); + const dimFactor = 1.0 - this.blackoutLerp; + if (this.standbyState.active) { this.renderStandbyFrame(); } @@ -272,10 +290,7 @@ export class ProjectionScreen extends SceneFeature { if (this.isVisualizerActive) { const beat = (state.music && state.music.beatIntensity) ? state.music.beatIntensity : 0.0; state.screenLight.intensity = state.originalScreenIntensity * (0.5 + beat * 0.5); - - if (state.blackoutMode) { - state.screenLight.intensity = 0; - } + state.screenLight.intensity *= dimFactor; // Update color buffer const colors = state.config.lightBarColors; @@ -297,13 +312,26 @@ export class ProjectionScreen extends SceneFeature { if (s.mesh.material.uniforms.u_colorCount) { s.mesh.material.uniforms.u_colorCount.value = colorCount; } - if (s.mesh.material.uniforms.u_opacity) { - const targetOpacity = state.blackoutMode ? 0.1 : state.screenOpacity; - s.mesh.material.uniforms.u_opacity.value = targetOpacity; - } } }); + } else { + // Video Mode + state.screenLight.intensity = state.originalScreenIntensity * dimFactor; } + + // Apply Opacity to all screens (Visualizer or Video) + this.screens.forEach(s => { + const material = s.mesh.material; + if (material && material.uniforms) { + if (material.uniforms.u_opacity) { + material.uniforms.u_opacity.value = currentOpacity; + } + // Ensure time is updated for video shader effects even if visualizer is off + if (!this.isVisualizerActive && material.uniforms.u_time) { + material.uniforms.u_time.value = state.clock.getElapsedTime(); + } + } + }); } onPartyStart() { @@ -554,11 +582,6 @@ export function updateScreenEffect() { if (material.uniforms.u_effect_strength) { material.uniforms.u_effect_strength.value = strength; } - - if (material.uniforms.u_opacity) { - const targetOpacity = state.blackoutMode ? 0.1 : (state.screenOpacity !== undefined ? state.screenOpacity : 0.7); - material.uniforms.u_opacity.value = targetOpacity; - } if (progress >= 1.0) { if (material.uniforms.u_effect_type) material.uniforms.u_effect_type.value = 0.0;