diff --git a/party-cathedral/src/core/animate.js b/party-cathedral/src/core/animate.js index ec47125..1e05ba1 100644 --- a/party-cathedral/src/core/animate.js +++ b/party-cathedral/src/core/animate.js @@ -3,40 +3,6 @@ import { state } from '../state.js'; import { updateScreenEffect } from '../scene/magic-mirror.js' import sceneFeatureManager from '../scene/SceneFeatureManager.js'; -function updateCamera() { - const globalTime = Date.now() * 0.0001; - const lookAtTime = Date.now() * 0.0002; - - // Base Camera Position in front of the TV - const baseX = 0; - const baseY = 3.6; - const baseZ = -5.0; - const camAmplitude = new THREE.Vector3(1.0, 1.0, 6.0); - - // Base LookAt target (Center of the screen) - const baseTargetX = 0; - const baseTargetY = 1.6; - const baseTargetZ = -30.0; - const lookAmplitude = 8.0; - - // Camera Position Offsets (Drift) - const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude.x; - const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude.y; - const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude.z; - - 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; - const lookOffsetZ = Math.cos(lookAtTime * 2.5) * lookAmplitude; - const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude * 0.5; - - // Apply lookAt to the subtly shifted target - state.camera.lookAt(baseTargetX + lookOffsetX, baseTargetY + lookOffsetY, baseTargetZ + lookOffsetZ); -} - function updateScreenLight() { if (state.isVideoLoaded && state.screenLight.intensity > 0) { const pulseTarget = state.originalScreenIntensity + (Math.random() - 0.5) * state.screenIntensityPulse; @@ -81,7 +47,6 @@ export function animate() { sceneFeatureManager.update(deltaTime); state.effectsManager.update(); - updateCamera(); updateScreenLight(); updateVideo(); updateShaderTime(); diff --git a/party-cathedral/src/scene/camera-manager.js b/party-cathedral/src/scene/camera-manager.js new file mode 100644 index 0000000..b7e4e87 --- /dev/null +++ b/party-cathedral/src/scene/camera-manager.js @@ -0,0 +1,142 @@ +import * as THREE from 'three'; +import { state } from '../state.js'; +import { SceneFeature } from './SceneFeature.js'; +import sceneFeatureManager from './SceneFeatureManager.js'; + +const minSwitchInterval = 2; +const maxSwitchInterval = 10; + +export class CameraManager extends SceneFeature { + constructor() { + super(); + this.cameras = []; + this.activeCameraIndex = 0; + this.switchInterval = 10; // seconds + this.lastSwitchTime = 0; + sceneFeatureManager.register(this); + } + + init() { + // The main camera from init.js is our first camera + const mainCamera = state.camera; + this.cameras.push({ + camera: mainCamera, + type: 'dynamic', + name: 'MainDynamicCamera', + update: this.updateDynamicCamera, // Assign its update function + }); + + // --- Static Camera 1: Left Aisle View --- + const staticCam1 = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100); + staticCam1.position.set(-5, 3, -13); + staticCam1.lookAt(0, 2, -18); // Look at the stage + this.cameras.push({ + camera: staticCam1, + type: 'static', + name: 'LeftAisleCam' + }); + + // --- Static Camera 2: Right Aisle View --- + const staticCam2 = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100); + staticCam2.position.set(5, 4, -12); + staticCam2.lookAt(0, 1.5, -18); // Look at the stage + this.cameras.push({ + camera: staticCam2, + type: 'static', + name: 'RightAisleCam' + }); + + // --- Static Camera 3: Far-Back view --- + const staticCam3 = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100); + staticCam3.position.set(0, 3, 12); + staticCam3.lookAt(0, 1.5, -20); // Look at the stage + this.cameras.push({ + camera: staticCam3, + type: 'static', + name: 'BackCam' + }); + + // --- Static Camera 3: Back view --- + const staticCam4 = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100); + staticCam4.position.set(0, 4, 0); + staticCam4.lookAt(0, 1.5, -20); // Look at the stage + this.cameras.push({ + camera: staticCam4, + type: 'static', + name: 'BackCam' + }); + + // --- Add Debug Helpers --- + if (state.debugCamera) { + this.cameras.forEach(camData => { + const helper = new THREE.CameraHelper(camData.camera); + state.scene.add(helper); + }); + } + + this.lastSwitchTime = state.clock.getElapsedTime(); + this.switchCamera(4); + } + + // This is the logic moved from animate.js + updateDynamicCamera() { + const globalTime = Date.now() * 0.0001; + const lookAtTime = Date.now() * 0.0002; + + const baseX = 0, baseY = 3.6, baseZ = -5.0; + const camAmplitude = new THREE.Vector3(1.0, 1.0, 6.0); + + const baseTargetX = 0, baseTargetY = 1.6, baseTargetZ = -30.0; + const lookAmplitude = 8.0; + + const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude.x; + const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude.y; + const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude.z; + + state.camera.position.x = baseX + camOffsetX; + state.camera.position.y = baseY + camOffsetY; + state.camera.position.z = baseZ + camOffsetZ; + + const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude; + const lookOffsetZ = Math.cos(lookAtTime * 2.5) * lookAmplitude; + const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude * 0.5; + + state.camera.lookAt(baseTargetX + lookOffsetX, baseTargetY + lookOffsetY, baseTargetZ + lookOffsetZ); + } + + switchCamera(index) { + if (index >= this.cameras.length || index < 0) return; + + this.activeCameraIndex = index; + const newCam = this.cameras[this.activeCameraIndex].camera; + + // Copy properties from the new camera to the main state camera + state.camera.position.copy(newCam.position); + state.camera.rotation.copy(newCam.rotation); + state.camera.fov = newCam.fov; + state.camera.aspect = newCam.aspect; + state.camera.near = newCam.near; + state.camera.far = newCam.far; + state.camera.updateProjectionMatrix(); + } + + update(deltaTime) { + const time = state.clock.getElapsedTime(); + + // Handle camera switching + if (time > this.lastSwitchTime + this.switchInterval) { + const newIndex = Math.floor(Math.random() * this.cameras.length); + this.switchCamera(newIndex); + this.lastSwitchTime = time; + this.switchInterval = minSwitchInterval + Math.random() * (maxSwitchInterval - minSwitchInterval); + } + + // Update the currently active camera if it has an update function + const activeCamData = this.cameras[this.activeCameraIndex]; + if (activeCamData.update) { + activeCamData.update(); + } + } +} + +new CameraManager(); \ No newline at end of file diff --git a/party-cathedral/src/scene/dancers.js b/party-cathedral/src/scene/dancers.js index 16f5cd0..d525e98 100644 --- a/party-cathedral/src/scene/dancers.js +++ b/party-cathedral/src/scene/dancers.js @@ -169,7 +169,8 @@ export class Dancers extends SceneFeature { mesh.position.y = dancerObj.baseY; } } else { - if (state.music && state.music.beatIntensity > 0.8 && Math.random() < 0.2) { + const musicTime = state.clock.getElapsedTime(); + if (state.music && state.music.beatIntensity > 0.8 && Math.random() < 0.2 && musicTime > 10) { dancerObj.isJumping = true; dancerObj.jumpStartTime = time; } diff --git a/party-cathedral/src/scene/medieval-musicians.js b/party-cathedral/src/scene/medieval-musicians.js index d6dbc06..048475c 100644 --- a/party-cathedral/src/scene/medieval-musicians.js +++ b/party-cathedral/src/scene/medieval-musicians.js @@ -259,7 +259,8 @@ export class MedievalMusicians extends SceneFeature { } } else { let currentJumpChance = jumpChance * deltaTime; // Base chance over time - if (state.music && state.music.beatIntensity > 0.8) { + const musicTime = state.clock.getElapsedTime(); + if (state.music && state.music.beatIntensity > 0.8 && musicTime > 15) { currentJumpChance = 0.1; // High, fixed chance on the beat } diff --git a/party-cathedral/src/scene/party-guests.js b/party-cathedral/src/scene/party-guests.js index e992098..aee0520 100644 --- a/party-cathedral/src/scene/party-guests.js +++ b/party-cathedral/src/scene/party-guests.js @@ -69,7 +69,7 @@ export class PartyGuests extends SceneFeature { const pos = new THREE.Vector3( (Math.random() - 0.5) * 10, guestHeight / 2, - (Math.random() * 20) - 6 // Position them in the main hall + (Math.random() * 20) - 2 // Position them in the main hall ); guest.position.copy(pos); state.scene.add(guest); @@ -99,7 +99,7 @@ export class PartyGuests extends SceneFeature { const time = state.clock.getElapsedTime(); const moveSpeed = 1.0; // Move slower - const movementArea = { x: 10, z: 30, y: 0, centerZ: 5 }; + const movementArea = { x: 10, z: 30, y: 0, centerZ: 2 }; const jumpChance = 0.05; // Jump way more const jumpDuration = 0.5; const jumpHeight = 0.1; diff --git a/party-cathedral/src/scene/root.js b/party-cathedral/src/scene/root.js index 5ffcfa5..426ebf9 100644 --- a/party-cathedral/src/scene/root.js +++ b/party-cathedral/src/scene/root.js @@ -3,6 +3,7 @@ import { state } from '../state.js'; import floorTextureUrl from '/textures/stone_floor.png'; import sceneFeatureManager from './SceneFeatureManager.js'; // Scene Features registered here: +import { CameraManager } from './camera-manager.js'; import { RoomWalls } from './room-walls.js'; import { LightBall } from './light-ball.js'; import { Pews } from './pews.js'; @@ -14,7 +15,6 @@ import { Dancers } from './dancers.js'; import { MusicVisualizer } from './music-visualizer.js'; import { RoseWindowLight } from './rose-window-light.js'; import { RoseWindowLightshafts } from './rose-window-lightshafts.js'; - // Scene Features ^^^ // --- Scene Modeling Function --- diff --git a/party-cathedral/src/state.js b/party-cathedral/src/state.js index f7aade6..4910de4 100644 --- a/party-cathedral/src/state.js +++ b/party-cathedral/src/state.js @@ -36,7 +36,8 @@ export function initState() { screenIntensityPulse: 0.2, roomSize: 5, roomHeight: 3, - debugLight: false, + debugLight: false, // Turn on light helpers + debugCamera: false, // Turn on camera helpers // DOM Elements container: document.body,