Initial commit
This commit is contained in:
181
assets/js/logo/reactive-logo.js
Normal file
181
assets/js/logo/reactive-logo.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Reactive Logo
|
||||
* Small WebGL canvas logo that reacts to audio and mouse
|
||||
*/
|
||||
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class ReactiveLogo {
|
||||
constructor(canvas, audioManager) {
|
||||
this.canvas = canvas;
|
||||
this.audioManager = audioManager;
|
||||
this.mouse = { x: 0, y: 0 };
|
||||
this.running = false;
|
||||
this.time = 0;
|
||||
|
||||
if (!canvas) {
|
||||
console.warn('ReactiveLogo: No canvas provided');
|
||||
return;
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const size = 32;
|
||||
|
||||
// Scene
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
// Camera
|
||||
this.camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
|
||||
this.camera.position.z = 3;
|
||||
|
||||
// Renderer
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
canvas: this.canvas,
|
||||
alpha: true,
|
||||
antialias: true
|
||||
});
|
||||
this.renderer.setSize(size, size);
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
|
||||
// Create logo geometry (simple points in a circular pattern)
|
||||
this.createLogoParticles();
|
||||
|
||||
// Mouse tracking
|
||||
this.canvas.addEventListener('mouseenter', () => this.onMouseEnter());
|
||||
this.canvas.addEventListener('mouseleave', () => this.onMouseLeave());
|
||||
window.addEventListener('mousemove', (e) => this.onMouseMove(e));
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
createLogoParticles() {
|
||||
const count = 50;
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(count * 3);
|
||||
const randoms = new Float32Array(count);
|
||||
|
||||
// Create points in a circular pattern
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = (i / count) * Math.PI * 2;
|
||||
const radius = 0.8 + Math.random() * 0.2;
|
||||
|
||||
positions[i * 3] = Math.cos(angle) * radius;
|
||||
positions[i * 3 + 1] = Math.sin(angle) * radius;
|
||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.2;
|
||||
|
||||
randoms[i] = Math.random();
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
|
||||
|
||||
const material = new THREE.ShaderMaterial({
|
||||
vertexShader: `
|
||||
uniform float uTime;
|
||||
uniform float uAudio;
|
||||
attribute float aRandom;
|
||||
|
||||
void main() {
|
||||
vec3 pos = position;
|
||||
|
||||
// Bouncy pulsing
|
||||
float pulse = 1.0 + sin(uTime * 5.0 + aRandom * 6.28) * 0.15;
|
||||
pulse += sin(uTime * 8.0 + aRandom * 3.14) * 0.1;
|
||||
pos *= pulse;
|
||||
|
||||
// Audio reactivity - bouncy
|
||||
float audioBounce = uAudio * (1.0 + sin(uTime * 10.0) * 0.3);
|
||||
pos *= 1.0 + audioBounce * 0.6;
|
||||
|
||||
vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
|
||||
gl_PointSize = 3.0 * (1.0 + uAudio * 0.8);
|
||||
gl_Position = projectionMatrix * mvPos;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
void main() {
|
||||
float dist = length(gl_PointCoord - 0.5);
|
||||
if (dist > 0.5) discard;
|
||||
float alpha = 1.0 - smoothstep(0.2, 0.5, dist);
|
||||
gl_FragColor = vec4(1.0, 1.0, 1.0, alpha);
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uAudio: { value: 0 }
|
||||
},
|
||||
transparent: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false
|
||||
});
|
||||
|
||||
this.logoMesh = new THREE.Points(geometry, material);
|
||||
this.scene.add(this.logoMesh);
|
||||
}
|
||||
|
||||
onMouseEnter() {
|
||||
this.isHovered = true;
|
||||
}
|
||||
|
||||
onMouseLeave() {
|
||||
this.isHovered = false;
|
||||
}
|
||||
|
||||
onMouseMove(e) {
|
||||
if (!this.isHovered) return;
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
this.mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
this.mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
this.animate();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
animate() {
|
||||
if (!this.running) return;
|
||||
requestAnimationFrame(() => this.animate());
|
||||
|
||||
this.time += 0.016;
|
||||
|
||||
// Get audio level
|
||||
let audioLevel = 0;
|
||||
if (this.audioManager?.isInitialized) {
|
||||
const bands = this.audioManager.getFrequencyBands();
|
||||
audioLevel = (bands.low + bands.mid + bands.high) / 3;
|
||||
}
|
||||
|
||||
// Update uniforms
|
||||
if (this.logoMesh?.material?.uniforms) {
|
||||
this.logoMesh.material.uniforms.uTime.value = this.time;
|
||||
this.logoMesh.material.uniforms.uAudio.value = audioLevel;
|
||||
}
|
||||
|
||||
// Rotate based on mouse
|
||||
if (this.logoMesh) {
|
||||
this.logoMesh.rotation.x += (this.mouse.y * 0.5 - this.logoMesh.rotation.x) * 0.1;
|
||||
this.logoMesh.rotation.y += (this.mouse.x * 0.5 - this.logoMesh.rotation.y) * 0.1;
|
||||
this.logoMesh.rotation.z = this.time * 0.2;
|
||||
}
|
||||
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.running = false;
|
||||
this.logoMesh?.geometry?.dispose();
|
||||
this.logoMesh?.material?.dispose();
|
||||
this.renderer?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactiveLogo;
|
||||
Reference in New Issue
Block a user