feat(visualizer): add multiple visualizers with mouse tracking and beat response
- Add BaseVisualizer class for shared visualizer logic - Add Helix visualizer (DNA helix with rungs, beat compression/bounce) - Add Tunnel visualizer (ring tunnel with depth parallax) - Refactor SphereVisualizer to extend BaseVisualizer - Add mouse tracking to all visualizers (canvas-relative, centered) - Add visualizer switching via logo click (persisted to localStorage) - Add beat-responsive bouncing and compression effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
90
assets/js/visualizer/base-visualizer.js
Normal file
90
assets/js/visualizer/base-visualizer.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Base Visualizer Class
|
||||
* Abstract base for all audio visualizers
|
||||
*/
|
||||
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class BaseVisualizer {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.mesh = null;
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize geometry, materials, and mesh
|
||||
* Override in subclasses
|
||||
*/
|
||||
init() {
|
||||
throw new Error('init() must be implemented by subclass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visualizer with audio data
|
||||
* @param {Object} bands - { low, mid, high } frequency bands (0-1)
|
||||
* @param {number} time - Animation time
|
||||
*/
|
||||
update(bands, time) {
|
||||
throw new Error('update() must be implemented by subclass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add visualizer to scene
|
||||
*/
|
||||
show() {
|
||||
if (this.mesh && !this.isVisible) {
|
||||
this.scene.add(this.mesh);
|
||||
this.isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove visualizer from scene
|
||||
*/
|
||||
hide() {
|
||||
if (this.mesh && this.isVisible) {
|
||||
this.scene.remove(this.mesh);
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup resources
|
||||
*/
|
||||
dispose() {
|
||||
this.hide();
|
||||
if (this.mesh) {
|
||||
this.mesh.geometry?.dispose();
|
||||
if (this.mesh.material) {
|
||||
if (Array.isArray(this.mesh.material)) {
|
||||
this.mesh.material.forEach(m => m.dispose());
|
||||
} else {
|
||||
this.mesh.material.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Create shader material with common settings
|
||||
*/
|
||||
createShaderMaterial(vertexShader, fragmentShader, uniforms = {}) {
|
||||
return new THREE.ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uLow: { value: 0 },
|
||||
uMid: { value: 0 },
|
||||
uHigh: { value: 0 },
|
||||
...uniforms
|
||||
},
|
||||
transparent: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseVisualizer;
|
||||
Reference in New Issue
Block a user