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:
@@ -1,17 +1,19 @@
|
||||
/**
|
||||
* Particle System
|
||||
* Particle System (Sphere) Visualizer
|
||||
* Audio-reactive 3D particle cloud
|
||||
*/
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { BaseVisualizer } from './base-visualizer.js';
|
||||
|
||||
// Vertex Shader
|
||||
const vertexShader = `
|
||||
uniform float uTime;
|
||||
uniform float uLow;
|
||||
uniform float uMid;
|
||||
uniform float uHigh;
|
||||
uniform float uSize;
|
||||
uniform float uMouseX;
|
||||
uniform float uMouseY;
|
||||
|
||||
attribute float aRandom;
|
||||
attribute float aScale;
|
||||
@@ -42,6 +44,10 @@ void main() {
|
||||
vec3 jitter = normalize(pos) * uHigh * aRandom * 5.0;
|
||||
pos += jitter;
|
||||
|
||||
// Mouse offset
|
||||
pos.x -= uMouseX * 3.0;
|
||||
pos.y -= uMouseY * 2.0;
|
||||
|
||||
// Calculate view position
|
||||
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
||||
|
||||
@@ -57,7 +63,6 @@ void main() {
|
||||
}
|
||||
`;
|
||||
|
||||
// Fragment Shader
|
||||
const fragmentShader = `
|
||||
varying float vAlpha;
|
||||
|
||||
@@ -74,17 +79,16 @@ void main() {
|
||||
}
|
||||
`;
|
||||
|
||||
export class ParticleSystem {
|
||||
constructor(count = 5000) {
|
||||
export class SphereVisualizer extends BaseVisualizer {
|
||||
static name = 'Sphere';
|
||||
|
||||
constructor(scene, count = 5000) {
|
||||
super(scene);
|
||||
this.count = count;
|
||||
this.createGeometry();
|
||||
this.createMaterial();
|
||||
this.mesh = new THREE.Points(this.geometry, this.material);
|
||||
this.init();
|
||||
}
|
||||
|
||||
createGeometry() {
|
||||
this.geometry = new THREE.BufferGeometry();
|
||||
|
||||
init() {
|
||||
const positions = new Float32Array(this.count * 3);
|
||||
const randoms = new Float32Array(this.count);
|
||||
const scales = new Float32Array(this.count);
|
||||
@@ -105,41 +109,37 @@ export class ParticleSystem {
|
||||
scales[i] = 0.5 + Math.random() * 1.5;
|
||||
}
|
||||
|
||||
this.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
this.geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
|
||||
this.geometry.setAttribute('aScale', new THREE.BufferAttribute(scales, 1));
|
||||
}
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
|
||||
geometry.setAttribute('aScale', new THREE.BufferAttribute(scales, 1));
|
||||
|
||||
createMaterial() {
|
||||
this.material = new THREE.ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uLow: { value: 0 },
|
||||
uMid: { value: 0 },
|
||||
uHigh: { value: 0 },
|
||||
uSize: { value: 2.0 }
|
||||
},
|
||||
transparent: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false
|
||||
this.material = this.createShaderMaterial(vertexShader, fragmentShader, {
|
||||
uSize: { value: 2.0 },
|
||||
uMouseX: { value: 0 },
|
||||
uMouseY: { value: 0 }
|
||||
});
|
||||
|
||||
this.mesh = new THREE.Points(geometry, this.material);
|
||||
}
|
||||
|
||||
update(bands, time) {
|
||||
update(bands, time, mouse = { x: 0, y: 0 }) {
|
||||
if (!this.material) return;
|
||||
|
||||
this.material.uniforms.uTime.value = time;
|
||||
this.material.uniforms.uLow.value = bands.low || 0;
|
||||
this.material.uniforms.uMid.value = bands.mid || 0;
|
||||
this.material.uniforms.uHigh.value = bands.high || 0;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry?.dispose();
|
||||
this.material?.dispose();
|
||||
this.material.uniforms.uMouseX.value = mouse.x;
|
||||
this.material.uniforms.uMouseY.value = mouse.y;
|
||||
}
|
||||
}
|
||||
|
||||
export default ParticleSystem;
|
||||
// Legacy export for backwards compatibility
|
||||
export class ParticleSystem extends SphereVisualizer {
|
||||
constructor(count = 5000) {
|
||||
super(null, count);
|
||||
}
|
||||
}
|
||||
|
||||
export default SphereVisualizer;
|
||||
|
||||
Reference in New Issue
Block a user