import * as THREE from 'three'; import { state } from '../state.js'; import { SceneFeature } from './SceneFeature.js'; import sceneFeatureManager from './SceneFeatureManager.js'; // --- Dimensions from room-walls.js for positioning --- const naveWidth = 12; const naveHeight = 15; const length = 40; export class LightBall extends SceneFeature { constructor() { super(); this.lightBalls = []; sceneFeatureManager.register(this); } init() { // --- Ball Properties --- const ballRadius = 0.2; const lightIntensity = 5.0; const lightColors = [0xff2222, 0x11ff11, 0x2222ff, 0xffff11, 0x00ffff, 0xff00ff]; // Red, Green, Blue, Yellow lightColors.forEach(color => { // --- Create the Ball --- const ballGeometry = new THREE.SphereGeometry(ballRadius, 32, 32); const ballMaterial = new THREE.MeshBasicMaterial({ color: color, emissive: color, emissiveIntensity: 1.2 }); const ball = new THREE.Mesh(ballGeometry, ballMaterial); ball.castShadow = false; ball.receiveShadow = false; ball.visible = false; // Start invisible // --- Create the Light --- const light = new THREE.PointLight(color, lightIntensity, length / 1.5); light.visible = false; // Start invisible // --- Initial Position --- ball.position.set( (Math.random() - 0.5) * naveWidth, naveHeight * 0.6 + Math.random() * 4, (Math.random() - 0.5) * length * 0.8 ); light.position.copy(ball.position); //state.scene.add(ball); // no need to show the ball state.scene.add(light); this.lightBalls.push({ mesh: ball, light: light, driftSpeed: 0.2 + Math.random() * 0.2, driftAmplitude: 4.0 + Math.random() * 4.0, offset: Math.random() * Math.PI * 6, }); }); } update(deltaTime) { if (!state.partyStarted) return; const time = state.clock.getElapsedTime(); this.lightBalls.forEach(lb => { const { mesh, light, driftSpeed, offset } = lb; mesh.position.x = Math.sin(time * driftSpeed + offset) * naveWidth/2 * 0.8; mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6; mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8; light.position.copy(mesh.position); // --- Music Visualization --- if (state.music) { const baseIntensity = 4.0; light.intensity = baseIntensity + state.music.beatIntensity * 3.0; const baseScale = 1.0; mesh.scale.setScalar(baseScale + state.music.beatIntensity * 0.5); } }); } onPartyStart() { this.lightBalls.forEach(lb => { //lb.mesh.visible = true; // no visible ball lb.light.visible = true; }); } onPartyEnd() { this.lightBalls.forEach(lb => { lb.mesh.visible = false; lb.light.visible = false; }); } } new LightBall();