feat: add 6 new audio visualizers
- Vortex: spiral particles pulled toward center, speed reacts to beat - Starfield: flying through stars with depth parallax and streak effects - Grid: 3D wave plane with ripple effects from mouse and audio - Galaxy: 3-arm spiral galaxy with tilted perspective - Waveform: circular audio waveform in concentric rings - Kaleidoscope: 8-segment mirrored geometric patterns All visualizers include: - GLSL shaders with audio reactivity (low/mid/high frequencies) - Mouse tracking for interactive parallax - Beat-synchronized animations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
152
assets/js/visualizer/kaleidoscope.js
Normal file
152
assets/js/visualizer/kaleidoscope.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Kaleidoscope Visualizer
|
||||
* Mirrored geometric patterns that morph with audio
|
||||
*/
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { BaseVisualizer } from './base-visualizer.js';
|
||||
|
||||
const vertexShader = `
|
||||
uniform float uTime;
|
||||
uniform float uLow;
|
||||
uniform float uMid;
|
||||
uniform float uHigh;
|
||||
uniform float uMouseX;
|
||||
uniform float uMouseY;
|
||||
|
||||
attribute float aSegment;
|
||||
attribute float aLayer;
|
||||
attribute float aProgress;
|
||||
|
||||
varying float vAlpha;
|
||||
varying float vSegment;
|
||||
|
||||
void main() {
|
||||
// Kaleidoscope parameters
|
||||
float segments = 8.0;
|
||||
float segmentAngle = 6.28318 / segments;
|
||||
|
||||
// Base angle for this segment
|
||||
float baseAngle = aSegment * segmentAngle;
|
||||
|
||||
// Create pattern within segment
|
||||
float patternRadius = 10.0 + aLayer * 15.0 + uLow * 10.0;
|
||||
float patternAngle = aProgress * segmentAngle * 0.8; // Stay within segment
|
||||
|
||||
// Mirror effect - alternate segments are flipped
|
||||
float mirror = mod(aSegment, 2.0) < 1.0 ? 1.0 : -1.0;
|
||||
patternAngle *= mirror;
|
||||
|
||||
// Add time-based morphing
|
||||
float morph = sin(uTime * 2.0 + aLayer) * (5.0 + uMid * 10.0);
|
||||
patternRadius += morph;
|
||||
|
||||
// Final angle
|
||||
float angle = baseAngle + patternAngle + uTime * 0.2;
|
||||
|
||||
// High frequencies add shimmer
|
||||
float shimmer = sin(aProgress * 20.0 + uTime * 8.0) * uHigh * 3.0;
|
||||
patternRadius += shimmer;
|
||||
|
||||
// Calculate position
|
||||
float x = cos(angle) * patternRadius;
|
||||
float y = sin(angle) * patternRadius;
|
||||
float z = sin(aProgress * 6.28 + uTime * 2.0) * (3.0 + uLow * 5.0);
|
||||
|
||||
// Mouse offset
|
||||
x -= uMouseX * 5.0;
|
||||
y -= uMouseY * 4.0;
|
||||
|
||||
vec3 pos = vec3(x, y, z);
|
||||
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
||||
|
||||
// Size varies with layer
|
||||
float size = 2.0 + aLayer * 0.5 + uLow * 1.0;
|
||||
gl_PointSize = size * (300.0 / -mvPosition.z);
|
||||
|
||||
// Alpha - inner layers brighter
|
||||
vAlpha = 0.8 - aLayer * 0.1 + uLow * 0.2;
|
||||
vSegment = aSegment;
|
||||
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentShader = `
|
||||
varying float vAlpha;
|
||||
varying float vSegment;
|
||||
|
||||
void main() {
|
||||
float dist = length(gl_PointCoord - vec2(0.5));
|
||||
if (dist > 0.5) discard;
|
||||
|
||||
float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
|
||||
alpha *= vAlpha;
|
||||
|
||||
vec3 color = vec3(1.0);
|
||||
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
export class KaleidoscopeVisualizer extends BaseVisualizer {
|
||||
static name = 'Kaleidoscope';
|
||||
|
||||
constructor(scene) {
|
||||
super(scene);
|
||||
this.segments = 8;
|
||||
this.layers = 5;
|
||||
this.pointsPerSegmentLayer = 30;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const count = this.segments * this.layers * this.pointsPerSegmentLayer;
|
||||
const positions = new Float32Array(count * 3);
|
||||
const segmentIndices = new Float32Array(count);
|
||||
const layerIndices = new Float32Array(count);
|
||||
const progress = new Float32Array(count);
|
||||
|
||||
let idx = 0;
|
||||
for (let seg = 0; seg < this.segments; seg++) {
|
||||
for (let layer = 0; layer < this.layers; layer++) {
|
||||
for (let p = 0; p < this.pointsPerSegmentLayer; p++) {
|
||||
positions[idx * 3] = 0;
|
||||
positions[idx * 3 + 1] = 0;
|
||||
positions[idx * 3 + 2] = 0;
|
||||
|
||||
segmentIndices[idx] = seg;
|
||||
layerIndices[idx] = layer;
|
||||
progress[idx] = p / this.pointsPerSegmentLayer;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('aSegment', new THREE.BufferAttribute(segmentIndices, 1));
|
||||
geometry.setAttribute('aLayer', new THREE.BufferAttribute(layerIndices, 1));
|
||||
geometry.setAttribute('aProgress', new THREE.BufferAttribute(progress, 1));
|
||||
|
||||
this.material = this.createShaderMaterial(vertexShader, fragmentShader, {
|
||||
uMouseX: { value: 0 },
|
||||
uMouseY: { value: 0 }
|
||||
});
|
||||
|
||||
this.mesh = new THREE.Points(geometry, this.material);
|
||||
}
|
||||
|
||||
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;
|
||||
this.material.uniforms.uMouseX.value = mouse.x;
|
||||
this.material.uniforms.uMouseY.value = mouse.y;
|
||||
}
|
||||
}
|
||||
|
||||
export default KaleidoscopeVisualizer;
|
||||
Reference in New Issue
Block a user