Feature: TV screen CRT has a warm up and power down sequence
This commit is contained in:
parent
1a7b93bc7c
commit
293834b704
@ -1,8 +1,8 @@
|
||||
import * as THREE from 'three';
|
||||
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
||||
import { updateDoor } from '../scene/door.js';
|
||||
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
import { updateScreenEffect } from '../scene/tv-set.js'
|
||||
|
||||
function updateCamera() {
|
||||
const globalTime = Date.now() * 0.00003;
|
||||
@ -166,6 +166,7 @@ export function animate() {
|
||||
updateBooks();
|
||||
updateDoor();
|
||||
updatePictureFrame();
|
||||
updateScreenEffect();
|
||||
|
||||
// RENDER!
|
||||
state.renderer.render(state.scene, state.camera);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { turnTvScreenOff, turnTvScreenOn } from '../scene/tv-set.js';
|
||||
import { turnTvScreenOff, turnTvScreenOn, setScreenEffect } from '../scene/tv-set.js';
|
||||
|
||||
// --- Play video by index ---
|
||||
export function playVideoByIndex(index) {
|
||||
@ -15,8 +15,10 @@ export function playVideoByIndex(index) {
|
||||
|
||||
if (index < 0 || index >= state.videoUrls.length) {
|
||||
console.info('End of playlist reached. Reload tapes to start again.');
|
||||
state.screenLight.intensity = 0.0;
|
||||
turnTvScreenOff();
|
||||
setScreenEffect(2, () => { // Power-down effect
|
||||
state.screenLight.intensity = 0.0;
|
||||
turnTvScreenOff();
|
||||
});
|
||||
state.isVideoLoaded = false;
|
||||
state.lastUpdateTime = -1; // force VCR to redraw
|
||||
return;
|
||||
@ -41,7 +43,8 @@ export function playVideoByIndex(index) {
|
||||
// 2. Apply the video texture to the screen mesh
|
||||
turnTvScreenOn();
|
||||
|
||||
// 3. Start playback
|
||||
// 3. Start playback and trigger the warm-up effect simultaneously
|
||||
setScreenEffect(1); // Trigger warm-up
|
||||
state.videoElement.play().then(() => {
|
||||
state.isVideoLoaded = true;
|
||||
// Use the defined base intensity for screen glow
|
||||
|
||||
@ -181,7 +181,9 @@ export function turnTvScreenOn() {
|
||||
|
||||
state.tvScreen.material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
videoTexture: { value: state.videoTexture }
|
||||
videoTexture: { value: state.videoTexture },
|
||||
u_effect_type: { value: 0.0 },
|
||||
u_effect_strength: { value: 0.0 },
|
||||
},
|
||||
vertexShader: screenVertexShader,
|
||||
fragmentShader: screenFragmentShader,
|
||||
@ -189,4 +191,47 @@ export function turnTvScreenOn() {
|
||||
});
|
||||
|
||||
state.tvScreen.material.needsUpdate = true;
|
||||
setScreenEffect(1); // Trigger warm-up
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the warm-up and power-down effects on the TV screen.
|
||||
* @param {number} effectType - 0 normal, 1 for warm-up, 2 for power-down.
|
||||
* @param {function} onComplete - Optional callback when the animation finishes.
|
||||
*/
|
||||
export function setScreenEffect(effectType, onComplete) {
|
||||
const material = state.tvScreen.material;
|
||||
if (!material.uniforms) return;
|
||||
|
||||
state.screenEffect.active = true;
|
||||
state.screenEffect.type = effectType;
|
||||
state.screenEffect.startTime = state.clock.getElapsedTime() * 1000;
|
||||
state.screenEffect.onComplete = onComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the screen effect animation. Should be called in the main render loop.
|
||||
*/
|
||||
export function updateScreenEffect() {
|
||||
if (!state.screenEffect.active) return;
|
||||
|
||||
const material = state.tvScreen.material;
|
||||
if (!material.uniforms) return;
|
||||
|
||||
const elapsedTime = (state.clock.getElapsedTime() * 1000) - state.screenEffect.startTime;
|
||||
const progress = Math.min(elapsedTime / state.screenEffect.duration, 1.0);
|
||||
|
||||
const easedProgress = state.screenEffect.easing(progress);
|
||||
|
||||
material.uniforms.u_effect_type.value = state.screenEffect.type;
|
||||
material.uniforms.u_effect_strength.value = easedProgress;
|
||||
|
||||
if (progress >= 1.0) {
|
||||
state.screenEffect.active = false;
|
||||
material.uniforms.u_effect_strength.value = (state.screenEffect.type === 2) ? 1.0 : 0.0; // Final state
|
||||
if (state.screenEffect.onComplete) {
|
||||
state.screenEffect.onComplete();
|
||||
}
|
||||
material.uniforms.u_effect_type.value = 0.0; // Reset effect type
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,41 @@ void main() {
|
||||
export const screenFragmentShader = `
|
||||
varying vec2 vUv;
|
||||
uniform sampler2D videoTexture;
|
||||
uniform float u_effect_type; // 0: none, 1: warmup, 2: powerdown
|
||||
uniform float u_effect_strength; // 0.0 to 1.0
|
||||
|
||||
void main() {
|
||||
// Sample the video texture
|
||||
gl_FragColor = texture2D(videoTexture, vUv);
|
||||
vec2 centeredUv = vUv - 0.5;
|
||||
vec4 finalColor;
|
||||
|
||||
if (u_effect_type < 0.5) { // No effect
|
||||
finalColor = texture2D(videoTexture, vUv);
|
||||
} else if (u_effect_type < 1.5) { // Warm-up effect
|
||||
// A bright dot expands to reveal the screen content
|
||||
float effectRadius = u_effect_strength * 0.75; // Max radius of 0.75 (sqrt(0.5*0.5 + 0.5*0.5))
|
||||
float distanceToCenter = length(centeredUv);
|
||||
|
||||
// Smoothly transition the edge of the circle
|
||||
float vignette = smoothstep(effectRadius, effectRadius - 0.1, distanceToCenter);
|
||||
vec4 videoColor = texture2D(videoTexture, vUv);
|
||||
|
||||
finalColor = videoColor * vignette * u_effect_strength; // Fade in brightness
|
||||
|
||||
} else { // Power-down effect
|
||||
// The image collapses into a bright horizontal line and fades
|
||||
float collapseFactor = 1.0 - u_effect_strength;
|
||||
|
||||
// Squeeze the UVs vertically
|
||||
vec2 squeezedUv = vec2(vUv.x, 0.5 + (vUv.y - 0.5) * collapseFactor);
|
||||
vec4 videoColor = texture2D(videoTexture, squeezedUv);
|
||||
|
||||
// Create a bright glow where the line is
|
||||
float lineGlow = pow(1.0 - abs(centeredUv.y) / (0.5 * collapseFactor + 0.01), 20.0);
|
||||
|
||||
finalColor = videoColor * collapseFactor + vec4(0.8, 0.9, 1.0, 1.0) * lineGlow * u_effect_strength;
|
||||
|
||||
}
|
||||
|
||||
gl_FragColor = finalColor;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@ -8,12 +8,21 @@ export function initState() {
|
||||
scene: null,
|
||||
camera: null,
|
||||
renderer: null,
|
||||
clock: new THREE.Clock(),
|
||||
tvScreen: null,
|
||||
videoTexture: null,
|
||||
screenLight: null,
|
||||
lampLightPoint: null,
|
||||
lampLightSpot: null,
|
||||
effectsManager: null,
|
||||
screenEffect: {
|
||||
active: false,
|
||||
type: 0,
|
||||
startTime: 0,
|
||||
duration: 1000, // in ms
|
||||
onComplete: null,
|
||||
easing: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t, // easeInOutQuad
|
||||
},
|
||||
|
||||
// VCR Display
|
||||
lastUpdateTime: -1,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user