/** * WebGL Visualizer Scene * Three.js particle system that reacts to audio frequency data */ import * as THREE from 'three'; import { ParticleSystem } from './particles.js'; export class Visualizer { constructor(canvas, audioManager) { this.canvas = canvas; this.audioManager = audioManager; this.running = false; this.time = 0; if (!canvas) { console.warn('Visualizer: No canvas provided'); return; } this.init(); } init() { // Scene setup this.scene = new THREE.Scene(); // Camera this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.z = 50; // Renderer this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas, alpha: true, antialias: true, powerPreference: 'high-performance' }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Particles this.particles = new ParticleSystem(5000); this.scene.add(this.particles.mesh); // Event listeners window.addEventListener('resize', () => this.resize()); // Start animation this.start(); } resize() { if (!this.camera || !this.renderer) return; this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } start() { if (this.running) return; this.running = true; this.animate(); } pause() { this.running = false; } resume() { this.start(); } animate() { if (!this.running) return; requestAnimationFrame(() => this.animate()); this.time += 0.01; // Get audio frequency bands let bands = { low: 0, mid: 0, high: 0 }; if (this.audioManager?.isInitialized) { bands = this.audioManager.getFrequencyBands(); } // Update particles with audio data this.particles.update(bands, this.time); // Subtle camera movement this.camera.position.x = Math.sin(this.time * 0.1) * 3; this.camera.position.y = Math.cos(this.time * 0.15) * 2; this.camera.lookAt(0, 0, 0); this.renderer.render(this.scene, this.camera); } destroy() { this.running = false; this.particles?.dispose(); this.renderer?.dispose(); } } export default Visualizer;