Compare commits
5 Commits
192fc39461
...
293834b704
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
293834b704 | ||
|
|
1a7b93bc7c | ||
|
|
272c4267a3 | ||
|
|
c1383e0845 | ||
|
|
c521828a08 |
@ -1,15 +1,15 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
|
||||||
import { updateDoor } from '../scene/door.js';
|
import { updateDoor } from '../scene/door.js';
|
||||||
|
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
|
import { updateScreenEffect } from '../scene/tv-set.js'
|
||||||
|
|
||||||
function updateCamera() {
|
function updateCamera() {
|
||||||
const globalTime = Date.now() * 0.00005;
|
const globalTime = Date.now() * 0.00003;
|
||||||
const lookAtTime = Date.now() * 0.00003;
|
const lookAtTime = Date.now() * 0.0002;
|
||||||
|
|
||||||
const camAmplitude = 0.5;
|
const camAmplitude = 0.2;
|
||||||
const lookAmplitude = 0.05;
|
const lookAmplitude = 0.1;
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
// Base Camera Position in front of the TV
|
||||||
const baseX = -0.5;
|
const baseX = -0.5;
|
||||||
@ -166,6 +166,7 @@ export function animate() {
|
|||||||
updateBooks();
|
updateBooks();
|
||||||
updateDoor();
|
updateDoor();
|
||||||
updatePictureFrame();
|
updatePictureFrame();
|
||||||
|
updateScreenEffect();
|
||||||
|
|
||||||
// RENDER!
|
// RENDER!
|
||||||
state.renderer.render(state.scene, state.camera);
|
state.renderer.render(state.scene, state.camera);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
|
import { turnTvScreenOff, turnTvScreenOn, setScreenEffect } from '../scene/tv-set.js';
|
||||||
|
|
||||||
// --- Play video by index ---
|
// --- Play video by index ---
|
||||||
export function playVideoByIndex(index) {
|
export function playVideoByIndex(index) {
|
||||||
@ -14,14 +15,10 @@ export function playVideoByIndex(index) {
|
|||||||
|
|
||||||
if (index < 0 || index >= state.videoUrls.length) {
|
if (index < 0 || index >= state.videoUrls.length) {
|
||||||
console.info('End of playlist reached. Reload tapes to start again.');
|
console.info('End of playlist reached. Reload tapes to start again.');
|
||||||
|
setScreenEffect(2, () => { // Power-down effect
|
||||||
state.screenLight.intensity = 0.0;
|
state.screenLight.intensity = 0.0;
|
||||||
state.tvScreen.material.dispose();
|
turnTvScreenOff();
|
||||||
state.tvScreen.material = new THREE.MeshPhongMaterial({
|
|
||||||
color: 0x0a0a0a, // Deep black
|
|
||||||
shininess: 5,
|
|
||||||
specular: 0x111111
|
|
||||||
});
|
});
|
||||||
state.tvScreen.material.needsUpdate = true;
|
|
||||||
state.isVideoLoaded = false;
|
state.isVideoLoaded = false;
|
||||||
state.lastUpdateTime = -1; // force VCR to redraw
|
state.lastUpdateTime = -1; // force VCR to redraw
|
||||||
return;
|
return;
|
||||||
@ -44,11 +41,10 @@ export function playVideoByIndex(index) {
|
|||||||
state.videoTexture.needsUpdate = true;
|
state.videoTexture.needsUpdate = true;
|
||||||
|
|
||||||
// 2. Apply the video texture to the screen mesh
|
// 2. Apply the video texture to the screen mesh
|
||||||
state.tvScreen.material.dispose();
|
turnTvScreenOn();
|
||||||
state.tvScreen.material = new THREE.MeshBasicMaterial({ map: state.videoTexture });
|
|
||||||
state.tvScreen.material.needsUpdate = true;
|
|
||||||
|
|
||||||
// 3. Start playback
|
// 3. Start playback and trigger the warm-up effect simultaneously
|
||||||
|
setScreenEffect(1); // Trigger warm-up
|
||||||
state.videoElement.play().then(() => {
|
state.videoElement.play().then(() => {
|
||||||
state.isVideoLoaded = true;
|
state.isVideoLoaded = true;
|
||||||
// Use the defined base intensity for screen glow
|
// Use the defined base intensity for screen glow
|
||||||
|
|||||||
@ -77,15 +77,15 @@ export function updateDoor() {
|
|||||||
|
|
||||||
if (stateTimer <= 0) {
|
if (stateTimer <= 0) {
|
||||||
const nextState = Math.random();
|
const nextState = Math.random();
|
||||||
if (nextState < 0.4) {
|
if (nextState < 0.1) {
|
||||||
doorState = DOOR_STATES.RESTING;
|
doorState = DOOR_STATES.RESTING;
|
||||||
stateTimer = 2 + Math.random() * 5;
|
stateTimer = 2 + Math.random() * 5;
|
||||||
} else if (nextState < 0.7) {
|
} else if (nextState < 0.5) {
|
||||||
doorState = DOOR_STATES.OPENING;
|
doorState = DOOR_STATES.OPENING;
|
||||||
stateTimer = 2 + Math.random() * 4;
|
stateTimer = 2 + Math.random() * 5;
|
||||||
} else {
|
} else {
|
||||||
doorState = DOOR_STATES.CLOSING;
|
doorState = DOOR_STATES.CLOSING;
|
||||||
stateTimer = 3 + Math.random() * 3;
|
stateTimer = 3 + Math.random() * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export function updateDoor() {
|
|||||||
break;
|
break;
|
||||||
case DOOR_STATES.CLOSING:
|
case DOOR_STATES.CLOSING:
|
||||||
if (doorGroupPanel.rotation.y < minAngle) {
|
if (doorGroupPanel.rotation.y < minAngle) {
|
||||||
doorGroupPanel.rotation.y += speed;
|
doorGroupPanel.rotation.y += speed * 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ export function updateDoor() {
|
|||||||
// Outside material pulsating glow
|
// Outside material pulsating glow
|
||||||
if (outsideMaterial) {
|
if (outsideMaterial) {
|
||||||
const glowMin = 0.001;
|
const glowMin = 0.001;
|
||||||
const glowMax = 0.01;
|
const glowMax = 0.005;
|
||||||
const glowSpeed = 0.0001; // Speed of the pulsation
|
const glowSpeed = 0.0001; // Speed of the pulsation
|
||||||
glowIntensity += glowDirection * glowSpeed;
|
glowIntensity += glowDirection * glowSpeed;
|
||||||
if (glowIntensity >= glowMax) {
|
if (glowIntensity >= glowMax) {
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export function createRoomWalls() {
|
|||||||
ceiling.receiveShadow = true;
|
ceiling.receiveShadow = true;
|
||||||
state.scene.add(ceiling);
|
state.scene.add(ceiling);
|
||||||
|
|
||||||
state.crawlSurfaces.push(backWall, frontWall, leftWall, rightWall);
|
state.crawlSurfaces.push(backWall, leftWall, rightWall);
|
||||||
|
|
||||||
// --- 6. Add a Window to the Back Wall ---
|
// --- 6. Add a Window to the Back Wall ---
|
||||||
const windowWidth = 1.5;
|
const windowWidth = 1.5;
|
||||||
|
|||||||
18
tv-player/src/scene/screen-shaders.js
Normal file
18
tv-player/src/scene/screen-shaders.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const screenVertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const screenFragmentShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
uniform sampler2D videoTexture;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Sample the video texture
|
||||||
|
gl_FragColor = texture2D(videoTexture, vUv);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import { createVcr } from './vcr.js';
|
import { createVcr } from './vcr.js';
|
||||||
|
import { screenVertexShader, screenFragmentShader } from '../shaders/screen-shaders.js';
|
||||||
|
|
||||||
export function createTvSet(x, z, rotY) {
|
export function createTvSet(x, z, rotY) {
|
||||||
// --- Materials (MeshPhongMaterial) ---
|
// --- Materials (MeshPhongMaterial) ---
|
||||||
@ -136,12 +137,7 @@ export function createTvSet(x, z, rotY) {
|
|||||||
|
|
||||||
// Position the curved screen
|
// Position the curved screen
|
||||||
state.tvScreen.position.set(0.0, 1.5, -2.1);
|
state.tvScreen.position.set(0.0, 1.5, -2.1);
|
||||||
state.tvScreen.material = new THREE.MeshPhongMaterial({
|
turnTvScreenOff();
|
||||||
color: 0x0a0a0a, // Deep black
|
|
||||||
shininess: 5,
|
|
||||||
specular: 0x111111
|
|
||||||
});
|
|
||||||
state.tvScreen.material.needsUpdate = true;
|
|
||||||
tvGroup.add(state.tvScreen);
|
tvGroup.add(state.tvScreen);
|
||||||
|
|
||||||
tvGroup.position.set(x, 0, z);
|
tvGroup.position.set(x, 0, z);
|
||||||
@ -165,3 +161,77 @@ export function createTvSet(x, z, rotY) {
|
|||||||
|
|
||||||
state.scene.add(tvGroup);
|
state.scene.add(tvGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function turnTvScreenOff() {
|
||||||
|
if (state.tvScreen.material) {
|
||||||
|
state.tvScreen.material.dispose();
|
||||||
|
}
|
||||||
|
state.tvScreen.material = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0x203530,
|
||||||
|
shininess: 45,
|
||||||
|
specular: 0x111111,
|
||||||
|
});
|
||||||
|
state.tvScreen.material.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function turnTvScreenOn() {
|
||||||
|
if (state.tvScreen.material) {
|
||||||
|
state.tvScreen.material.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.tvScreen.material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
videoTexture: { value: state.videoTexture },
|
||||||
|
u_effect_type: { value: 0.0 },
|
||||||
|
u_effect_strength: { value: 0.0 },
|
||||||
|
},
|
||||||
|
vertexShader: screenVertexShader,
|
||||||
|
fragmentShader: screenFragmentShader,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
50
tv-player/src/shaders/screen-shaders.js
Normal file
50
tv-player/src/shaders/screen-shaders.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
export const screenVertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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,
|
scene: null,
|
||||||
camera: null,
|
camera: null,
|
||||||
renderer: null,
|
renderer: null,
|
||||||
|
clock: new THREE.Clock(),
|
||||||
tvScreen: null,
|
tvScreen: null,
|
||||||
videoTexture: null,
|
videoTexture: null,
|
||||||
screenLight: null,
|
screenLight: null,
|
||||||
lampLightPoint: null,
|
lampLightPoint: null,
|
||||||
lampLightSpot: null,
|
lampLightSpot: null,
|
||||||
effectsManager: 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
|
// VCR Display
|
||||||
lastUpdateTime: -1,
|
lastUpdateTime: -1,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user