180 lines
6.5 KiB
JavaScript
180 lines
6.5 KiB
JavaScript
import * as THREE from 'three';
|
|
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;
|
|
const lookAtTime = Date.now() * 0.0002;
|
|
|
|
const camAmplitude = 0.2;
|
|
const lookAmplitude = 0.1;
|
|
|
|
// Base Camera Position in front of the TV
|
|
const baseX = -0.5;
|
|
const baseY = 1.5;
|
|
const baseZ = 2.2;
|
|
|
|
// Base LookAt target (Center of the screen)
|
|
const baseTargetX = -0.7;
|
|
const baseTargetY = 1.7;
|
|
const baseTargetZ = -0.3;
|
|
|
|
// Camera Position Offsets (Drift)
|
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.4;
|
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.4;
|
|
|
|
state.camera.position.x = baseX + camOffsetX;
|
|
state.camera.position.y = baseY + camOffsetY;
|
|
state.camera.position.z = baseZ + camOffsetZ;
|
|
|
|
// LookAt Target Offsets (Subtle Gaze Shift)
|
|
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude * 3;
|
|
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
|
|
|
// Apply lookAt to the subtly shifted target
|
|
state.camera.lookAt(baseTargetX + lookOffsetX, baseTargetY + lookOffsetY, baseTargetZ);
|
|
}
|
|
|
|
function updateLampFlicker() {
|
|
const flickerChance = 0.995;
|
|
const restoreRate = 0.15;
|
|
|
|
if (Math.random() > flickerChance) {
|
|
// Flickers quickly to a dimmer random value (between 0.3 and 1.05)
|
|
let lampLightIntensity = state.originalLampIntensity * (0.3 + Math.random() * 0.7);
|
|
state.lampLightSpot.intensity = lampLightIntensity;
|
|
state.lampLightPoint.intensity = lampLightIntensity;
|
|
} else if (state.lampLightPoint.intensity < state.originalLampIntensity) {
|
|
// Smoothly restore original intensity
|
|
let lampLightIntensity = THREE.MathUtils.lerp(state.lampLightPoint.intensity, state.originalLampIntensity, restoreRate);
|
|
state.lampLightSpot.intensity = lampLightIntensity;
|
|
state.lampLightPoint.intensity = lampLightIntensity;
|
|
}
|
|
}
|
|
|
|
function updateScreenLight() {
|
|
if (state.isVideoLoaded && state.screenLight.intensity > 0) {
|
|
const pulseTarget = state.originalScreenIntensity + (Math.random() - 0.5) * state.screenIntensityPulse;
|
|
state.screenLight.intensity = THREE.MathUtils.lerp(state.screenLight.intensity, pulseTarget, 0.1);
|
|
|
|
const lightTime = Date.now() * 0.0001;
|
|
const radius = 0.01;
|
|
const centerX = 0;
|
|
const centerY = 1.5;
|
|
|
|
state.screenLight.position.x = centerX + Math.cos(lightTime) * radius;
|
|
state.screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y
|
|
}
|
|
}
|
|
|
|
function updateVideo() {
|
|
if (state.videoTexture) {
|
|
state.videoTexture.needsUpdate = true;
|
|
}
|
|
}
|
|
|
|
function updateVcr() {
|
|
const currentTime = state.baseTime + state.videoElement.currentTime;
|
|
if (Math.abs(currentTime - state.lastUpdateTime) > 0.1) {
|
|
updateVcrDisplay(currentTime);
|
|
state.lastUpdateTime = currentTime;
|
|
}
|
|
if (currentTime - state.lastBlinkToggleTime > 0.5) { // Blink every 0.5 seconds
|
|
state.blinkState = !state.blinkState;
|
|
state.lastBlinkToggleTime = currentTime;
|
|
}
|
|
}
|
|
|
|
function updateBooks() {
|
|
const LEVITATE_CHANCE = 0.0003; // Chance for a resting book to start levitating per frame
|
|
const LEVITATE_DURATION_MIN = 100; // frames
|
|
const LEVITATE_DURATION_MAX = 300; // frames
|
|
const LEVITATE_AMPLITUDE = 0.02; // Max vertical displacement
|
|
const LEVITATE_SPEED_FACTOR = 0.03; // Speed of oscillation
|
|
const START_RATE = 0.05; // How quickly a book starts to levitate
|
|
const RETURN_RATE = 0.1; // How quickly a book returns to original position
|
|
const START_DURATION = 120; // frames for the starting transition
|
|
const levitation = state.bookLevitation;
|
|
|
|
// Manage the global levitation state
|
|
if (levitation.state === 'resting') {
|
|
if (Math.random() < LEVITATE_CHANCE) {
|
|
levitation.state = 'starting';
|
|
levitation.timer = START_DURATION;
|
|
}
|
|
} else if (levitation.state === 'starting') {
|
|
levitation.timer--;
|
|
if (levitation.timer <= 0) {
|
|
levitation.state = 'levitating';
|
|
levitation.timer = LEVITATE_DURATION_MIN + Math.random() * (LEVITATE_DURATION_MAX - LEVITATE_DURATION_MIN);
|
|
}
|
|
} else if (levitation.state === 'levitating') {
|
|
levitation.timer--;
|
|
if (levitation.timer <= 0) {
|
|
levitation.state = 'returning';
|
|
}
|
|
}
|
|
|
|
// Animate books based on the global state
|
|
let allBooksReturned = true;
|
|
state.books.forEach(book => {
|
|
const data = book.userData;
|
|
|
|
if (levitation.state === 'starting') {
|
|
allBooksReturned = false;
|
|
book.position.y = THREE.MathUtils.lerp(book.position.y, data.originalY + LEVITATE_AMPLITUDE/2, START_RATE);
|
|
data.oscillationTime = 0;
|
|
} else if (levitation.state === 'levitating') {
|
|
allBooksReturned = false;
|
|
data.oscillationTime += LEVITATE_SPEED_FACTOR;
|
|
data.levitateOffset = Math.sin(data.oscillationTime) * LEVITATE_AMPLITUDE;
|
|
book.position.y = data.originalY + data.levitateOffset + LEVITATE_AMPLITUDE/2;
|
|
} else if (levitation.state === 'returning') {
|
|
book.position.y = THREE.MathUtils.lerp(book.position.y, data.originalY, RETURN_RATE);
|
|
data.levitateOffset = book.position.y - data.originalY;
|
|
|
|
if (Math.abs(data.levitateOffset) > 0.001) {
|
|
allBooksReturned = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (levitation.state === 'returning' && allBooksReturned) {
|
|
levitation.state = 'resting';
|
|
}
|
|
}
|
|
|
|
function updatePictureFrame() {
|
|
state.pictureFrames.forEach((pictureFrame) => {
|
|
pictureFrame.update();
|
|
});
|
|
}
|
|
|
|
// --- Animation Loop ---
|
|
export function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
state.effectsManager.update();
|
|
updateCamera();
|
|
updateLampFlicker();
|
|
updateScreenLight();
|
|
updateVideo();
|
|
updateVcr();
|
|
updateBooks();
|
|
updateDoor();
|
|
updatePictureFrame();
|
|
updateScreenEffect();
|
|
|
|
// RENDER!
|
|
state.renderer.render(state.scene, state.camera);
|
|
}
|
|
|
|
// --- Window Resize Handler ---
|
|
export function onWindowResize() {
|
|
state.camera.aspect = window.innerWidth / window.innerHeight;
|
|
state.camera.updateProjectionMatrix();
|
|
state.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
} |