// --- Animation Loop --- function animate() { requestAnimationFrame(animate); // 1. Dust animation: slow downward drift if (dust) { const positions = dust.geometry.attributes.position.array; for (let i = 1; i < positions.length; i += 3) { positions[i] -= 0.001; if (positions[i] < -2) { positions[i] = 8; } } dust.geometry.attributes.position.needsUpdate = true; } // 2. Camera movement (Gentle, random hovering) const globalTime = Date.now() * 0.00005; const lookAtTime = Date.now() * 0.00003; const camAmplitude = 0.7; const lookAmplitude = 0.05; // Base Camera Position in front of the TV const baseX = -0.5; const baseY = 1.5; const baseZ = 2.5; // 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; camera.position.x = baseX + camOffsetX; camera.position.y = baseY + camOffsetY; camera.position.z = baseZ + camOffsetZ; // LookAt Target Offsets (Subtle Gaze Shift) const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude; const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude; // Apply lookAt to the subtly shifted target camera.lookAt( baseTargetX + lookOffsetX, baseTargetY + lookOffsetY, baseTargetZ ); // 3. Lamp Flicker Effect 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 = originalLampIntensity * (0.3 + Math.random() * 0.7); lampLightSpot.intensity = lampLightIntensity; lampLightPoint.intensity = lampLightIntensity; } else if (lampLightPoint.intensity < originalLampIntensity) { // Smoothly restore original intensity let lampLightIntensity = THREE.MathUtils.lerp(lampLightPoint.intensity, originalLampIntensity, restoreRate); lampLightSpot.intensity = lampLightIntensity; lampLightPoint.intensity = lampLightIntensity; } // 4. Screen Light Pulse and Movement Effect (Updated) if (isVideoLoaded && screenLight.intensity > 0) { // A. Pulse Effect (Intensity Fluctuation) // Generate a small random fluctuation for the pulse (Range: 1.35 to 1.65 around base 1.5) const pulseTarget = originalScreenIntensity + (Math.random() - 0.5) * screenIntensityPulse; // Smoothly interpolate towards the new target fluctuation screenLight.intensity = THREE.MathUtils.lerp(screenLight.intensity, pulseTarget, 0.1); // B. Movement Effect (Subtle circle around the screen center - circling the room area) const lightTime = Date.now() * 0.0001; const radius = 0.01; const centerX = 0; const centerY = 1.5; //const centerZ = 1.2; // Use the updated Z position of the light source // Move the light in a subtle, erratic circle screenLight.position.x = centerX + Math.cos(lightTime) * radius; screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y //screenLight.position.z = centerZ; // Keep Z constant at the screen light plane } // 5. Update video texture (essential to grab the next frame) if (videoTexture) { videoTexture.needsUpdate = true; // Update time display in the animation loop if (isVideoLoaded && videoElement.readyState >= 3) { const currentTime = formatTime(videoElement.currentTime); const duration = formatTime(videoElement.duration); console.info(`Tape ${currentVideoIndex + 1} of ${videoUrls.length}. Time: ${currentTime} / ${duration}`); } } updateFlies(); const currentTime = baseTime + videoElement.currentTime; // Simulate playback time if (Math.abs(currentTime - lastUpdateTime) > 0.1) { updateVcrDisplay(currentTime); lastUpdateTime = currentTime; } // Blink the colon every second if (currentTime - lastBlinkToggleTime > 0.5) { // Blink every 0.5 seconds blinkState = !blinkState; lastBlinkToggleTime = currentTime; } // RENDER! renderer.render(scene, camera); } // --- Window Resize Handler --- function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }