diff --git a/party-cathedral/src/core/animate.js b/party-cathedral/src/core/animate.js index 2b2bf32..d322e0d 100644 --- a/party-cathedral/src/core/animate.js +++ b/party-cathedral/src/core/animate.js @@ -13,12 +13,12 @@ function updateCamera() { // Base Camera Position in front of the TV const baseX = 0; const baseY = 1.6; - const baseZ = 10.0; + const baseZ = -10.0; // Base LookAt target (Center of the screen) const baseTargetX = 0; const baseTargetY = 1.6; - const baseTargetZ = -10.0; + const baseTargetZ = -30.0; // Camera Position Offsets (Drift) const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude; diff --git a/party-cathedral/src/scene/medieval-musicians.js b/party-cathedral/src/scene/medieval-musicians.js index cbeed8b..d377751 100644 --- a/party-cathedral/src/scene/medieval-musicians.js +++ b/party-cathedral/src/scene/medieval-musicians.js @@ -1 +1,101 @@ -// This file will contain the Three.js code for creating and animating the medieval musicians on the stage. \ No newline at end of file +import * as THREE from 'three'; +import { state } from '../state.js'; +import { SceneFeature } from './SceneFeature.js'; +import sceneFeatureManager from './SceneFeatureManager.js'; +import musiciansTextureUrl from '/textures/musician1.png'; + +export class MedievalMusicians extends SceneFeature { + constructor() { + super(); + this.musicians = []; + sceneFeatureManager.register(this); + } + + init() { + // --- Stage dimensions for positioning --- + const stageHeight = 1.5; + const stageDepth = 5; + const length = 40; + + // --- Billboard Properties --- + const musicianHeight = 2.5; + const musicianWidth = 2.5; + + // Load the texture and create the material inside the callback + state.loader.load(musiciansTextureUrl, (texture) => { + // 1. Draw texture to canvas to process it + const image = texture.image; + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); + + // 2. Get the key color from the top-left pixel + const keyPixelData = context.getImageData(0, 0, 1, 1).data; + const keyColor = { r: keyPixelData[0], g: keyPixelData[1], b: keyPixelData[2] }; + + // 3. Process the entire canvas to make background transparent + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + const threshold = 20; // Adjust this for more/less color tolerance + + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + + const distance = Math.sqrt(Math.pow(r - keyColor.r, 2) + Math.pow(g - keyColor.g, 2) + Math.pow(b - keyColor.b, 2)); + + if (distance < threshold) { + data[i + 3] = 0; // Set alpha to 0 (transparent) + } + } + context.putImageData(imageData, 0, 0); + + // 4. Create a new texture from the modified canvas + const processedTexture = new THREE.CanvasTexture(canvas); + + // 5. Create a standard material with the new texture + const material = new THREE.MeshStandardMaterial({ + map: processedTexture, + side: THREE.DoubleSide, + transparent: true, + roughness: 0.7, + metalness: 0.1, + }); + + // 6. Create and position the musicians + const geometry = new THREE.PlaneGeometry(musicianWidth, musicianHeight); + + const musicianPositions = [ + new THREE.Vector3(-2, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1), + new THREE.Vector3(0, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.5), + new THREE.Vector3(2.5, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.2), + ]; + + musicianPositions.forEach(pos => { + const musician = new THREE.Mesh(geometry, material); + musician.position.copy(pos); + + state.scene.add(musician); + this.musicians.push(musician); + }); + }); + } + + update(deltaTime) { + // Billboard effect: make each musician face the camera + if (this.musicians.length > 0) { + const cameraPosition = new THREE.Vector3(); + state.camera.getWorldPosition(cameraPosition); + + this.musicians.forEach(musician => { + // We only want to rotate on the Y axis to keep them upright + musician.lookAt(cameraPosition.x, musician.position.y, cameraPosition.z); + }); + } + } +} + +new MedievalMusicians(); \ No newline at end of file diff --git a/party-cathedral/src/scene/root.js b/party-cathedral/src/scene/root.js index 6240586..159974b 100644 --- a/party-cathedral/src/scene/root.js +++ b/party-cathedral/src/scene/root.js @@ -6,6 +6,8 @@ import sceneFeatureManager from './SceneFeatureManager.js'; import { RoomWalls } from './room-walls.js'; import { LightBall } from './light-ball.js'; import { Pews } from './pews.js'; +import { Stage } from './stage.js'; +import { MedievalMusicians } from './medieval-musicians.js'; // Scene Features ^^^ // --- Scene Modeling Function --- diff --git a/party-cathedral/src/scene/stage.js b/party-cathedral/src/scene/stage.js index 27ac726..8d02924 100644 --- a/party-cathedral/src/scene/stage.js +++ b/party-cathedral/src/scene/stage.js @@ -1 +1,44 @@ -// This file will contain the Three.js code for creating the stage at the front of the cathedral. \ No newline at end of file +import * as THREE from 'three'; +import { state } from '../state.js'; +import { SceneFeature } from './SceneFeature.js'; +import sceneFeatureManager from './SceneFeatureManager.js'; +import woodTextureUrl from '/textures/wood.png'; + +export class Stage extends SceneFeature { + constructor() { + super(); + sceneFeatureManager.register(this); + } + + init() { + // --- Dimensions from room-walls.js for positioning --- + const length = 40; + const naveWidth = 12; + + // --- Stage Properties --- + const stageWidth = naveWidth - 1; // Slightly narrower than the nave + const stageHeight = 1.5; + const stageDepth = 5; + + // --- Material --- + const woodTexture = state.loader.load(woodTextureUrl); + woodTexture.wrapS = THREE.RepeatWrapping; + woodTexture.wrapT = THREE.RepeatWrapping; + woodTexture.repeat.set(stageWidth / 2, stageDepth / 2); + const woodMaterial = new THREE.MeshStandardMaterial({ + map: woodTexture, + roughness: 0.8, + metalness: 0.1, + }); + + // --- Create Stage Mesh --- + const stageGeo = new THREE.BoxGeometry(stageWidth, stageHeight, stageDepth); + const stageMesh = new THREE.Mesh(stageGeo, woodMaterial); + stageMesh.castShadow = true; + stageMesh.receiveShadow = true; + stageMesh.position.set(0, stageHeight / 2, -length / 2 + stageDepth / 2); + state.scene.add(stageMesh); + } +} + +new Stage(); \ No newline at end of file diff --git a/party-cathedral/textures/musician1.png b/party-cathedral/textures/musician1.png new file mode 100644 index 0000000..6700c30 Binary files /dev/null and b/party-cathedral/textures/musician1.png differ