159 lines
4.2 KiB
JavaScript
159 lines
4.2 KiB
JavaScript
|
|
/**
|
||
|
|
* Galaxy Visualizer
|
||
|
|
* Spiral arm formation with rotating particles
|
||
|
|
*/
|
||
|
|
|
||
|
|
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 aArm;
|
||
|
|
attribute float aOffset;
|
||
|
|
attribute float aRadius;
|
||
|
|
|
||
|
|
varying float vAlpha;
|
||
|
|
varying float vArm;
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
// Spiral arm parameters
|
||
|
|
float arms = 3.0;
|
||
|
|
float armAngle = aArm * (6.28318 / arms);
|
||
|
|
|
||
|
|
// Radius with some variation
|
||
|
|
float radius = aRadius * (1.0 + uLow * 0.3);
|
||
|
|
|
||
|
|
// Spiral: angle increases with radius
|
||
|
|
float spiralTightness = 0.15;
|
||
|
|
float angle = armAngle + aRadius * spiralTightness + uTime * (0.2 + uMid * 0.5);
|
||
|
|
|
||
|
|
// Add offset for thickness
|
||
|
|
angle += aOffset * 0.3;
|
||
|
|
float radiusOffset = aOffset * 3.0;
|
||
|
|
|
||
|
|
// Calculate position
|
||
|
|
float x = cos(angle) * (radius + radiusOffset);
|
||
|
|
float y = sin(angle) * (radius + radiusOffset);
|
||
|
|
|
||
|
|
// Height variation - thicker disk toward center
|
||
|
|
float z = sin(aOffset * 3.0 + aRadius * 0.1) * (3.0 - aRadius * 0.05);
|
||
|
|
z += uLow * 5.0; // Bounce with bass
|
||
|
|
|
||
|
|
// High frequencies add sparkle
|
||
|
|
x += sin(aOffset * 20.0 + uTime * 5.0) * uHigh * 2.0;
|
||
|
|
y += cos(aOffset * 20.0 + uTime * 5.0) * uHigh * 2.0;
|
||
|
|
|
||
|
|
// Tilt the galaxy
|
||
|
|
float tiltX = 0.3;
|
||
|
|
float cosX = cos(tiltX);
|
||
|
|
float sinX = sin(tiltX);
|
||
|
|
float newY = y * cosX - z * sinX;
|
||
|
|
float newZ = y * sinX + z * cosX;
|
||
|
|
y = newY;
|
||
|
|
z = newZ;
|
||
|
|
|
||
|
|
// Mouse offset
|
||
|
|
x -= uMouseX * 5.0;
|
||
|
|
y -= uMouseY * 4.0;
|
||
|
|
|
||
|
|
vec3 pos = vec3(x, y, z);
|
||
|
|
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
||
|
|
|
||
|
|
// Size - larger toward center, pulses with bass
|
||
|
|
float size = 2.5 * (1.0 - aRadius / 60.0) + uLow * 1.0;
|
||
|
|
gl_PointSize = size * (300.0 / -mvPosition.z);
|
||
|
|
|
||
|
|
// Alpha - brighter toward center and in arm cores
|
||
|
|
vAlpha = (0.7 - aRadius / 80.0) * (1.0 - abs(aOffset) * 0.3) + uLow * 0.2;
|
||
|
|
vArm = aArm;
|
||
|
|
|
||
|
|
gl_Position = projectionMatrix * mvPosition;
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
|
||
|
|
const fragmentShader = `
|
||
|
|
varying float vAlpha;
|
||
|
|
varying float vArm;
|
||
|
|
|
||
|
|
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 GalaxyVisualizer extends BaseVisualizer {
|
||
|
|
static name = 'Galaxy';
|
||
|
|
|
||
|
|
constructor(scene) {
|
||
|
|
super(scene);
|
||
|
|
this.particleCount = 4000;
|
||
|
|
this.arms = 3;
|
||
|
|
this.init();
|
||
|
|
}
|
||
|
|
|
||
|
|
init() {
|
||
|
|
const positions = new Float32Array(this.particleCount * 3);
|
||
|
|
const armIndices = new Float32Array(this.particleCount);
|
||
|
|
const offsets = new Float32Array(this.particleCount);
|
||
|
|
const radii = new Float32Array(this.particleCount);
|
||
|
|
|
||
|
|
for (let i = 0; i < this.particleCount; i++) {
|
||
|
|
// Assign to an arm
|
||
|
|
const arm = Math.floor(Math.random() * this.arms);
|
||
|
|
|
||
|
|
// Radius - more particles toward center
|
||
|
|
const radius = Math.pow(Math.random(), 0.5) * 50;
|
||
|
|
|
||
|
|
// Offset from arm center
|
||
|
|
const offset = (Math.random() - 0.5) * 2;
|
||
|
|
|
||
|
|
positions[i * 3] = 0;
|
||
|
|
positions[i * 3 + 1] = 0;
|
||
|
|
positions[i * 3 + 2] = 0;
|
||
|
|
|
||
|
|
armIndices[i] = arm;
|
||
|
|
offsets[i] = offset;
|
||
|
|
radii[i] = radius;
|
||
|
|
}
|
||
|
|
|
||
|
|
const geometry = new THREE.BufferGeometry();
|
||
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||
|
|
geometry.setAttribute('aArm', new THREE.BufferAttribute(armIndices, 1));
|
||
|
|
geometry.setAttribute('aOffset', new THREE.BufferAttribute(offsets, 1));
|
||
|
|
geometry.setAttribute('aRadius', new THREE.BufferAttribute(radii, 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 GalaxyVisualizer;
|