Prompt
User interactions puppeteer a set of glitchy avatars rendered with Three.js. Each avatar emits a unique sound patch from Tone.js and reacts unpredictably to clicks and drags. The avatars occasionally “break” apart into pixel shards that float and recombine, creating a chaotic dance of sound and form.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <title>Chaotic Dance of Sound and Form</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body { margin: 0; overflow: hidden; background-color: #050505; color: #fff; font-family: monospace; } canvas { display: block; } #info { position: absolute; top: 10px; width: 100%; text-align: center; z-index: 100; display:block; text-shadow: 0 0 5px #00aaff; } #info.hidden { display: none; } </style> </head> <body> <div id="info">Click or drag the avatars</div> <script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.157.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // --- Tone.js --- // Since Tone.js is not an ES module on most CDNs, we'll load it the old-fashioned way // and attach it to the window. await new Promise(resolve => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js'; script.onload = resolve; document.head.appendChild(script); }); // --- GLOBAL VARIABLES --- let scene, camera, renderer, clock, controls; let raycaster, pointer; const avatars = []; let selectedAvatar = null; let audioInitialized = false; // --- SHADER DEFINITIONS --- // Shader for the intact, "glitchy" avatar const glitchyVertexShader = ` uniform float u_time; uniform float u_glitch_amount; varying vec2 vUv; // Simple noise function float noise(vec3 p) { return sin(p.x * 10.0 + u_time) * cos(p.y * 10.0 + u_time) * sin(p.z * 10.0 + u_time); } void main() { vUv = uv; vec3 displaced = position + normal * noise(position) * u_glitch_amount; gl_Position = projectionMatrix * modelViewMatrix * vec4(displaced, 1.0); } `; const glitchyFragmentShader = ` uniform float u_time; uniform vec3 u_color; varying vec2 vUv; void main() { float scanline = sin(vUv.y * 800.0 + u_time * 2.0) * 0.05; vec3 finalColor = u_color + vec3(scanline); float vignette = 1.0 - length(vUv - vec2(0.5)) * 1.2; gl_FragColor = vec4(finalColor * vignette, 1.0); } `; // Shader for the "shattering" particles const particleVertexShader = ` uniform float u_time; uniform float u_progress; // 0: fully shattered, 1: fully recombined uniform float u_duration; attribute vec3 initialVelocity; // Easing function: easeInOutQuad float easeInOut(float t) { return t < 0.5 ? 2.0 * t * t : -1.0 + (4.0 - 2.0 * t) * t; } void main() { float t = u_time / u_duration; // Position when fully shattered (parabolic arc) vec3 shatteredPos = position + initialVelocity * t - vec3(0.0, 2.0, 0.0) * t * t; // Mix between shattered and original position based on progress vec3 finalPos = mix(shatteredPos, position, easeInOut(u_progress)); vec4 mvPosition = modelViewMatrix * vec4(finalPos, 1.0); gl_PointSize = (3.0 / -mvPosition.z) * (1.0 - u_progress + 0.1); gl_Position = projectionMatrix * mvPosition; } `; const particleFragmentShader = ` uniform vec3 u_color; uniform float u_progress; void main() { float alpha = 1.0 - u_progress; if (distance(gl_PointCoord, vec2(0.5, 0.5)) > 0.45) discard; gl_FragColor = vec4(u_color * (alpha + 0.5), alpha); } `; // --- AVATAR CLASS --- class GlitchyAvatar { constructor(soundPatch, color, position) { this.sound = soundPatch; this.color = new THREE.Color(color); this.state = 'INTACT'; // INTACT, SHATTERING, RECOMBINING this.animation = { time: 0, duration: 0, progress: 0 }; // 1. The intact mesh const geometry = new THREE.IcosahedronGeometry(1.5, 3); this.material = new THREE.ShaderMaterial({ uniforms: { u_time: { value: 0 }, u_glitch_amount: { value: 0.0 }, u_color: { value: this.color } }, vertexShader: glitchyVertexShader, fragmentShader: glitchyFragmentShader }); this.mesh = new THREE.Mesh(geometry, this.material); // 2. The particle system for shattering const particleCount = geometry.attributes.position.count; const particleGeometry = new THREE.BufferGeometry(); particleGeometry.setAttribute('position', geometry.attributes.position.clone()); const velocities = new Float32Array(particleCount * 3); for (let i = 0; i < particleCount; i++) { const normal = new THREE.Vector3( geometry.attributes.normal.getX(i), geometry.attributes.normal.getY(i), geometry.attributes.normal.getZ(i) ); const speed = 2.0 + Math.random() * 4.0; normal.multiplyScalar(speed); velocities[i * 3] = normal.x; velocities[i * 3 + 1] = normal.y; velocities[i * 3 + 2] = normal.z; } particleGeometry.setAttribute('initialVelocity', new THREE.BufferAttribute(velocities, 3)); this.particleMaterial = new THREE.ShaderMaterial({ uniforms: { u_time: { value: 0 }, u_progress: { value: 0 }, u_duration: { value: 0}, u_color: { value: this.color } }, vertexShader: particleVertexShader, fragmentShader: particleFragmentShader, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending }); this.particles = new THREE.Points(particleGeometry, this.particleMaterial); // Group to hold both and manage position this.group = new THREE.Group(); this.group.add(this.mesh); this.group.add(this.particles); this.group.position.copy(position); this.particles.visible = false; // Add a reference back to this class instance from the Three.js object this.mesh.userData.avatar = this; } shatter() { if (this.state !== 'INTACT') return; // Play sound this.sound.triggerAttackRelease(Tone.Frequency(200 + Math.random() * 600).toNote(), '0.5'); this.state = 'SHATTERING'; this.mesh.visible = false; this.particles.visible = true; this.animation.time = 0; this.animation.duration = 2 + Math.random() * 2; // Lasts 2-4 seconds this.particleMaterial.uniforms.u_duration.value = this.animation.duration; } update(delta) { // Update shader time for constant glitch effect this.material.uniforms.u_time.value += delta; this.material.uniforms.u_glitch_amount.value = this.state === 'INTACT' ? 0.05 + (Math.sin(clock.getElapsedTime() * 5) * 0.05) : 0; if (this.state === 'INTACT') return; this.animation.time += delta; if (this.state === 'SHATTERING') { this.particleMaterial.uniforms.u_time.value = this.animation.time; this.particleMaterial.uniforms.u_progress.value = 0; if (this.animation.time >= this.animation.duration) { this.state = 'RECOMBINING'; this.animation.time = 0; this.animation.duration = 1.5 + Math.random() * 1.5; // Recombine over 1.5-3 seconds } } else if (this.state === 'RECOMBINING') { this.animation.progress = Math.min(this.animation.time / this.animation.duration, 1.0); this.particleMaterial.uniforms.u_progress.value = this.animation.progress; if (this.animation.progress >= 1.0) { this.state = 'INTACT'; this.mesh.visible = true; this.particles.visible = false; } } } } // --- MAIN FUNCTIONS --- function init() { // Scene scene = new THREE.Scene(); clock = new THREE.Clock(); // Camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 15; // Renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.body.appendChild(renderer.domElement); // Controls controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.minDistance = 5; controls.maxDistance = 50; // Lights scene.add(new THREE.AmbientLight(0xffffff, 0.3)); const dirLight = new THREE.DirectionalLight(0xffffff, 1.0); dirLight.position.set(5, 10, 7); scene.add(dirLight); // Raycasting raycaster = new THREE.Raycaster(); pointer = new THREE.Vector2(); // Create Audio Synths const synth1 = new Tone.FMSynth({ harmonicity: 3.01, modulationIndex: 14, envelope: { attack: 0.01, decay: 0.2, release: 0.5 }, modulationEnvelope: { attack: 0.01, decay: 0.3, release: 0.5 } }).toDestination(); const synth2 = new Tone.MembraneSynth({ pitchDecay: 0.08, octaves: 6, envelope: { attack: 0.001, decay: 0.5, sustain: 0.01, release: 0.4 } }).toDestination(); const synth3 = new Tone.AMSynth({ harmonicity: 1.5, envelope: { attack: 0.05, decay: 0.3, release: 1 }, modulationEnvelope: { attack: 0.1, decay: 0.2, release: 0.5 } }).toDestination(); const reverb = new Tone.Reverb(2).toDestination(); synth1.connect(reverb); synth2.connect(reverb); synth3.connect(reverb); // Create Avatars const avatarData = [ { sound: synth1, color: 0x00aaff, position: new THREE.Vector3(-5, 2, 0) }, { sound: synth2, color: 0xff4400, position: new THREE.Vector3(5, -2, -2) }, { sound: synth3, color: 0x00ff88, position: new THREE.Vector3(0, 0, 3) } ]; avatarData.forEach(data => { const avatar = new GlitchyAvatar(data.sound, data.color, data.position); avatars.push(avatar); scene.add(avatar.group); }); // Event Listeners window.addEventListener('resize', onWindowResize); window.addEventListener('pointerdown', onPointerDown); window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerup', onPointerUp); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function onPointerDown(event) { // First user interaction initializes audio context if (!audioInitialized) { Tone.start(); audioInitialized = true; document.getElementById('info').classList.add('hidden'); } updatePointer(event); const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh)); if (intersects.length > 0) { const avatar = intersects[0].object.userData.avatar; if (avatar) { controls.enabled = false; selectedAvatar = avatar; avatar.shatter(); // Trigger the effect on click } } } function onPointerMove(event) { if (selectedAvatar) { updatePointer(event); // Project pointer to a plane at the avatar's distance const plane = new THREE.Plane(new THREE.Vector3(0,0,1), -selectedAvatar.group.position.z); const intersection = new THREE.Vector3(); raycaster.ray.intersectPlane(plane, intersection); // Unpredictable drag motion const dragForce = intersection.sub(selectedAvatar.group.position).multiplyScalar(0.1); selectedAvatar.group.position.add(dragForce); // Occasionally trigger a smaller sound if (Math.random() > 0.98) { selectedAvatar.sound.triggerAttackRelease(Tone.Frequency(400 + Math.random() * 200).toNote(), '0.1'); } } } function onPointerUp() { if (selectedAvatar) { selectedAvatar = null; controls.enabled = true; } } function updatePointer(event) { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = - (event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); } function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); controls.update(); avatars.forEach(avatar => { avatar.update(delta); // add a slight bobbing motion avatar.group.position.y += Math.sin(clock.getElapsedTime() * 0.5 + avatar.group.position.x) * 0.005; }); renderer.render(scene, camera); } // --- RUN --- init(); animate(); </script> </body> </html> ```
Here's a complete HTML implementation that creates glitchy Three.js avatars with Tone.js sound interactions: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Glitchy Audio Avatars</title> <style> body { margin: 0; overflow: hidden; background-color: #000; } canvas { display: block; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script> <script> // Initialize Three.js scene const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Initialize Tone.js const synths = []; const reverb = new Tone.Reverb(3).toDestination(); reverb.decay = 2.5; reverb.wet.value = 0.4; // Avatar class class GlitchAvatar { constructor(x, y, z) { this.x = x; this.y = y; this.z = z; this.isBroken = false; this.shards = []; this.velocity = new THREE.Vector3(); // Create random geometry (mix between sphere and box) const geometrySeed = Math.random(); let geometry; if (geometrySeed < 0.3) { geometry = new THREE.SphereGeometry(0.5, 12, 12); } else if (geometrySeed < 0.6) { geometry = new THREE.BoxGeometry(0.8, 0.8, 0.8); } else { geometry = new THREE.IcosahedronGeometry(0.6, 0); } // Apply glitchy distortion to geometry const positions = geometry.attributes.position; for (let i = 0; i < positions.count; i++) { positions.setXYZ( i, positions.getX(i) + (Math.random() * 0.2 - 0.1), positions.getY(i) + (Math.random() * 0.2 - 0.1), positions.getZ(i) + (Math.random() * 0.2 - 0.1) ); } // Create material with random color and slight transparency const hue = Math.random(); this.material = new THREE.MeshStandardMaterial({ color: new THREE.Color().setHSL(hue, 0.7, 0.5), metalness: 0.2, roughness: 0.7, transparent: true, opacity: 0.9 }); // Create mesh this.mesh = new THREE.Mesh(geometry, this.material); this.mesh.position.set(x, y, z); scene.add(this.mesh); // Create synth for this avatar this.synth = new Tone.PolySynth(Tone.Synth).connect(reverb); this.synth.volume.value = -12; // Store synth parameters this.note = Math.floor(Math.random() * 24) + 48; // MIDI note this.detune = Math.random() * 100 - 50; this.envelope = { attack: Math.random() * 0.2 + 0.01, decay: Math.random() * 0.3, sustain: 0.4 + Math.random() * 0.3, release: Math.random() * 1 + 0.5 }; this.synth.set({ oscillator: { type: Math.random() < 0.5 ? "sine" : "sawtooth", detune: this.detune }, envelope: this.envelope }); } breakApart() { if (this.isBroken) return; this.isBroken = true; // Hide main mesh this.mesh.visible = false; // Get geometry data for shards const geometry = this.mesh.geometry; const positions = geometry.attributes.position; const colors = []; // Create color array if doesn't exist if (!geometry.attributes.color) { const color = this.mesh.material.color; for (let i = 0; i < positions.count; i++) { colors.push(color.r, color.g, color.b); } } else { for (let i = 0; i < positions.count; i++) { colors.push( geometry.attributes.color.getX(i), geometry.attributes.color.getY(i), geometry.attributes.color.getZ(i) ); } } // Create shards (individual points) const shardGeometry = new THREE.BufferGeometry(); shardGeometry.setAttribute('position', new THREE.Float32BufferAttribute(positions.array, 3)); shardGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); // Create points material const shardMaterial = new THREE.PointsMaterial({ size: 0.05, vertexColors: true, transparent: true, opacity: 0.9, sizeAttenuation: true }); // Create points system and add to scene this.points = new THREE.Points(shardGeometry, shardMaterial); this.points.position.copy(this.mesh.position); scene.add(this.points); // Create velocity data for each point this.pointVelocities = []; for (let i = 0; i < positions.count; i++) { this.pointVelocities.push(new THREE.Vector3( (Math.random() - 0.5) * 0.05, (Math.random() - 0.5) * 0.05, (Math.random() - 0.5) * 0.05 )); } // Play break sound this.playSound(); } reassemble() { if (!this.isBroken) return; this.isBroken = false; // Show main mesh this.mesh.visible = true; // Remove points system if (this.points) { scene.remove(this.points); this.points = null; } // Play reassemble sound this.playSound(); } playSound() { // Trigger sound with some randomness const now = Tone.now(); this.synth.triggerAttackRelease( Tone.Frequency(this.note, "midi").transpose(Math.random() * 7 - 3.5), Math.random() * 0.5 + 0.1, now, Math.random() * 0.3 ); } update() { if (this.isBroken) { // Animate broken shards const positions = this.points.geometry.attributes.position; for (let i = 0; i < positions.count; i++) { const vel = this.pointVelocities[i]; // Update position positions.setXYZ( i, positions.getX(i) + vel.x, positions.getY(i) + vel.y, positions.getZ(i) + vel.z ); // Apply some randomness to velocity vel.x += (Math.random() - 0.5) * 0.001; vel.y += (Math.random() - 0.5) * 0.001; vel.z += (Math.random() - 0.5) * 0.001; // Apply gentle pull back to center vel.x -= (positions.getX(i) - this.x) * 0.01; vel.y -= (positions.getY(i) - this.y) * 0.01; vel.z -= (positions.getZ(i) - this.z) * 0.01; } positions.needsUpdate = true; // Random chance to reassemble if (Math.random() < 0.005) { this.reassemble(); } } else { // Normal avatar movement this.velocity.x += (Math.random() - 0.5) * 0.001; this.velocity.y += (Math.random() - 0.5) * 0.001; this.velocity.z += (Math.random() - 0.5) * 0.001; // Apply bounds const bounds = 5; if (this.mesh.position.x < -bounds || this.mesh.position.x > bounds) this.velocity.x *= -1; if (this.mesh.position.y < -bounds || this.mesh.position.y > bounds) this.velocity.y *= -1; if (this.mesh.position.z < -bounds || this.mesh.position.z > bounds) this.velocity.z *= -1; // Move position this.mesh.position.add(this.velocity); // Random rotation this.mesh.rotation.x += 0.005; this.mesh.rotation.y += 0.008; // Random glitch effect if (Math.random() < 0.01) { const pos = this.mesh.geometry.attributes.position; for (let i = 0; i < pos.count; i++) { if (Math.random() < 0.1) { pos.setXYZ( i, pos.getX(i) + (Math.random() - 0.5) * 0.2, pos.getY(i) + (Math.random() - 0.5) * 0.2, pos.getZ(i) + (Math.random() - 0.5) * 0.2 ); } } pos.needsUpdate = true; } // Random chance to break apart if (Math.random() < 0.003) { this.breakApart(); } } } } // Create avatars const avatars = []; const avatarCount = 8; for (let i = 0; i < avatarCount; i++) { const x = Math.random() * 10 - 5; const y = Math.random() * 6 - 3; const z = Math.random() * 10 - 5; avatars.push(new GlitchAvatar(x, y, z)); } // Add lights const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); const pointLight = new THREE.PointLight(0xffaa00, 0.5, 50); pointLight.position.set(5, 5, 5); scene.add(pointLight); // Camera position camera.position.z = 10; // Raycaster for mouse interactions const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // Mouse move handler function onMouseMove(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; } // Click handler function onClick() { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects( avatars.map(avatar => avatar.isBroken ? avatar.points : avatar.mesh) ); if (intersects.length > 0) { const pointIndex = intersects[0].index; const object = intersects[0].object; // Find which avatar was clicked for (const avatar of avatars) { if (avatar.isBroken && avatar.points === object) { // Explode shards more when clicked if (pointIndex !== undefined) { const vel = avatar.pointVelocities[pointIndex]; const power = 0.2; vel.x += (Math.random() - 0.5) * power; vel.y += (Math.random() - 0.5) * power; vel.z += (Math.random() - 0.5) * power; } avatar.playSound(); break; } else if (!avatar.isBroken && avatar.mesh === object) { avatar.breakApart(); break; } } } } // Drag variables let isDragging = false; let draggedAvatar = null; let dragOffset = new THREE.Vector3(); function onMouseDown(event) { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects( avatars.map(avatar => avatar.isBroken ? avatar.points : avatar.mesh) ); if (intersects.length > 0) { const object = intersects[0].object; for (const avatar of avatars) { if (!avatar.isBroken && avatar.mesh === object) { isDragging = true; draggedAvatar = avatar; // Calculate offset between mouse and avatar position const vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject(camera); dragOffset = new THREE.Vector3().copy(avatar.mesh.position).sub(vector); break; } } } } function onMouseUp() { isDragging = false; draggedAvatar = null; } function onMouseMoveDrag(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; if (isDragging && draggedAvatar) { const vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject(camera); draggedAvatar.mesh.position.copy(vector.add(dragOffset)); // Add some chaotic movement when dragging draggedAvatar.velocity.set( (Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1 ); // Random chance to trigger sound while dragging if (Math.random() < 0.1) { draggedAvatar.playSound(); } } } // Register event listeners window.addEventListener('mousemove', onMouseMove, false); window.addEventListener('click', onClick, false); window.addEventListener('mousedown', onMouseDown, false); window.addEventListener('mouseup', onMouseUp, false); window.addEventListener('mousemove', onMouseMoveDrag, false); // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Animation loop function animate() { requestAnimationFrame(animate); // Update avatars for (const avatar of avatars) { avatar.update(); } renderer.render(scene, camera); } // Start audio context on first interaction document.body.addEventListener('click', () => { Tone.start(); document.body.removeEventListener('click', this); }, { once: true }); // Start animation animate(); </script> </body> </html> ``` This implementation creates: 1. Multiple glitchy avatars with Three.js that have: - Random geometric shapes (spheres, boxes, icosahedrons) - Jittery animations and random movement - "Broken" states where they explode into particle shards - Automatic reassembly after being broken 2. Tone.js audio features: - Each avatar has a unique synth sound - Sounds trigger when avatars break/reassemble or are clicked - Random note variations for chaotic effects - Reverb for atmosphere 3. Interactive elements: - Click to break avatars apart - Click broken avatars to scatter their shards - Drag avatars around the screen - Mouse tracking for interaction The avatars will autonomously glitch, move, break apart, and reform while emitting procedural sounds. User interactions amplify the chaotic behavior.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatar Chaos</title> <style> body { margin: 0; padding: 0; background: #000; overflow: hidden; font-family: 'Courier New', monospace; cursor: crosshair; } #container { position: relative; width: 100vw; height: 100vh; } #info { position: absolute; top: 10px; left: 10px; color: #00ff41; font-size: 12px; z-index: 100; text-shadow: 0 0 10px #00ff41; } .glitch { animation: glitch 0.3s infinite; } @keyframes glitch { 0% { transform: translate(0); } 20% { transform: translate(-2px, 2px); } 40% { transform: translate(-2px, -2px); } 60% { transform: translate(2px, 2px); } 80% { transform: translate(2px, -2px); } 100% { transform: translate(0); } } </style> </head> <body> <div id="container"> <div id="info"> GLITCH_AVATARS_v2.1<br> [CLICK/DRAG TO CORRUPT]<br> STATUS: CHAOTIC </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: false }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); document.getElementById('container').appendChild(renderer.domElement); // Audio setup let audioStarted = false; async function startAudio() { if (!audioStarted) { await Tone.start(); audioStarted = true; } } // Glitch Avatar Class class GlitchAvatar { constructor(x, y, z) { this.originalPosition = new THREE.Vector3(x, y, z); this.position = new THREE.Vector3(x, y, z); this.isDragging = false; this.isExploded = false; this.glitchIntensity = 0; this.shards = []; this.createMesh(); this.createSound(); this.setupBehavior(); } createMesh() { // Create glitchy avatar geometry const geometry = new THREE.ConeGeometry(0.5, 1.5, 6); const material = new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(Math.random(), 0.8, 0.6), wireframe: Math.random() > 0.5, transparent: true, opacity: 0.8 }); this.mesh = new THREE.Mesh(geometry, material); this.mesh.position.copy(this.position); this.mesh.userData = this; // Add glitch effect this.addGlitchEffect(); scene.add(this.mesh); } addGlitchEffect() { // Random vertex displacement for glitch effect const positions = this.mesh.geometry.attributes.position; this.originalPositions = positions.array.slice(); setInterval(() => { if (this.glitchIntensity > 0) { for (let i = 0; i < positions.count; i++) { const i3 = i * 3; positions.array[i3] = this.originalPositions[i3] + (Math.random() - 0.5) * this.glitchIntensity; positions.array[i3 + 1] = this.originalPositions[i3 + 1] + (Math.random() - 0.5) * this.glitchIntensity; positions.array[i3 + 2] = this.originalPositions[i3 + 2] + (Math.random() - 0.5) * this.glitchIntensity; } positions.needsUpdate = true; } }, 100); } createSound() { // Create unique sound patch const sounds = [ () => new Tone.FMSynth({ harmonicity: Math.random() * 5, modulationIndex: Math.random() * 100, envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 0.8 } }), () => new Tone.AMSynth({ harmonicity: Math.random() * 3, envelope: { attack: 0.1, decay: 0.3, sustain: 0.4, release: 0.9 } }), () => new Tone.NoiseSynth({ noise: { type: ['white', 'brown', 'pink'][Math.floor(Math.random() * 3)] }, envelope: { attack: 0.005, decay: 0.1, sustain: 0.0 } }), () => new Tone.MetalSynth({ frequency: 200 + Math.random() * 400, envelope: { attack: 0.001, decay: 0.1, release: 0.8 }, harmonicity: 5.1, modulationIndex: 32 }) ]; this.synth = sounds[Math.floor(Math.random() * sounds.length)](); // Random effects chain this.effects = [ new Tone.Distortion(Math.random()), new Tone.BitCrusher(Math.floor(Math.random() * 8) + 1), new Tone.FrequencyShifter(Math.random() * 100 - 50), new Tone.AutoFilter(Math.random() * 10 + 0.5).start() ]; // Chain effects randomly let chain = this.synth; for (let i = 0; i < Math.floor(Math.random() * 3) + 1; i++) { const effect = this.effects[Math.floor(Math.random() * this.effects.length)]; chain = chain.connect(effect); } chain.toDestination(); this.baseFreq = 100 + Math.random() * 500; } setupBehavior() { // Random autonomous behavior setInterval(() => { if (!this.isDragging && Math.random() > 0.95) { this.triggerGlitch(); } if (!this.isDragging && Math.random() > 0.98) { this.explode(); } // Autonomous movement if (!this.isDragging && !this.isExploded) { this.position.x += (Math.random() - 0.5) * 0.02; this.position.y += (Math.random() - 0.5) * 0.02; this.mesh.position.lerp(this.position, 0.1); } }, 100); } triggerGlitch() { this.glitchIntensity = 0.5 + Math.random() * 0.5; // Visual glitch this.mesh.material.color.setHSL(Math.random(), 0.8, 0.6); this.mesh.rotation.x += Math.random() * 0.5; this.mesh.rotation.z += Math.random() * 0.5; // Audio glitch if (audioStarted) { const freq = this.baseFreq + (Math.random() - 0.5) * 200; const duration = 0.1 + Math.random() * 0.3; if (this.synth.triggerAttackRelease) { this.synth.triggerAttackRelease(freq, duration); } else { this.synth.triggerAttackRelease(duration); } } // Decay glitch setTimeout(() => { this.glitchIntensity *= 0.8; }, 200); } explode() { if (this.isExploded) return; this.isExploded = true; scene.remove(this.mesh); // Create pixel shards for (let i = 0; i < 20; i++) { const shard = this.createShard(); this.shards.push(shard); scene.add(shard.mesh); } // Audio explosion if (audioStarted) { for (let i = 0; i < 5; i++) { setTimeout(() => { const freq = this.baseFreq + (Math.random() - 0.5) * 400; if (this.synth.triggerAttackRelease) { this.synth.triggerAttackRelease(freq, 0.05); } else { this.synth.triggerAttackRelease(0.05); } }, i * 50); } } // Recombine after delay setTimeout(() => { this.recombine(); }, 3000 + Math.random() * 2000); } createShard() { const geometry = new THREE.BoxGeometry(0.05, 0.05, 0.05); const material = new THREE.MeshBasicMaterial({ color: this.mesh.material.color.clone(), transparent: true, opacity: 0.8 }); const mesh = new THREE.Mesh(geometry, material); mesh.position.copy(this.mesh.position); const velocity = new THREE.Vector3( (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2 ); return { mesh, velocity }; } recombine() { // Animate shards back const duration = 1000; const startTime = Date.now(); const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); this.shards.forEach(shard => { shard.mesh.position.lerp(this.originalPosition, progress * 0.02); shard.mesh.material.opacity = 1 - progress; }); if (progress < 1) { requestAnimationFrame(animate); } else { // Remove shards and restore avatar this.shards.forEach(shard => scene.remove(shard.mesh)); this.shards = []; this.isExploded = false; this.position.copy(this.originalPosition); scene.add(this.mesh); this.mesh.position.copy(this.position); this.triggerGlitch(); } }; animate(); } onInteraction(type, position) { if (type === 'click') { this.triggerGlitch(); if (Math.random() > 0.7) { this.explode(); } } else if (type === 'drag') { this.isDragging = true; this.position.copy(position); this.glitchIntensity = 0.3; this.triggerGlitch(); } } onDragEnd() { this.isDragging = false; this.glitchIntensity = 0.1; } } // Create avatars const avatars = []; for (let i = 0; i < 8; i++) { const x = (Math.random() - 0.5) * 8; const y = (Math.random() - 0.5) * 4; const z = (Math.random() - 0.5) * 4; avatars.push(new GlitchAvatar(x, y, z)); } // Interaction handling const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); let selectedAvatar = null; function onMouseMove(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; if (selectedAvatar) { raycaster.setFromCamera(mouse, camera); const distance = 5; const position = raycaster.ray.origin.clone().add(raycaster.ray.direction.multiplyScalar(distance)); selectedAvatar.onInteraction('drag', position); } } function onMouseDown(event) { startAudio(); mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children.filter(obj => obj.userData instanceof GlitchAvatar)); if (intersects.length > 0) { selectedAvatar = intersects[0].object.userData; selectedAvatar.onInteraction('click', intersects[0].point); } } function onMouseUp() { if (selectedAvatar) { selectedAvatar.onDragEnd(); selectedAvatar = null; } } renderer.domElement.addEventListener('mousemove', onMouseMove); renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener('mouseup', onMouseUp); // Camera setup camera.position.z = 8; camera.position.y = 2; // Animation loop function animate() { requestAnimationFrame(animate); // Camera drift camera.position.x += Math.sin(Date.now() * 0.001) * 0.001; camera.position.y += Math.cos(Date.now() * 0.0007) * 0.001; // Update avatars avatars.forEach(avatar => { if (avatar.mesh.parent && !avatar.isDragging) { avatar.mesh.rotation.y += 0.01; avatar.mesh.rotation.x += 0.005; } }); renderer.render(scene, camera); } // Handle resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Global glitch effect setInterval(() => { if (Math.random() > 0.95) { document.getElementById('info').classList.toggle('glitch'); setTimeout(() => { document.getElementById('info').classList.remove('glitch'); }, 300); } }, 1000); animate(); </script> </body> </html> ``` This creates a chaotic interactive experience with glitchy avatars that: - **Unique Sound Patches**: Each avatar generates different synthesized sounds using Tone.js (FM, AM, Noise, Metal synths) with random effects chains - **Unpredictable Reactions**: Clicking triggers glitches and occasional explosions, dragging causes continuous glitch effects - **Pixel Shard System**: Avatars randomly "break apart" into floating pixel shards that recombine after a few seconds - **Glitch Effects**: Real-time vertex displacement, color shifting, and visual corruption - **Autonomous Behavior**: Avatars move around randomly and trigger their own glitch events - **Audio-Visual Sync**: Sounds trigger with visual glitches for a cohesive chaotic experience The interface deliberately feels unstable and unpredictable, creating a digital ecosystem where sound and form dance in constant flux.
Below is a compact, self-contained demo that combines Three.js and Tone.js to create: • Three “glitch avatars” (colored cubes). • Each avatar owns a unique Tone.js synth patch. • Click an avatar: it plays a note, gains a random spin and may “explode” into voxel-shards that drift, then magnetically recombine. • Drag over an avatar: pitch shifts unpredictably. • Avatars may also self-destruct at random intervals. Feel free to expand the geometry, textures, or synth settings—the structure is in place for you to swap in more complex models and richer sounds. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Glitchy Avatars – Three.js + Tone.js</title> <style> html,body{margin:0;height:100%;overflow:hidden;background:#000;color:#fff;font-family:sans-serif;} #info{position:fixed;top:10px;left:10px;font-size:14px;opacity:.8;} a{color:#0af} </style> </head> <body> <div id="info"> CLICK / DRAG an avatar. <br> Audio will start the first time you interact. </div> <script type="module"> import * as THREE from 'https://unpkg.com/three@0.161.0/build/three.module.js'; import { OrbitControls } from 'https://unpkg.com/three@0.161.0/examples/jsm/controls/OrbitControls.js'; import * as Tone from 'https://cdn.skypack.dev/tone@14.8.46'; let scene, camera, renderer, controls, raycaster; let pointer = new THREE.Vector2(); const avatars = []; init(); animate(0); //----- SETUP ----- function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x000000); camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100); camera.position.set(0, 2.5, 7); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(devicePixelRatio); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement); controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.enablePan = false; raycaster = new THREE.Raycaster(); // lights const ambient = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambient); const dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(5, 10, 7); scene.add(dir); // floor grid const grid = new THREE.GridHelper(20, 40, 0x444444, 0x222222); scene.add(grid); // create avatars const colors = [0x00ffff, 0xff00ff, 0xffff00]; const patches = [ new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 10 }).toDestination(), new Tone.FMSynth({ modulationIndex: 12, harmonicity: 6 }).toDestination(), new Tone.Synth({ oscillator: { type: "sawtooth" }, envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 0.5 } }).toDestination() ]; for (let i = 0; i < 3; i++) { const avatar = new GlitchAvatar({ color: colors[i], synth: patches[i], position: new THREE.Vector3((i - 1) * 3, 0.5, 0) }); avatars.push(avatar); scene.add(avatar.group); } window.addEventListener('resize', onResize); window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerdown', onPointerDown); } //----- AVATAR CLASS ----- class GlitchAvatar { constructor({ color, synth, position }) { this.synth = synth; this.group = new THREE.Group(); this.group.position.copy(position); // main mesh const geo = new THREE.BoxGeometry(1, 1, 1); const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.4, metalness: 0.1 }); this.mesh = new THREE.Mesh(geo, mat); this.group.add(this.mesh); // shard container this.shards = []; this.broken = false; this.breakTimer = 0; this.reformTimer = 0; // schedule random self-breaks this.nextAutoBreak = performance.now() + THREE.MathUtils.randInt(4000, 9000); } triggerNote(freq = 220) { this.synth.triggerAttackRelease(freq, "8n", undefined, 0.8); } breakApart() { if (this.broken) return; this.broken = true; this.mesh.visible = false; const shardSize = 0.2; const half = 0.5 - shardSize / 2; for (let x = -half; x <= half; x += shardSize) { for (let y = -half; y <= half; y += shardSize) { for (let z = -half; z <= half; z += shardSize) { const geo = new THREE.BoxGeometry(shardSize, shardSize, shardSize); const mat = new THREE.MeshStandardMaterial({ color: this.mesh.material.color, roughness: 0.5 }); const shard = new THREE.Mesh(geo, mat); shard.position.set(x, y, z); shard.original = shard.position.clone(); shard.velocity = new THREE.Vector3( THREE.MathUtils.randFloatSpread(2), THREE.MathUtils.randFloat(0.5, 2.5), THREE.MathUtils.randFloatSpread(2) ); this.group.add(shard); this.shards.push(shard); } } } this.breakTimer = 1500; // ms shards fly out this.reformTimer = 1500; // ms they glide back } update(dt) { // autodestruct if (!this.broken && performance.now() > this.nextAutoBreak) { this.breakApart(); this.nextAutoBreak = performance.now() + THREE.MathUtils.randInt(6000, 12000); } // rotate whole avatar a bit for life this.group.rotation.y += dt * 0.2; if (this.broken) { if (this.breakTimer > 0) { // flying apart this.shards.forEach(s => { s.position.addScaledVector(s.velocity, dt); s.velocity.multiplyScalar(0.98); // drag }); this.breakTimer -= dt * 1000; if (this.breakTimer <= 0) { // start reform this.shards.forEach(s => s.velocity.set(0,0,0)); } } else if (this.reformTimer > 0) { // coming back this.shards.forEach(s => { s.position.lerp(s.original, 0.04); }); this.reformTimer -= dt * 1000; if (this.reformTimer <= 0) { // done this.shards.forEach(s => this.group.remove(s)); this.shards.length = 0; this.mesh.visible = true; this.broken = false; } } } } } //----- INTERACTION HANDLERS ----- function onPointerMove(e) { updatePointer(e); if (e.buttons) { // dragging intersectAvatars(i => { const pitch = THREE.MathUtils.mapLinear(pointer.y, -1, 1, 120, 40); // invert axis i.object.__avatar.triggerNote(pitch); }); } } function onPointerDown(e) { updatePointer(e); Tone.start(); // first human gesture starts Web Audio intersectAvatars(i => { const avatar = i.object.__avatar; avatar.triggerNote(THREE.MathUtils.randInt(60, 800)); avatar.group.rotation.x += Math.random() * 2; avatar.group.rotation.y += Math.random() * 2; if (Math.random() < 0.7) avatar.breakApart(); }); } function updatePointer(e) { const rect = renderer.domElement.getBoundingClientRect(); pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; } function intersectAvatars(callback) { raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh), false); if (intersects.length) callback(intersects[0]); } //----- ANIMATION LOOP ----- let prev = performance.now(); function animate() { requestAnimationFrame(animate); const now = performance.now(); const dt = (now - prev) / 1000; prev = now; avatars.forEach(a => a.update(dt)); controls.update(); renderer.render(scene, camera); } function onResize() { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); } </script> </body> </html> ``` How to extend: 1. Replace the cube geometry with GLTF models or custom shaders for richer “glitch” visuals. 2. Experiment with Tone.js effects (BitCrusher, FeedbackDelay, Reverb) for deeper sonic chaos. 3. Add post-processing (e.g., GlitchPass from Three.js examples) for screen-wide distortion.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatars</title> <style> body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; background-color: #000; } canvas { display: block; } #instructions { position: absolute; top: 20px; color: white; font-family: monospace; text-align: center; width: 100%; pointer-events: none; /* Allow clicks to pass through */ font-size: 1.2em; text-shadow: 0 0 5px rgba(255, 255, 255, 0.5); background-color: rgba(0, 0, 0, 0.5); padding: 10px; box-sizing: border-box; opacity: 1; transition: opacity 1s ease-in-out; z-index: 10; } #instructions.fade-out { opacity: 0; pointer-events: none; } </style> </head> <body> <div id="instructions">Click an avatar to make it glitch! Drag to move.</div> <!-- Three.js CDN --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.min.js"></script> <!-- Tone.js CDN --> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js"></script> <script> // --- Three.js Setup --- let scene, camera, renderer, raycaster, mouse; let avatars = []; let intersected = null; // The currently hovered avatar mesh let isDragging = false; let selectedAvatar = null; // The avatar currently being dragged let offset = new THREE.Vector3(); // Offset for dragging calculation // Audio Context state - Tone.js requires a user interaction to start let audioStarted = false; // --- Avatar States and Class --- const AVATAR_STATES = { NORMAL: 'NORMAL', CLICKED: 'CLICKED', // Brief visual/sound feedback on click GLITCHING: 'GLITCHING', BROKEN: 'BROKEN', // Geometry is distorted (pixel shards floating out) RECOMBINING: 'RECOMBINING' // Geometry is returning to normal }; class Avatar { constructor(id) { this.id = id; this.originalPosition = new THREE.Vector3(); this.originalRotation = new THREE.Euler(0, 0, 0); // Store initial rotation this.originalScale = new THREE.Vector3(1, 1, 1); this.state = AVATAR_STATES.NORMAL; this.stateStartTime = 0; this.stateDuration = 0; // How long to remain in the current state // Geometries and Material this.normalGeometry = new THREE.BoxGeometry(1, 1, 1); // The default shape this.material = new THREE.MeshPhongMaterial({ color: new THREE.Color(Math.random(), Math.random(), Math.random()).getHex(), shininess: 50, emissive: 0x000000, // For hover/glitch glow specular: 0x777777 }); this.mesh = new THREE.Mesh(this.normalGeometry, this.material); scene.add(this.mesh); // For vertex manipulation in broken state this.originalVertices = new Float32Array(this.normalGeometry.attributes.position.array); // Store original vertex data this.currentVerticesData = new Float32Array(this.originalVertices.length); // Mutable data buffer for BufferGeometry this.velocityVertices = []; // Stores velocity for each "shard" when broken this.centerOfGravity = new THREE.Vector3(); // Center point for explosion calculation this.normalGeometry.computeBoundingSphere(); // Compute sphere for the BoxGeometry this.centerOfGravity.copy(this.normalGeometry.boundingSphere.center); // Tone.js Synth this.synth = this.createSynth(); // 3D panner to give spatial audio effect this.panner = new Tone.Panner3D().toDestination(); this.synth.connect(this.panner); } // Creates a unique Tone.js synthesizer patch createSynth() { const synthType = Math.floor(Math.random() * 3); let synth; switch (synthType) { case 0: // AM Synth (amplitude modulation) synth = new Tone.AMSynth({ harmonicity: Tone.Midi.mtof(Math.random() * 12 + 60), // Random harmonicity detune: Math.random() * 200 - 100, // Random detune oscillator: { type: "sine" }, envelope: { attack: 0.005, decay: 0.1, sustain: 0.05, release: 0.2 }, modulation: { type: "square" }, modulationEnvelope: { attack: 0.01, decay: 0.05, sustain: 0.2, release: 0.1 } }); break; case 1: // FM Synth (frequency modulation) synth = new Tone.FMSynth({ harmonicity: Math.random() * 2 + 0.5, modulationIndex: Math.random() * 10 + 1, detune: Math.random() * 100 - 50, oscillator: { type: "triangle" }, envelope: { attack: 0.001, decay: 0.2, sustain: 0.05, release: 0.5 }, modulation: { type: Math.random() > 0.5 ? "sine" : "sawtooth" }, modulationEnvelope: { attack: 0.005, decay: 0.1, sustain: 0.1, release: 0.2 } }); break; case 2: // Noise Synth with AutoFilter (noisy, sweeping sound) synth = new Tone.NoiseSynth({ noise: { type: "pink" }, envelope: { attack: 0.005, decay: 0.05, sustain: 0.1, release: 0.1 } }); const filter = new Tone.AutoFilter({ frequency: Math.random() * 5 + 0.1, depth: Math.random() * 1, baseFrequency: 200, octaves: 4 }).start(); synth.connect(filter); filter.toDestination(); // Connect the filter to the destination break; } return synth; } // Sets the initial position and updates the 3D panner setPosition(x, y, z) { this.mesh.position.set(x, y, z); this.originalPosition.copy(this.mesh.position); this.panner.positionX.value = x; this.panner.positionY.value = y; this.panner.positionZ.value = z; } // Ensures Tone.js AudioContext is started on first user interaction triggerActivation() { if (!audioStarted) { Tone.start(); audioStarted = true; console.log("AudioContext started."); // Fade out instructions after first interaction document.getElementById('instructions').classList.add('fade-out'); } } // Handles avatar click interaction onClick() { this.triggerActivation(); if (this.state === AVATAR_STATES.NORMAL) { this.setState(AVATAR_STATES.CLICKED, 0.15); // Brief visual feedback // Play sound at Tone.now() (current time) with random volume this.synth.triggerAttackRelease("C4", "8n", 0, Math.random() * 0.5 + 0.5); } else if (this.state === AVATAR_STATES.GLITCHING) { // Clicking a glitching avatar makes it break this.breakApart(); } else { // Clicking a broken/recombining avatar makes it recombine faster this.recombine(); } } // Sets the avatar's state and state timer setState(newState, duration = 0) { this.state = newState; this.stateStartTime = performance.now(); this.stateDuration = duration * 1000; // Convert duration from seconds to milliseconds } // Initiates the glitching visual and sound effects glitch() { if (this.state !== AVATAR_STATES.GLITCHING) { this.setState(AVATAR_STATES.GLITCHING, 3 + Math.random() * 5); // Glitch for 3-8 seconds this.synth.triggerAttackRelease( Math.random() > 0.5 ? "A2" : "D#2", // Random low notes "2n", 0, 0.7 ); } this.glitchIntensity = 0.5 + Math.random() * 0.5; // Random intensity for jitter } // Causes the avatar to break apart into "pixel shards" breakApart() { if (this.state === AVATAR_STATES.BROKEN) return; this.setState(AVATAR_STATES.BROKEN, 3 + Math.random() * 5); // Broken for 3-8 seconds // Play a sharp, breaking sound this.synth.triggerAttackRelease( Math.random() > 0.5 ? "G#1" : "F1", "4n", 0, 1.0 ); // Copy original vertices to the mutable data array this.currentVerticesData.set(this.originalVertices); this.velocityVertices = []; // Clear previous velocities // Calculate a random velocity for each vertex based on its position relative to the center for (let i = 0; i < this.originalVertices.length; i += 3) { const vertexPos = new THREE.Vector3( this.originalVertices[i], this.originalVertices[i + 1], this.originalVertices[i + 2] ); const dir = vertexPos.sub(this.centerOfGravity).normalize(); // Direction from center const speed = 0.05 + Math.random() * 0.1; // Base explosion speed this.velocityVertices.push( dir.x * speed + (Math.random() - 0.5) * 0.02, // Add some randomness for chaotic motion dir.y * speed + (Math.random() - 0.5) * 0.02, dir.z * speed + (Math.random() - 0.5) * 0.02 ); } // If mesh is currently using basic geometry, dispose it and switch to BufferGeometry if (this.mesh.geometry !== this.normalGeometry) { this.mesh.geometry.dispose(); } const brokenGeo = new THREE.BufferGeometry(); brokenGeo.setAttribute('position', new THREE.BufferAttribute(this.currentVerticesData, 3)); this.mesh.geometry = brokenGeo; // Assign the new BufferGeometry // Keep mesh's global position/rotation/scale fixed while vertices move internally this.mesh.scale.copy(this.originalScale); this.mesh.rotation.copy(this.originalRotation); this.mesh.position.copy(this.originalPosition); } // Initiates the recombination process for fractured avatars recombine() { if (this.state === AVATAR_STATES.RECOMBINING || this.state === AVATAR_STATES.NORMAL) return; this.setState(AVATAR_STATES.RECOMBINING, 2 + Math.random() * 3); // Recombine over 2-5 seconds // Play a more evolving, soothing sound this.synth.triggerAttackRelease( Math.random() > 0.5 ? "C2" : "F#2", "1n", 0, 0.8 ); } // Main update loop for avatar behavior update(deltaTime) { const now = performance.now(); const elapsedSinceStateChange = (now - this.stateStartTime); const progress = this.stateDuration > 0 ? elapsedSinceStateChange / this.stateDuration : 0; // Handle state transitions once duration is over if (this.stateDuration > 0 && progress >= 1) { if (this.state === AVATAR_STATES.CLICKED) { this.glitch(); // After brief click feedback, start glitching } else if (this.state === AVATAR_STATES.GLITCHING) { if (Math.random() < 0.2) { // 20% chance to break after glitching this.breakApart(); } else { this.setState(AVATAR_STATES.NORMAL); // Otherwise, return to normal } } else if (this.state === AVATAR_STATES.BROKEN) { this.recombine(); // After being broken for some time, start recombining } else if (this.state === AVATAR_STATES.RECOMBINING) { this.setState(AVATAR_STATES.NORMAL); // Once recombined, return to normal } } // Apply state-specific visual and audial updates switch (this.state) { case AVATAR_STATES.NORMAL: // Restore original geometry if it was a BufferGeometry from broken state if (this.mesh.geometry !== this.normalGeometry) { this.mesh.geometry.dispose(); // Dispose the temporary BufferGeometry this.mesh.geometry = this.normalGeometry; // Revert to original BoxGeometry } this.mesh.visible = true; // Ensure avatar is visible this.mesh.material.emissive.setHex(0x000000); // No emissive glow this.mesh.scale.copy(this.originalScale); // Restore scale this.mesh.rotation.x = this.originalRotation.x; this.mesh.rotation.z = this.originalRotation.z; // Restore X and Z rotation this.mesh.rotation.y += 0.005; // Gentle constant rotation this.mesh.position.y = this.originalPosition.y + Math.sin(now * 0.001 + this.id) * 0.1; // Gentle bobbing motion // Restore synth volume (if it was modulated during broken/recombining) if (this.synth.volume) this.synth.volume.value = 0; break; case AVATAR_STATES.CLICKED: // Brief scale pulse for click feedback const clickScale = 1 + Math.sin(progress * Math.PI) * 0.2; this.mesh.scale.set(clickScale, clickScale, clickScale); this.mesh.material.emissive.setHex(0xFFFFFF); // Flash white this.mesh.material.needsUpdate = true; break; case AVATAR_STATES.GLITCHING: this.mesh.scale.copy(this.originalScale); // Ensure original scale during glitch // Random visual flashes if (Math.random() < 0.1) { this.mesh.material.emissive.setHex(0xFF00FF); // Magenta flash this.mesh.material.needsUpdate = true; } else { this.mesh.material.emissive.setHex(0x000000); } // Jitter position and rotation this.mesh.position.x = this.originalPosition.x + (Math.random() - 0.5) * 0.1 * this.glitchIntensity; this.mesh.position.y = this.originalPosition.y + (Math.random() - 0.5) * 0.1 * this.glitchIntensity; this.mesh.position.z = this.originalPosition.z + (Math.random() - 0.5) * 0.1 * this.glitchIntensity; this.mesh.rotation.x = this.originalRotation.x + (Math.random() - 0.5) * 0.5 * this.glitchIntensity; this.mesh.rotation.y = this.originalRotation.y + (Math.random() - 0.5) * 0.5 * this.glitchIntensity; this.mesh.rotation.z = this.originalRotation.z + (Math.random() - 0.5) * 0.5 * this.glitchIntensity; // Modulate synth properties for glitchy sound if (this.synth.harmonicity) this.synth.harmonicity.value = 0.5 + Math.random() * 3; if (this.synth.detune) this.synth.detune.value = Math.random() * 2000 - 1000; if (this.synth.volume) this.synth.volume.value = 0; break; case AVATAR_STATES.BROKEN: // Animate vertices outwards (explosion effect) for (let i = 0; i < this.currentVerticesData.length; i += 3) { this.currentVerticesData[i] += this.velocityVertices[i] * deltaTime * 10; this.currentVerticesData[i + 1] += this.velocityVertices[i + 1] * deltaTime * 10; this.currentVerticesData[i + 2] += this.velocityVertices[i + 2] * deltaTime * 10; } this.mesh.geometry.attributes.position.needsUpdate = true; // Flag Three.js to use updated vertex data // Fade out synth volume if (this.synth.volume) { this.synth.volume.value = Math.max(-40, Tone.Math.linToLog(1 - progress) -10); } break; case AVATAR_STATES.RECOMBINING: // Animate vertices back to original positions (implosion effect) const recombineFactor = Math.sin(progress * Math.PI / 2); // Easing curve for smooth motion for (let i = 0; i < this.currentVerticesData.length; i += 3) { this.currentVerticesData[i] = THREE.MathUtils.lerp( this.currentVerticesData[i], this.originalVertices[i], recombineFactor * 0.15 ); this.currentVerticesData[i + 1] = THREE.MathUtils.lerp( this.currentVerticesData[i + 1], this.originalVertices[i + 1], recombineFactor * 0.15 ); this.currentVerticesData[i + 2] = THREE.MathUtils.lerp( this.currentVerticesData[i + 2], this.originalVertices[i + 2], recombineFactor * 0.15 ); } this.mesh.geometry.attributes.position.needsUpdate = true; // Fade in synth volume if (this.synth.volume) { this.synth.volume.value = Math.min(0, Tone.Math.linToLog(progress) - 10); } break; } // Update panner position in Tone.js to match the avatar's global position. // Note: when broken/recombining, the mesh's global position is fixed, but internal vertices move. // The panner should follow the original static position for consistency during these states. if (this.state === AVATAR_STATES.NORMAL || this.state === AVATAR_STATES.CLICKED || this.state === AVATAR_STATES.GLITCHING) { this.panner.positionX.value = this.mesh.position.x; this.panner.positionY.value = this.mesh.position.y; this.panner.positionZ.value = this.mesh.position.z; } } } // --- Three.js Initialization --- function init() { // Scene: where all objects, cameras, and lights live scene = new THREE.Scene(); scene.background = new THREE.Color(0x050510); // Dark blue background scene.fog = new THREE.Fog(0x050510, 5, 20); // Fog to create depth effect // Camera: defines what we see camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100); camera.position.set(0, 2, 5); // Position the camera camera.lookAt(0, 0, 0); // Point camera at the origin // Renderer: renders the scene renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); // Maintain quality on high-DPI screens document.body.appendChild(renderer.domElement); // Add canvas to HTML // Lights: illuminate the scene const ambientLight = new THREE.AmbientLight(0x404040, 2); // Soft white ambient light scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // Directional light for shadows/highlights directionalLight.position.set(5, 10, 7.5); directionalLight.castShadow = true; scene.add(directionalLight); // Raycaster for detecting mouse interactions with 3D objects raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); // Stores normalized mouse coordinates (-1 to +1) // Create Avatars and position them in a circle const numAvatars = 5; const radius = 3; for (let i = 0; i < numAvatars; i++) { const avatar = new Avatar(i); const angle = (i / numAvatars) * Math.PI * 2; // Distribute evenly in a circle avatar.setPosition( Math.cos(angle) * radius, 0, // All avatars on the ground plane Math.sin(angle) * radius ); avatars.push(avatar); } // Event Listeners for user input renderer.domElement.addEventListener('pointerdown', onPointerDown); renderer.domElement.addEventListener('pointermove', onPointerMove); renderer.domElement.addEventListener('pointerup', onPointerUp); window.addEventListener('resize', onWindowResize); // Handle window resizing animate(); // Start the animation loop } // --- Animation Loop --- let lastTime = 0; function animate(time) { requestAnimationFrame(animate); // Request next frame const deltaTime = (time - lastTime) / 1000; // Calculate time elapsed since last frame (in seconds) lastTime = time; avatars.forEach(avatar => avatar.update(deltaTime)); // Update each avatar's state and animation // Update raycaster for hover effects only if not currently dragging an object if (!isDragging) { raycaster.setFromCamera(mouse, camera); // Set raycaster origin and direction from camera and mouse const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh)); // Find intersections with avatar meshes if (intersects.length > 0) { // If a new object is intersected, clear previous highlight and set new one if (intersected != intersects[0].object) { if (intersected && intersected.material.emissive) intersected.material.emissive.setHex(0x000000); intersected = intersects[0].object; if (intersected.material.emissive) intersected.material.emissive.setHex(0x333333); // Subtle hover glow } } else { // No object intersected, clear any existing highlight if (intersected && intersected.material.emissive) { intersected.material.emissive.setHex(0x000000); } intersected = null; } } renderer.render(scene, camera); // Render the scene } // --- Event Handlers --- function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function onPointerDown(event) { // Convert mouse coordinates to normalized device coordinates (-1 to +1) mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; let targetMesh = null; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh)); if (intersects.length > 0) { targetMesh = intersects[0].object; selectedAvatar = avatars.find(a => a.mesh === targetMesh); if (selectedAvatar) { isDragging = true; selectedAvatar.onClick(); // Trigger avatar's click behavior // Calculate the offset between the intersection point and the object's origin // This prevents the object from "jumping" to the mouse position when dragged const intersectionPoint = intersects[0].point; offset.copy(selectedAvatar.mesh.position).sub(intersectionPoint); // Clear hover highlight if an object is selected to drag if(intersected && intersected.material.emissive) intersected.material.emissive.setHex(0x000000); intersected = null; } } else { selectedAvatar = null; // Clicked on empty space } } function onPointerMove(event) { if (!isDragging || !selectedAvatar) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); // Create a plane at the object's current depth, parallel to the camera's view const movePlane = new THREE.Plane().setFromNormalAndCoplanarPoint( camera.getWorldDirection(new THREE.Vector3()), selectedAvatar.mesh.position ); const intersectPoint = new THREE.Vector3(); // Find the intersection point of the ray with this plane raycaster.ray.intersectPlane(movePlane, intersectPoint); if (intersectPoint) { // Apply the calculated offset to get the new position const newPosition = intersectPoint.add(offset); selectedAvatar.mesh.position.copy(newPosition); selectedAvatar.originalPosition.copy(newPosition); // Update original position for later reference } } function onPointerUp() { isDragging = false; selectedAvatar = null; } // Start the application init(); </script> </body> </html> ```
```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Glitch Avatars</title> <style> body { margin: 0; overflow: hidden; background-color: #000; } canvas { display: block; } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.css"> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script> <script> (function() { const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); const avatars = []; const numAvatars = 5; // Adjustable number of avatars const shards = []; const maxShardsPerAvatar = 30; // Tone.js setup const synth = new Tone.Synth().toDestination(); const reverb = new Tone.Reverb(0.5).toDestination(); function getRandomNote() { const notes = ["C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5", "D5"]; return notes[Math.floor(Math.random() * notes.length)]; } function playSound() { const note = getRandomNote(); const duration = "2n"; const velocity = 0.5 + Math.random() * 0.5; synth.triggerAttackRelease(note, duration, velocity); reverb.process(synth); } class Avatar { constructor(index) { this.index = index; this.geometry = new THREE.SphereGeometry( 1, 32, 32 ); const material = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ); this.mesh = new THREE.Mesh( this.geometry, material ); scene.add( this.mesh ); this.mesh.position.x = (Math.random() - 0.5) * 4; this.mesh.position.y = Math.random() * 6 - 3; this.mesh.position.z = (Math.random() - 0.5) * 4; this.isBreaking = false; this.breakTimer = 0; this.onClick = () => { playSound(); if (!this.isBreaking) { this.breakApart(); } }; this.onDrag = () => { playSound(); // do something on drag this.mesh.rotation.x += 0.05; this.mesh.rotation.y += 0.05; }; this.initEventListeners(); } initEventListeners() { this.mesh.addEventListener('click', this.onClick, false); let isDragging = false; let prevMouseX = 0; let prevMouseY = 0; this.mesh.addEventListener('mousedown', (event) => { isDragging = true; prevMouseX = event.clientX; prevMouseY = event.clientY; this.onDrag(); }, false); document.addEventListener('mouseup', () => { isDragging = false; }, false); document.addEventListener('mousemove', (event) => { if (isDragging) { const deltaX = event.clientX - prevMouseX; const deltaY = event.clientY - prevMouseY; this.mesh.rotation.y += deltaX * 0.005; this.mesh.rotation.x += deltaY * 0.005; prevMouseX = event.clientX; prevMouseY = event.clientY; this.onDrag(); } }, false); } breakApart() { this.isBreaking = true; this.breakTimer = 0; for (let i = 0; i < maxShardsPerAvatar; i++) { const shardGeometry = new THREE.SphereGeometry( 0.1, 16, 16 ); const shardMaterial = new THREE.MeshBasicMaterial( { color: this.mesh.material.color.clone() } ); const shard = new THREE.Mesh( shardGeometry, shardMaterial ); shard.position.copy(this.mesh.position); shard.position.x += (Math.random() - 0.5) * 2; shard.position.y += (Math.random() - 0.5) * 2; shard.position.z += (Math.random() - 0.5) * 2; const velocityX = (Math.random() - 0.5) * 5; const velocityY = (Math.random() - 0.5) * 5; const velocityZ = (Math.random() - 0.5) * 5; shards.push({ mesh: shard, velocityX: velocityX, velocityY: velocityY, velocityZ: velocityZ }); scene.add(shard); } this.mesh.visible = false; } recombine() { this.isBreaking = false; this.mesh.visible = true; this.mesh.position.x = (Math.random() - 0.5) * 4; this.mesh.position.y = Math.random() * 6 - 3; this.mesh.position.z = (Math.random() - 0.5) * 4; } } for (let i = 0; i < numAvatars; i++) { avatars.push(new Avatar(i)); } function animate() { requestAnimationFrame( animate ); // Shard movement and recombination for (let i = 0; i < shards.length; i++) { const shard = shards[i]; shard.mesh.position.x += shard.velocityX * 0.01; shard.mesh.position.y += shard.velocityY * 0.01; shard.mesh.position.z += shard.velocityZ * 0.01; shard.velocityX *= 0.99; shard.velocityY *= 0.99; shard.velocityZ *= 0.99; if (shard.mesh.position.y < -5) { scene.remove(shard.mesh); shards.splice(i, 1); i--; } } //Check if avatars should recombine; avatars.forEach(avatar => { if (avatar.isBreaking) { avatar.breakTimer += 1; if (avatar.breakTimer > 100) { avatar.recombine(); } } }); camera.lookAt(new THREE.Vector3(0, 0, 0)); renderer.render( scene, camera ); } animate(); window.addEventListener( 'resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); }, false ); })(); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Glitchy Avatars with Sound & Chaos</title> <style> html, body { background: #181924; margin: 0; padding: 0; width: 100vw; height: 100vh; overflow: hidden; } #ui { position: fixed; top: 10px; left: 10px; color: #FFF; font-family: monospace; font-size: 15px; user-select: none; z-index: 10; text-shadow: 0 1px 7px #0008; background: #222b; border-radius: 7px; padding: 7px 12px; } </style> <script src="https://cdn.jsdelivr.net/npm/three@0.155.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.155.0/examples/js/controls/OrbitControls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tone@14.8.49/build/Tone.min.js"></script> </head> <body> <div id="ui"> <b>Glitchy Avatars</b><br> Click/drag avatars to glitch. <br> Listen to the sound chaos.<br> <small>By AI</small> </div> <script> // === PARAMETERS === const NUM_AVATARS = 5; // How many avatars const SHARD_COLORS = [0x87e0ff, 0xff58b2, 0xffec89, 0x7cfd90, 0xb08afe, 0xff5252]; const AVATAR_COLORS = [0x46e6e6, 0xd2fdff, 0xff949c, 0x99ff7f, 0x836fff]; const AVATAR_SIZES = [1.6, 1.3, 1.8, 1.1, 1.5]; const GLICH_PROB = 0.04; // === THREE.JS BOOTSTRAP === let scene = new THREE.Scene(); scene.background = new THREE.Color(0x181924); let camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 100); camera.position.set(0, 3.6, 9); let renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setClearColor(0x181924); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Orbit controls just for fun (disable on pointer down) let controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; // Lights { const l1 = new THREE.DirectionalLight(0xffffff, 1.0); l1.position.set(5,9,5); scene.add(l1); scene.add(new THREE.AmbientLight(0x404050, 0.65)); } // === AVATAR SYSTEM === class GlitchyAvatar { constructor(idx, position, color, soundPatch) { // Parameters this.idx = idx; this.state = "normal"; // normal|glitching|broken this.glitchTimer = 0.0; this.dragged = false; this.shatterTimeout = null; this.soundPatch = soundPatch; // Avatar object group this.group = new THREE.Group(); this.group.position.copy(position); // Avatar "figure": head, odd body, arms let bodyColor = color; this.components = []; // Body let bodyGeom = new THREE.CylinderGeometry(0.5, 0.7, 1.7, 4, 2, true); let bodyMat = new THREE.MeshPhysicalMaterial({color: bodyColor, roughness:0.45, metalness:0.3, transmission:0.25, clearcoat:0.3}); let body = new THREE.Mesh(bodyGeom, bodyMat); body.position.y = 0.8; body.rotation.y = Math.PI/6 + 0.1*(Math.random()-0.5); this.group.add(body); this.components.push(body); // Head let headGeom = new THREE.OctahedronGeometry(0.48,1); let headMat = new THREE.MeshPhysicalMaterial({color: bodyColor, roughness:0.27, metalness:0.5, emissive:0x1ff}); let head = new THREE.Mesh(headGeom, headMat); head.position.set(0, 1.8, 0); head.rotation.y = Math.random() * Math.PI; this.group.add(head); this.components.push(head); // Arms for (let side of [-1, 1]) { let armGeom = new THREE.BoxGeometry(0.15, 0.7, 0.15); let armMat = new THREE.MeshPhysicalMaterial({color: bodyColor, roughness:0.6, metalness:0.15}); let arm = new THREE.Mesh(armGeom, armMat); arm.position.set(side*0.52, 1.1, Math.random()*0.12-0.06); arm.rotation.z = side * Math.PI/7; arm.rotation.x = 0.2*(Math.random()-0.5); this.group.add(arm); this.components.push(arm); } // Glitchy "aura" (pixel plane shards hidden by default) this.shards = []; this.group.add(this.createShards()); // Add to scene scene.add(this.group); } createShards() { // Create a group of pixel floating shards, initially hidden let g = new THREE.Group(); g.visible = false; for (let i = 0; i < 32+Math.floor(16*Math.random()); i++) { let sz = Math.random()*0.23 + 0.09; let mat = new THREE.MeshPhysicalMaterial({ color: SHARD_COLORS[Math.floor(Math.random()*SHARD_COLORS.length)], roughness: 0.45, metalness: 0.5, flatShading: true, transparent:true, opacity:0.8 }); let shard = new THREE.Mesh( new THREE.BoxGeometry(sz, sz, sz), mat ); shard.position.set( THREE.MathUtils.randFloat(-0.7,0.7), THREE.MathUtils.randFloat(0.3,2.1), THREE.MathUtils.randFloat(-0.7,0.7) ); shard.startPos = shard.position.clone(); g.add(shard); this.shards.push(shard); } return g; } setState(newState) { if(this.state === newState) return; this.state = newState; if(newState === "broken") { this.group.children.forEach(c=>{ if (!c.geometry) return; c.visible = false; }); if(this.group.children[this.group.children.length-1]) { this.group.children[this.group.children.length-1].visible = true; } this.soundPatch.triggerAttackRelease('C5', '8n', '+0.01'); } else if(newState === "normal") { this.group.children.forEach(c=>{ if(!c.geometry) return; c.visible = true; }); if(this.group.children[this.group.children.length-1]) { this.group.children[this.group.children.length-1].visible = false; } } } glitch(now) { if(this.state === "broken") return; this.state = "glitching"; this.glitchTimer = now + 0.44 + Math.random()*0.4; this.group.children.forEach((c, i)=>{ if(c.geometry) { c.rotation.x = (Math.sin(now*4+(this.idx+i)*2) * 0.5) * Math.random(); c.rotation.y = (Math.cos(now*4-(this.idx+i)*2) * 0.5) * Math.random(); } }); // Play a unpredictable sound blip when glitching let freq = 440 + Math.random()*350; let dur = "32n"; this.soundPatch.triggerAttackRelease(freq, dur, '+0.01'); } breakApart() { this.setState("broken"); // Animate shards outwards with random velocity this.shards.forEach(shard=>{ shard.position.copy(shard.startPos); shard.userData.vel = new THREE.Vector3( (Math.random()-0.5)*1.5, Math.random()*2.5, (Math.random()-0.5)*1.5 ); shard.userData.floatY = (Math.random()-0.5)*2; }); // After random time, recombine clearTimeout(this.shatterTimeout); this.shatterTimeout = setTimeout(()=>{ this.recombine(); }, 950 + Math.random()*1800); } recombine() { // Animate shards to original positions & hide shards let t0 = performance.now(); let that = this; function lerpShards() { let t = (performance.now() - t0) / 650; if (t < 1) { that.shards.forEach(shard => { shard.position.lerp(shard.startPos, t); }); requestAnimationFrame(lerpShards); } else { that.shards.forEach(shard => { shard.position.copy(shard.startPos); }); that.setState("normal"); } } lerpShards(); } // Update internal animations; if broken, animate shards update(now, delta) { if (this.state === "glitching" && now > this.glitchTimer) { // Chance to break apart after glitching if (Math.random() < 0.77) this.setState("normal"); else this.breakApart(); } if (this.state === "broken") { // Animate shards floating outward and upwards if(this.group.children[this.group.children.length-1]) { let g = this.group.children[this.group.children.length-1]; g.visible = true; this.shards.forEach((shard,i)=>{ shard.position.addScaledVector(shard.userData.vel, delta*0.73); shard.position.y += Math.sin(now*3+i*0.5)*0.007*shard.userData.floatY; // Slow gravity shard.userData.vel.y -= delta*0.53; if(shard.position.y < 0.2) shard.userData.vel.y = Math.abs(shard.userData.vel.y)*0.47 + Math.random()*0.12; }); } } } // Check if a world position is close to this group (for picking) isNear(worldPos) { let dist = this.group.position.distanceTo(worldPos); return dist < 1.4; } } // === CREATE SOUND PATCHES — Tone.js Synths === function makePatch(idx) { // Each patch is a poly synth with a different preset switch(idx % 5) { case 0: // glitchy FM return new Tone.Synth({ oscillator: { type: 'fatsawtooth', count: 3, spread: 15 }, envelope: { attack: 0.02, decay: 0.13, sustain: 0.33, release: 0.23 } }).connect(new Tone.Chorus(2, 1.5, 0.6).toDestination()).toDestination(); case 1: // glassy return new Tone.AMSynth({ harmonicity: 1.95, envelope: { attack: 0.1, decay: 0.11, sustain: 0.46, release: 0.17 } }).connect(new Tone.Reverb(1.3)).toDestination(); case 2: // pinky metallic return new Tone.DuoSynth().connect(new Tone.PingPongDelay('4n', 0.25)).toDestination(); case 3: // crispy digital return new Tone.FMSynth({ modulationIndex: 2.7, envelope: { attack: 0.03, decay: 0.1, sustain: 0.21, release: 0.09 } }).connect(new Tone.BitCrusher(2)).toDestination(); case 4: // lofi percussive return new Tone.MembraneSynth({ envelope: { attack: 0.005, decay: 0.1, sustain: 0.1, release: 0.2 } }).toDestination(); } } // === INIT AVATARS === let AVATARS = []; for (let i=0; i<NUM_AVATARS; ++i) { let theta = i/NUM_AVATARS * Math.PI*2; let rad = 3.2 + 0.7*Math.random(); let pos = new THREE.Vector3( Math.cos(theta)*rad, 0, Math.sin(theta)*rad ); AVATARS.push( new GlitchyAvatar( i, pos, AVATAR_COLORS[i%AVATAR_COLORS.length], makePatch(i) ) ); } // === RAYCASTING & DRAG SYSTEM === let raycaster = new THREE.Raycaster(), pointer = new THREE.Vector2(); let draggingAvatar = null, dragOffset = new THREE.Vector3(); function getWorldPosFromPointer(x, y) { pointer.x = (x/renderer.domElement.width)*2 - 1; pointer.y = - (y/renderer.domElement.height)*2 + 1; raycaster.setFromCamera(pointer, camera); let groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); let intersection = new THREE.Vector3(); raycaster.ray.intersectPlane(groundPlane, intersection); return intersection; } function pickAvatar(x, y) { for(let av of AVATARS) { let worldPos = getWorldPosFromPointer(x, y); if(av.isNear(worldPos)) return av; } return null; } renderer.domElement.addEventListener('pointerdown', e=>{ // Resume Tone.js if necessary if(Tone && Tone.getContext() && Tone.context.state !== 'running') Tone.start(); let av = pickAvatar(e.offsetX, e.offsetY); if(av) { draggingAvatar = av; av.dragged = true; let wp = getWorldPosFromPointer(e.offsetX, e.offsetY); dragOffset = av.group.position.clone().sub(wp); // Trigger glitch for fun av.glitch(performance.now()*0.001); } }); renderer.domElement.addEventListener('pointermove', e=>{ if (draggingAvatar) { let wp = getWorldPosFromPointer(e.offsetX, e.offsetY); draggingAvatar.group.position.copy(wp.clone().add(dragOffset)); // Add a little extra random shakiness draggingAvatar.group.position.x += Math.sin(performance.now()*0.0021)*0.09; draggingAvatar.group.position.z += Math.cos(performance.now()*0.0021)*0.09; if (Math.random() < 0.15) draggingAvatar.glitch(performance.now()*0.001); } }); renderer.domElement.addEventListener('pointerup', ()=>{ if (draggingAvatar) { draggingAvatar.dragged = false; draggingAvatar = null; } }); // === AVATAR REACTIVITY — spontaneous chaos === function spontaneousGlitch(now) { // Each has a small chance to auto-glitch or break apart on its own AVATARS.forEach(av=>{ if(av.state === "normal" && Math.random()<GLICH_PROB) av.glitch(now); else if(av.state === "normal" && Math.random()<GLICH_PROB*0.3) av.breakApart(); }); } // === CHAOTIC MAIN RENDER-UPDATE LOOP === let lastTime = performance.now(); function animate() { let now = performance.now(); let dt = (now-lastTime)/1000.0; lastTime = now; let nowSec = now*0.001; spontaneousGlitch(nowSec); AVATARS.forEach((av,i)=>{ // Let avatars wiggle subtly if(av.state === "normal") { av.group.children.forEach((c,j)=>{ if(c.geometry) { c.rotation.x = Math.sin(nowSec*1.4+av.idx*1.35+j)*0.13 + Math.sin(nowSec*4.8+j)*0.03; c.rotation.y += Math.sin(nowSec*7.1+i+j)*0.015; } }); av.group.position.y = Math.sin(nowSec*0.83+av.idx)*0.10 + 0.02*(Math.random()-0.5); } av.update(nowSec, dt); }); controls.enabled = !draggingAvatar; controls.update(); renderer.render(scene, camera); requestAnimationFrame(animate); } animate(); window.addEventListener('resize', ()=>{ camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html> <head> <title>Glitch Avatars</title> <style>body{margin:0;overflow:hidden;background:black}</style> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r148/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.148.0/examples/js/controls/OrbitControls.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.79/Tone.min.js"></script> </head> <body> <script> let scene, camera, renderer, controls, shardsArray = []; const avatars = []; init(); function init() { // Three.js setup scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); camera.position.z = 10; renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); controls = new THREE.OrbitControls(camera, renderer.domElement); // Create avatars for (let i = 0; i < 3; i++) { const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, wireframe: true }); const mesh = new THREE.Mesh(geometry, material); mesh.position.set( (Math.random() - 0.5) * 12, (Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8 ); scene.add(mesh); // Tone.js setup const synth = new Tone.PolySynth(Tone.Synth).toDestination(); synth.set({ oscillator: { type: ['sine', 'square', 'sawtooth'][i % 3] }, envelope: { attack: 0.1, decay: 0.2, sustain: 0.5, release: 1 } }); avatars.push({ mesh, geometry, material, synth }); } // Event listeners window.addEventListener('resize', () => { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); document.addEventListener('click', (e) => { const mouse = new THREE.Vector2( (e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1 ); const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh)); if (intersects.length > 0) { const avatar = avatars.find(a => a.mesh === intersects[0].object); if (Tone.Transport.state !== 'started') Tone.Transport.start(); avatar.synth.triggerAttackRelease('C4', '8n'); breakAvatar(avatar); } }); animate(); } function breakAvatar(avatar) { // Remove original mesh scene.remove(avatar.mesh); const originalPos = avatar.mesh.position.clone(); // Create shards const shardCount = 15; const shards = []; for (let i = 0; i < shardCount; i++) { const shardGeo = new THREE.BoxGeometry(0.15, 0.15, 0.15); const shardMat = new THREE.MeshBasicMaterial({ color: avatar.material.color.getHex() }); const shard = new THREE.Mesh(shardGeo, shardMat); shard.position.set( originalPos.x + (Math.random() - 0.5) * 2, originalPos.y + (Math.random() - 0.5) * 2, originalPos.z + (Math.random() - 0.5) * 2 ); scene.add(shard); shards.push(shard); // Store reference } shardsArray.push(...shards); // Add to global array // Schedule recombination setTimeout(() => { // Remove shards shards.forEach(s => { scene.remove(s); s.geometry.dispose(); s.material.dispose(); }); // Recreate avatar const newMesh = new THREE.Mesh(avatar.geometry, avatar.material.clone()); newMesh.position.copy(originalPos); scene.add(newMesh); avatar.mesh = newMesh; }, 2000); } function animate() { requestAnimationFrame(animate); // Animate shards shardsArray = shardsArray.filter(shard => { if (!scene.children.includes(shard)) return false; shard.rotation.x += 0.05 + Math.random() * 0.02; shard.rotation.y += 0.05 + Math.random() * 0.02; shard.position.y += 0.02; return true; }); renderer.render(scene, camera); } </script> </body> </html> ```
Here's a complete HTML code that creates a scene with glitchy avatars using Three.js, Tone.js for sound, and user interactions: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatars</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.78/Tone.min.js"></script> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); // Tone.js setup const synths = []; for (let i = 0; i < 5; i++) { synths.push(new Tone.Synth().toDestination()); } // Avatar class class Avatar { constructor(x, y, z, color, size) { this.group = new THREE.Group(); this.color = color; this.size = size; this.shards = []; this.isBroken = false; this.synth = synths[Math.floor(Math.random() * synths.length)]; // Create shards for (let i = 0; i < 10; i++) { const shardGeometry = new THREE.BoxGeometry(size / 5, size / 5, size / 5); const shardMaterial = new THREE.MeshPhongMaterial({ color: color }); const shard = new THREE.Mesh(shardGeometry, shardMaterial); shard.position.set( (Math.random() - 0.5) * size, (Math.random() - 0.5) * size, (Math.random() - 0.5) * size ); this.shards.push(shard); this.group.add(shard); } // Main avatar geometry const geometry = new THREE.BoxGeometry(size, size, size); const material = new THREE.MeshPhongMaterial({ color: color }); this.main = new THREE.Mesh(geometry, material); this.group.add(this.main); // Position the avatar this.group.position.set(x, y, z); scene.add(this.group); // Randomize the avatar this.randomize(); } randomize() { this.main.rotation.set( Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI ); } breakApart() { if (this.isBroken) return; this.isBroken = true; this.main.visible = false; for (let shard of this.shards) { shard.userData.velocity = { x: (Math.random() - 0.5) * 0.1, y: (Math.random() - 0.5) * 0.1, z: (Math.random() - 0.5) * 0.1 }; } this.synth.triggerAttackRelease("C4", "8n"); } recombine() { if (!this.isBroken) return; this.isBroken = false; this.main.visible = true; for (let shard of this.shards) { shard.position.set( (Math.random() - 0.5) * this.size, (Math.random() - 0.5) * this.size, (Math.random() - 0.5) * this.size ); shard.userData.velocity = { x: 0, y: 0, z: 0 }; } this.synth.triggerAttackRelease("E4", "8n"); } update() { if (this.isBroken) { for (let shard of this.shards) { shard.position.x += shard.userData.velocity.x; shard.position.y += shard.userData.velocity.y; shard.position.z += shard.userData.velocity.z; shard.userData.velocity.y -= 0.001; // Gravity } } } } // Create avatars const avatars = []; const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff]; for (let i = 0; i < 5; i++) { avatars.push(new Avatar( (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, colors[i], 1 + Math.random() * 2 )); } // Camera position camera.position.z = 25; // Raycaster for interactions const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Handle mouse move window.addEventListener('mousemove', (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; }); // Handle mouse click window.addEventListener('click', (event) => { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.group)); if (intersects.length > 0) { const avatar = avatars.find(a => a.group === intersects[0].object.parent); if (avatar) { if (Math.random() > 0.5) { avatar.breakApart(); } else { avatar.recombine(); } } } }); // Handle touch events window.addEventListener('touchmove', (event) => { if (event.touches.length > 0) { mouse.x = (event.touches[0].clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.touches[0].clientY / window.innerHeight) * 2 + 1; } }); window.addEventListener('touchstart', (event) => { if (event.touches.length > 0) { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.group)); if (intersects.length > 0) { const avatar = avatars.find(a => a.group === intersects[0].object.parent); if (avatar) { if (Math.random() > 0.5) { avatar.breakApart(); } else { avatar.recombine(); } } } } }); // Animation loop function animate() { requestAnimationFrame(animate); // Randomly break or recombine avatars if (Math.random() < 0.01) { const avatar = avatars[Math.floor(Math.random() * avatars.length)]; if (Math.random() > 0.5) { avatar.breakApart(); } else { avatar.recombine(); } } // Update avatars for (let avatar of avatars) { avatar.update(); } // Rotate camera slightly camera.position.x = Math.sin(Date.now() * 0.0005) * 20; camera.position.y = Math.sin(Date.now() * 0.0003) * 10; camera.lookAt(0, 0, 0); renderer.render(scene, camera); } animate(); </script> </body> </html> ``` This code creates a scene with 5 avatars that can be interacted with by clicking or touching them. Each avatar has a unique color and sound. The avatars can break apart into pixel shards and recombine, creating a chaotic dance of sound and form. The camera slowly orbits around the scene.
Here's a full HTML code that creates a set of glitchy avatars using Three.js and Tone.js. The avatars react to user interactions and emit unique sounds. Please note that you'll need to host this on a server to properly load the external libraries and assets. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatars</title> <style> body { margin: 0; overflow: hidden; background-color: #000; } canvas { display: block; } </style> </head> <body> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/tone@14.7.77/build/Tone.min.js"></script> <script> // Initialize Three.js scene const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Add orbit controls const controls = new THREE.OrbitControls(camera, renderer.domElement); camera.position.z = 5; // Initialize Tone.js const synths = []; const now = Tone.now(); // Create avatars const avatars = []; const avatarCount = 5; const avatarGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const avatarMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }); for (let i = 0; i < avatarCount; i++) { const avatar = new THREE.Mesh(avatarGeometry, avatarMaterial.clone()); avatar.position.x = (Math.random() - 0.5) * 10; avatar.position.y = (Math.random() - 0.5) * 10; avatar.position.z = (Math.random() - 0.5) * 10; // Create a unique sound for each avatar const synth = new Tone.Synth({ oscillator: { type: ['sine', 'square', 'triangle', 'sawtooth'][Math.floor(Math.random() * 4)] }, envelope: { attack: Math.random() * 0.5, decay: Math.random() * 0.5, sustain: Math.random(), release: Math.random() * 1.5 } }).toDestination(); synths.push(synth); avatars.push({ mesh: avatar, synth: synth, glitching: false, glitchProgress: 0 }); scene.add(avatar); } // Handle clicks and drags const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); let draggedAvatar = null; function onMouseMove(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; if (draggedAvatar) { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(avatar => avatar.mesh)); if (intersects.length > 0) { draggedAvatar.mesh.position.copy(intersects[0].point); } } } function onMouseDown(event) { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(avatars.map(avatar => avatar.mesh)); if (intersects.length > 0) { draggedAvatar = avatars.find(avatar => avatar.mesh === intersects[0].object); draggedAvatar.synth.triggerAttack('C4', now); } } function onMouseUp() { if (draggedAvatar) { draggedAvatar.synth.triggerRelease(now + 0.1); draggedAvatar = null; } } window.addEventListener('mousemove', onMouseMove, false); window.addEventListener('mousedown', onMouseDown, false); window.addEventListener('mouseup', onMouseUp, false); // Animation loop function animate() { requestAnimationFrame(animate); avatars.forEach(avatar => { if (!avatar.glitching && Math.random() < 0.01) { avatar.glitching = true; avatar.glitchProgress = 0; } if (avatar.glitching) { avatar.glitchProgress += 0.05; const glitchFactor = Math.sin(avatar.glitchProgress * Math.PI * 2) * 0.5; avatar.mesh.geometry.vertices.forEach(vertex => { vertex.x += (Math.random() - 0.5) * glitchFactor * 0.1; vertex.y += (Math.random() - 0.5) * glitchFactor * 0.1; vertex.z += (Math.random() - 0.5) * glitchFactor * 0.1; }); avatar.mesh.geometry.verticesNeedUpdate = true; if (avatar.glitchProgress >= 1) { avatar.glitching = false; avatar.mesh.geometry.vertices.forEach(vertex => { vertex.x = vertex._originalX || vertex.x; vertex.y = vertex._originalY || vertex.y; vertex.z = vertex._originalZ || vertex.z; }); avatar.mesh.geometry.verticesNeedUpdate = true; } else { avatar.mesh.geometry.vertices.forEach(vertex => { vertex._originalX = vertex._originalX || vertex.x; vertex._originalY = vertex._originalY || vertex.y; vertex._originalZ = vertex._originalZ || vertex.z; }); } } }); controls.update(); renderer.render(scene, camera); } animate(); // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html> ``` This code creates a 3D scene with glitchy avatars that react to user interactions. Each avatar emits a unique sound when clicked and dragged. The avatars occasionally "break" apart into pixel shards and recombine, creating a chaotic dance of sound and form. The code includes all necessary libraries (Three.js and Tone.js) and handles user interactions such as clicks and drags.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Glitchy Avatars with Three.js and Tone.js</title> <style> body { margin: 0; overflow: hidden; background: #000; } canvas { display: block; } </style> <!-- Include Three.js and Tone.js via CDN --> <script src="https://cdn.jsdelivr.net/npm/three@0.150.1/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tone@14.8.33/build/Tone.min.js"></script> </head> <body> <script> // --- Initialization --- let scene, camera, renderer, avatars = [], shards = [], raycaster, mouse = new THREE.Vector2(); let interactionActive = false; const avatarCount = 4; // Number of avatars const shardCount = 200; // Number of pixel shards init(); animate(); function init() { // Scene and Camera scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 1000); camera.position.z = 50; // Renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Raycaster for interaction raycaster = new THREE.Raycaster(); // Light const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(0, 50, 50); scene.add(light); // Create avatars for(let i=0; i<avatarCount; i++) { const avatar = createAvatar(i); scene.add(avatar.mesh); avatars.push(avatar); } // Create shards for(let i=0; i<shardCount; i++) { const shard = createShard(); scene.add(shard.mesh); shards.push(shard); } // Event listeners window.addEventListener('resize', onWindowResize, false); window.addEventListener('mousedown', onMouseDown, false); window.addEventListener('mouseup', onMouseUp, false); window.addEventListener('mousemove', onMouseMove, false); } // Avatar class class Avatar { constructor(index) { this.index = index; this.group = new THREE.Group(); // Create a simple mascot shape - a sphere with different colors const geometry = new THREE.SphereGeometry(2, 32, 32); const material = new THREE.MeshStandardMaterial({ color: new THREE.Color(`hsl(${index * 90}, 70%, 50%)`) }); this.mesh = new THREE.Mesh(geometry, material); this.mesh.position.x = (index - 1.5) * 8; // Add slight pulsing animation this.pulseDirection = 1; this.scale = 1; // Sound setup this.synth = new Tone.Synth().toDestination(); this.setupSoundPatch(); // State this.breaking = false; this.shards = []; } setupSoundPatch() { const waveformOptions = ['sine', 'square', 'triangle', 'sawtooth']; const waveType = waveformOptions[this.index % waveformOptions.length]; this.synth.type = waveType; } triggerSound() { const freq = Math.random() * 400 + 200; this.synth.triggerAttackRelease(freq, "8n"); } breakApart() { if(this.breaking) return; this.breaking = true; // Generate shards for(let i=0; i<20; i++) { const shard = createShard(); shard.mesh.position.copy(this.mesh.position); // Sprinkle in velocity shard.velocity = new THREE.Vector3( (Math.random() - 0.5) * 2, Math.random() * 2, (Math.random() - 0.5) * 2 ); shards.push(shard); } // Remove avatar mesh scene.remove(this.mesh); } update(delta) { // Pulsing animation this.scale += this.pulseDirection * delta * 0.5; if(this.scale > 1.2 || this.scale < 0.8) { this.pulseDirection *= -1; } this.mesh.scale.setScalar(this.scale); // if breaking, animate shards if(this.breaking) return; } } // Create an avatar function createAvatar(index) { return new Avatar(index); } // Shard class class Shard { constructor() { const geometry = new THREE.PlaneGeometry(0.5, 0.5); const material = new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff, transparent: true, opacity: 0.8 }); this.mesh = new THREE.Mesh(geometry, material); this.velocity = new THREE.Vector3(); } update(delta) { this.mesh.position.addScaledVector(this.velocity, delta); // Optional: wrap around if(this.mesh.position.y < -50) { scene.remove(this.mesh); } } } function createShard() { return new Shard(); } // --- Animation Loop --- function animate() { requestAnimationFrame(animate); const delta = 0.016; // approx 60fps // Animate avatars avatars.forEach(avatar => { avatar.update(delta); }); // Animate shards for(let i=shards.length -1; i>=0; i--) { const shard = shards[i]; shard.update(delta); // Remove if out of bounds if(shard.mesh.position.y < -50) { scene.remove(shard.mesh); shards.splice(i,1); } } renderer.render(scene, camera); } // --- Interaction --- function onWindowResize() { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function getIntersects(event) { mouse.x = ( event.clientX / window.innerWidth) * 2 - 1; mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; raycaster.setFromCamera(mouse, camera); const objects = avatars.map(a => a.mesh); const intersects = raycaster.intersectObjects(objects); return intersects; } function onMouseDown(event) { const intersects = getIntersects(event); if(intersects.length > 0) { const avatarMesh = intersects[0].object; const avatar = avatars.find(a => a.mesh === avatarMesh); if(avatar) { avatar.triggerSound(); avatar.breakApart(); } } } function onMouseUp(event) { // Could trigger reactions on release } function onMouseMove(event) { // Could implement drag reactions } </script> </body> </html> ```
Here’s a self-contained HTML example that sets up a Three.js scene of “glitchy” avatars, each tied to its own Tone.js synth. Avatars respond to clicks/drags, occasionally shatter into pixel shards, make chaotic motion and sound, then reassemble. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Glitchy Avatars with Three.js & Tone.js</title> <style> body { margin: 0; overflow: hidden; background: #000; } canvas { display: block; } </style> <!-- Three.js --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r152/three.min.js"></script> <!-- Tone.js --> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.54/Tone.min.js"></script> </head> <body> <script> // === Scene Setup === const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000); camera.position.z = 5; const renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement); // Raycaster for interaction const ray = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // Tone.js context Tone.start(); // Avatar container const avatars = []; // Utility: random between function rnd(a,b){return a + (b-a)*Math.random();} // Build N avatars const N = 5; for(let i=0;i<N;i++){ // Plane geometry as avatar const geo = new THREE.PlaneGeometry(1,1,10,10); const mat = new THREE.ShaderMaterial({ uniforms: { time: {value:0}, colorOffset: {value:Math.random()} }, vertexShader:` varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); } `, fragmentShader:` uniform float time, colorOffset; varying vec2 vUv; float rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);} void main(){ vec2 uv = vUv; // glitch: offset rows float y = floor(uv.y*10.0)/10.0; float shift = (rand(vec2(y,time)) - 0.5)*0.2; uv.x += shift; // color modulation vec3 col = vec3(0.2+0.8*uv.x, 0.2+0.8*uv.y, 0.5+0.5*sin(time+colorOffset*6.28)); gl_FragColor = vec4(col,1.0); } ` }); const mesh = new THREE.Mesh(geo, mat); mesh.position.x = rnd(-3,3); mesh.position.y = rnd(-2,2); mesh.userData = { broken:false, shards:[], drag:false, offset:new THREE.Vector3() }; scene.add(mesh); avatars.push(mesh); // Each avatar gets its own synth const synth = new Tone.Synth({ oscillator: { type: "square" }, envelope: { attack:0.01, decay:0.2, sustain:0.3, release:1 } }).toDestination(); mesh.userData.synth = synth; } // Handle resize window.addEventListener('resize', ()=> { camera.aspect = innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight); }); // Interaction renderer.domElement.addEventListener('pointerdown', e=>{ mouse.x = (e.clientX/innerWidth)*2 -1; mouse.y = -(e.clientY/innerHeight)*2 +1; ray.setFromCamera(mouse,camera); const hits = ray.intersectObjects(avatars); if(hits.length){ const a = hits[0].object; a.userData.drag = true; // record offset const planeZ = new THREE.Plane(new THREE.Vector3(0,0,1), -a.position.z); const intersect = new THREE.Vector3(); ray.ray.intersectPlane(planeZ, intersect); a.userData.offset.copy(intersect).sub(a.position); // play a random note const note = ['C4','E4','G4','B4','D5'][Math.floor(Math.random()*5)]; a.userData.synth.triggerAttackRelease(note, '8n'); // small glitch animation a.material.uniforms.time.value += 1.0; } }); renderer.domElement.addEventListener('pointermove', e=>{ mouse.x = (e.clientX/innerWidth)*2 -1; mouse.y = -(e.clientY/innerHeight)*2 +1; ray.setFromCamera(mouse,camera); const planeZ = new THREE.Plane(new THREE.Vector3(0,0,1), 0); const intersect = new THREE.Vector3(); ray.ray.intersectPlane(planeZ, intersect); avatars.forEach(a=>{ if(a.userData.drag && !a.userData.broken){ a.position.copy(intersect).sub(a.userData.offset); } }); }); renderer.domElement.addEventListener('pointerup', ()=> { avatars.forEach(a=> a.userData.drag = false); }); // Breaking/Recombining logic function shatter(avatar){ if(avatar.userData.broken) return; avatar.userData.broken = true; const mat = avatar.material.clone(); const shards = []; const N = 20; for(let i=0;i<N;i++){ const g = new THREE.PlaneGeometry(0.2,0.2); const m = mat.clone(); const s = new THREE.Mesh(g,m); s.position.copy(avatar.position); // random direction s.userData.velocity = new THREE.Vector3(rnd(-1,1),rnd(-1,1),rnd(-0.5,0.5)); scene.add(s); shards.push(s); } avatar.visible = false; avatar.userData.shards = shards; // play burst sound avatar.userData.synth.triggerAttackRelease('C6','16n'); // schedule reassembly setTimeout(()=>{ shards.forEach(s=> scene.remove(s)); avatar.visible = true; avatar.userData.broken = false; }, rnd(1000,2000)); } // Animation loop const clock = new THREE.Clock(); function animate(){ requestAnimationFrame(animate); const t = clock.getElapsedTime(); avatars.forEach(a=>{ a.material.uniforms.time.value = t*0.5; // random jitter if(!a.userData.broken){ a.rotation.z += 0.001 * Math.sin(t+a.material.uniforms.colorOffset.value*10); } // animate shards a.userData.shards.forEach(s=>{ s.position.addScaledVector(s.userData.velocity, 0.02); s.rotation.x += 0.1; s.rotation.y += 0.1; }); // occasionally break if(Math.random() < 0.0008) shatter(a); }); renderer.render(scene,camera); } animate(); </script> </body> </html> ``` How it works: • Creates N plane-mesh avatars with a simple “glitch” fragment shader. • Each avatar has its own Tone.js Synth. Clicking triggers a note and a glitch spike. • Drag-and-drop via raycasting. • On rare random ticks, an avatar shatters into tiny plane-shards that fly and then reassemble. • Shards carry their own velocity and rotate, while the avatar is hidden, then pops back. Feel free to swap in real avatar textures, tweak fragment shader glitch patterns, adjust sound patches or shard geometry for richer visuals and audio chaos!
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatars</title> <style> body { margin: 0; background-color: #000; } #canvas { width: 100%; height: 100vh; display: block; } </style> </head> <body> <canvas id="canvas"></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone.js/14.7.58/Tone.min.js"></script> <script> // Create the scene, camera, and renderer const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); // Create the avatars const avatars = []; for (let i = 0; i < 10; i++) { const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff }); const avatar = new THREE.Mesh(geometry, material); avatar.position.x = Math.random() * 10 - 5; avatar.position.y = Math.random() * 10 - 5; avatar.position.z = Math.random() * 10 - 5; scene.add(avatar); avatars.push(avatar); } // Create the sound patches const soundPatches = []; for (let i = 0; i < 10; i++) { const synth = new Tone.PolySynth().toDestination(); soundPatches.push(synth); } // Animate the avatars function animate() { requestAnimationFrame(animate); for (let i = 0; i < avatars.length; i++) { const avatar = avatars[i]; avatar.rotation.x += 0.01; avatar.rotation.y += 0.01; if (Math.random() < 0.1) { // Break apart into pixel shards const shards = []; for (let j = 0; j < 100; j++) { const shard = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff })); shard.position.x = avatar.position.x + Math.random() * 2 - 1; shard.position.y = avatar.position.y + Math.random() * 2 - 1; shard.position.z = avatar.position.z + Math.random() * 2 - 1; scene.add(shard); shards.push(shard); } // Float and recombine for (let j = 0; j < shards.length; j++) { const shard = shards[j]; shard.position.x += Math.random() * 0.1 - 0.05; shard.position.y += Math.random() * 0.1 - 0.05; shard.position.z += Math.random() * 0.1 - 0.05; if (Math.random() < 0.1) { scene.remove(shard); } } } // Play sound patch soundPatches[i].triggerAttackRelease('C4', '8n'); } renderer.render(scene, camera); } animate(); // Handle user interactions document.addEventListener('click', (event) => { for (let i = 0; i < avatars.length; i++) { const avatar = avatars[i]; // React unpredictably to clicks if (Math.random() < 0.5) { avatar.position.x += Math.random() * 2 - 1; avatar.position.y += Math.random() * 2 - 1; avatar.position.z += Math.random() * 2 - 1; } } }); document.addEventListener('dragover', (event) => { for (let i = 0; i < avatars.length; i++) { const avatar = avatars[i]; // React unpredictably to drags if (Math.random() < 0.5) { avatar.rotation.x += Math.random() * 2 - 1; avatar.rotation.y += Math.random() * 2 - 1; avatar.rotation.z += Math.random() * 2 - 1; } } }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Glitchy Avatars Puppeteer</title> <style> body, html { margin: 0; padding: 0; overflow: hidden; background: #111; user-select: none; height: 100vh; width: 100vw; } canvas { display: block; position: absolute; top: 0; left: 0; } #info { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); color: #eee; font-family: monospace; user-select: none; pointer-events: none; z-index: 10; font-size: 14px; } </style> </head> <body> <div id="info">Click or drag avatars to glitch their sound & form</div> <script src="https://cdn.jsdelivr.net/npm/three@0.150.1/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.150.1/examples/js/controls/OrbitControls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tone@14.8.52/build/Tone.js"></script> <script> // ----- Setup Three.js Scene ----- const scene = new THREE.Scene(); scene.background = new THREE.Color(0x111111); const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000); camera.position.set(0, 2, 8); const renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enablePan = false; controls.minDistance = 4; controls.maxDistance = 12; // Ambient light and subtle directional light scene.add(new THREE.AmbientLight(0x555555)); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(5, 10, 7); scene.add(dirLight); // Utility: random between min,max inclusive function randRange(min,max){ return Math.random()*(max-min)+min; } // Glitchy Avatar Class class GlitchyAvatar { constructor(id, position){ this.id = id; this.position = position.clone(); // Create a simple avatar geometry: a low-poly icosahedron this.geometry = new THREE.IcosahedronGeometry(0.8, 1); this.material = new THREE.MeshStandardMaterial({ color: new THREE.Color(`hsl(${randRange(150, 240)}, 80%, 55%)`), roughness: 0.45, metalness: 0.6, flatShading: true, transparent: true, }); this.mesh = new THREE.Mesh(this.geometry, this.material); this.mesh.position.copy(this.position); scene.add(this.mesh); // Original vertices copied for reset this.originalVertices = this.geometry.attributes.position.array.slice(); // State this.isBroken = false; this.shards = null; this.shardVelocities = null; this.shardLifeTime = 0; // Setup Tone.js sound patch this.synth = this.makeUniqueSynth(); this.synth.volume.value = -12; // Sound parameters fluctuating randomly this.soundParams = { frequency: randRange(220, 550), detune: randRange(-120,120), tremoloFreq: randRange(3, 8), filterFreq: randRange(400, 1200), }; this.soundPlaying = false; // Start continuous ambient sound but very low volume & subtle modulation this.tremolo = new Tone.Tremolo(this.soundParams.tremoloFreq, 0.1).start(); this.filter = new Tone.Filter(this.soundParams.filterFreq, "lowpass"); this.synth.chain(this.filter, this.tremolo, Tone.Destination); this.playAmbientSound(); // Interaction state this.dragging = false; // Bind methods this.update = this.update.bind(this); this.onClick = this.onClick.bind(this); this.onDrag = this.onDrag.bind(this); this.breakApart = this.breakApart.bind(this); this.recombine = this.recombine.bind(this); } makeUniqueSynth(){ // Use a Tone.Synth with slightly different oscillator types and envelopes per avatar const oscTypes = ['triangle', 'sine', 'square', 'sawtooth']; const oscType = oscTypes[Math.floor(randRange(0, oscTypes.length))]; const attack = randRange(0.01, 0.2); const release = randRange(0.2, 0.45); return new Tone.Synth({ oscillator: { type: oscType }, envelope: { attack, decay:0.01, sustain:0.05, release } }); } playAmbientSound(){ if(this.soundPlaying) return; const now = Tone.now(); this.synth.triggerAttack(this.soundParams.frequency, now); this.soundPlaying = true; } stopAmbientSound(){ if(!this.soundPlaying) return; this.synth.triggerRelease(Tone.now()); this.soundPlaying = false; } // Unpredictable glitch on click: modulate sound and distort avatar vertices onClick(){ if(this.isBroken) return; // ignore clicks while broken // Randomly detune frequency, filter freq and trigger small envelope pulses this.soundParams.detune += randRange(-100, 100); this.soundParams.filterFreq += randRange(-200, 200); // Clamp filter freq to sane range this.soundParams.filterFreq = Math.min(Math.max(this.soundParams.filterFreq, 300), 2000); this.filter.frequency.rampTo(this.soundParams.filterFreq, 0.3); this.synth.detune.rampTo(this.soundParams.detune, 0.2); // Quick retrigger a short note for glitch effect this.synth.triggerAttackRelease(this.soundParams.frequency * (1 + randRange(-0.05, 0.05)), "16n"); // Distort vertices randomly (small glitch) const posAttr = this.geometry.attributes.position; for(let i=0; i<posAttr.count; i++){ if(Math.random() < 0.3){ let x = posAttr.getX(i); let y = posAttr.getY(i); let z = posAttr.getZ(i); x += randRange(-0.08, 0.08); y += randRange(-0.08, 0.08); z += randRange(-0.08, 0.08); posAttr.setXYZ(i, x, y, z); } } posAttr.needsUpdate = true; } // On drag: unpredictably move avatar and pulse sound/tremolo freq onDrag(movementX, movementY){ if(this.isBroken) return; // Move avatar position loosely with some randomness this.mesh.position.x += movementX * 0.008 * (Math.random()*1.5 + 0.3); this.mesh.position.y -= movementY * 0.008 * (Math.random()*1.5 + 0.3); // Modulate sound tremolo freq wildly based on drag speed let nextFreq = this.soundParams.tremoloFreq + movementX*0.5 + movementY*0.5; nextFreq = Math.min(Math.max(nextFreq, 1.5), 15); this.tremolo.frequency.rampTo(nextFreq, 0.1); this.soundParams.tremoloFreq = nextFreq; // Pulse a note randomly to add chaotic feel if(Math.random() < 0.1) { this.synth.triggerAttackRelease( this.soundParams.frequency * (1 + randRange(-0.2, 0.2)), "32n" ); } // Slight vertex jitter on drag const posAttr = this.geometry.attributes.position; for(let i=0; i<posAttr.count; i++){ if(Math.random() < 0.2){ let x = posAttr.getX(i); let y = posAttr.getY(i); let z = posAttr.getZ(i); x += randRange(-0.05, 0.05); y += randRange(-0.05, 0.05); z += randRange(-0.05, 0.05); posAttr.setXYZ(i, x, y, z); } } posAttr.needsUpdate = true; // Occasionally break apart on strong drags if(Math.abs(movementX) + Math.abs(movementY) > 25 && !this.isBroken && Math.random() < 0.7){ this.breakApart(); } } breakApart(){ // Destroy original mesh geometry & create pixel shards this.isBroken = true; scene.remove(this.mesh); // Create shards: small spheres replacing vertices scattered around original position const shardCount = this.geometry.attributes.position.count; this.shards = new THREE.Group(); // Create spheres for each vertex let positions = this.geometry.attributes.position.array; for(let i=0; i<shardCount; i++){ const shardGeo = new THREE.SphereGeometry(0.05, 4, 4); const color = new THREE.Color(this.material.color.getHex()); color.offsetHSL(randRange(-0.1,0.1), randRange(-0.2,0.2), randRange(-0.1,0.1)); const shardMat = new THREE.MeshStandardMaterial({ color: color, flatShading: true, metalness: 0.3, roughness: 0.6, transparent: true, opacity: 1.0 }); const mesh = new THREE.Mesh(shardGeo, shardMat); // Initial position from original vertex in world relative to avatar position let vx = positions[i*3 + 0]; let vy = positions[i*3 + 1]; let vz = positions[i*3 + 2]; mesh.position.set(vx, vy, vz); this.shards.add(mesh); } this.shards.position.copy(this.position); scene.add(this.shards); // Give shards random velocities for floating apart this.shardVelocities = []; for(let i=0; i<shardCount; i++){ this.shardVelocities.push(new THREE.Vector3( randRange(-0.05,0.05), randRange(0.03,0.15), randRange(-0.05,0.05) )); } this.shardLifeTime = 0; // Stop ambient sound & trigger chaotic glitch sound burst this.stopAmbientSound(); this.triggerGlitchExplosionSound(); } triggerGlitchExplosionSound(){ const now = Tone.now(); for(let i=0; i<5; i++){ const freq = this.soundParams.frequency * randRange(0.7, 1.5); const dur = "32n"; this.synth.triggerAttackRelease(freq, dur, now + i*0.05); } } recombine(){ // Slowly recombine shards back into the avatar mesh if(!this.isBroken) return; this.shardLifeTime += 0.013; let done = false; const targetVertices = this.geometry.attributes.position; const shardCount = targetVertices.count; for(let i=0; i<shardCount; i++){ const shard = this.shards.children[i]; if(!shard) continue; // Move shard toward original vertex position with easing const origX = this.originalVertices[i*3]; const origY = this.originalVertices[i*3+1]; const origZ = this.originalVertices[i*3+2]; // Lerp position shard.position.x += (origX - shard.position.x) * 0.08; shard.position.y += (origY - shard.position.y) * 0.08; shard.position.z += (origZ - shard.position.z) * 0.08; // Reduce velocity this.shardVelocities[i].multiplyScalar(0.85); // Fade shards opacity out shard.material.opacity -= 0.012; if(shard.material.opacity < 0) shard.material.opacity = 0; // Add small jitter to shards to keep chaotic feel shard.position.add(this.shardVelocities[i]); if(shard.material.opacity > 0.15) done = false; else done = true; // Tentative - will finalize after loop } // When shards faded & aligned sufficiently close, restore mesh if(done) { scene.remove(this.shards); this.shards.traverse(child => { if(child.geometry) child.geometry.dispose(); if(child.material) child.material.dispose(); }); this.shards = null; this.isBroken = false; // Restore mesh & reset vertices this.mesh.geometry.attributes.position.array.set(this.originalVertices); this.mesh.geometry.attributes.position.needsUpdate = true; this.mesh.position.copy(this.position); scene.add(this.mesh); // Re-enable ambient sound gradually with low volume burst this.soundParams.filterFreq = 1000; this.filter.frequency.cancelScheduledValues(Tone.now()); this.filter.frequency.rampTo(this.soundParams.filterFreq, 0.6); this.synth.detune.cancelScheduledValues(Tone.now()); this.synth.detune.rampTo(0, 0.6); this.playAmbientSound(); // Small pulse on recombine this.synth.triggerAttackRelease(this.soundParams.frequency*1.1, "16n"); } } update(){ if(this.isBroken) { this.recombine(); } else { // Subtle oscillations in vertices to keep glitch alive const posAttr = this.geometry.attributes.position; const time = performance.now()*0.001; for(let i=0; i<posAttr.count; i++){ const ox = this.originalVertices[i*3]; const oy = this.originalVertices[i*3+1]; const oz = this.originalVertices[i*3+2]; // Small noise-based oscillation const nx = ox + 0.04 * Math.sin(i*3 + time*4 + this.id); const ny = oy + 0.04 * Math.cos(i*2 + time*5 + this.id*1.5); const nz = oz + 0.04 * Math.sin(i + time*3 + this.id*2); posAttr.setXYZ(i, nx, ny, nz); } posAttr.needsUpdate = true; // Slight rotation continuous & subtle position oscillation this.mesh.rotation.x += 0.002+this.id*0.0003; this.mesh.rotation.y += 0.004+this.id*0.0005; this.mesh.position.y = this.position.y + Math.sin(time + this.id*10)*0.07; } } } // --- Setup multiple avatars --- const avatars = []; const avatarCount = 7; const radius = 4.3; // Position avatars in rough circle for(let i=0; i<avatarCount; i++){ const angle = (i / avatarCount) * Math.PI * 2; const pos = new THREE.Vector3(Math.cos(angle)*radius, 0, Math.sin(angle)*radius); avatars.push(new GlitchyAvatar(i, pos)); } // --- Raycasting for interaction --- const raycaster = new THREE.Raycaster(); const pointer = new THREE.Vector2(); let selectedAvatar = null; let dragStart = null; function onPointerDown(event){ pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = - (event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects(avatars.map(a => a.mesh)); if(intersects.length > 0){ selectedAvatar = avatars.find(a => a.mesh === intersects[0].object); if(selectedAvatar && !selectedAvatar.isBroken){ dragStart = {x: event.clientX, y: event.clientY}; selectedAvatar.onClick(); } } } function onPointerMove(event){ if(selectedAvatar && dragStart){ const movementX = event.clientX - dragStart.x; const movementY = event.clientY - dragStart.y; dragStart.x = event.clientX; dragStart.y = event.clientY; selectedAvatar.onDrag(movementX, movementY); } } function onPointerUp(event){ selectedAvatar = null; dragStart = null; } window.addEventListener('pointerdown', onPointerDown); window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerup', onPointerUp); // --- Animation Loop --- function animate(){ requestAnimationFrame(animate); avatars.forEach(av => av.update()); controls.update(); renderer.render(scene, camera); } animate(); // Window resize handling window.addEventListener('resize', () => { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Start Tone on user interaction (some browsers require implicit start) window.addEventListener('click', async function startTone(){ if(Tone.context.state !== 'running'){ await Tone.start(); } window.removeEventListener('click', startTone); }); </script> </body> </html> ```
# Glitchy Avatar Soundscape I'll create an interactive experience with glitchy abstract avatars that react to interactions with unpredictable animations and sound. Each avatar uses Three.js for visual effects and Tone.js for unique audio reactions. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Glitchy Avatar Soundscape</title> <style> body { margin: 0; overflow: hidden; background: #000; font-family: 'Courier New', monospace; } #scene-container { position: relative; } #controls { position: absolute; bottom: 20px; left: 20px; color: #0f0; background: rgba(0, 20, 0, 0.7); padding: 15px; border-radius: 8px; border: 1px solid #0f0; } #title { position: absolute; top: 20px; left: 0; width: 100%; text-align: center; color: #0f0; font-size: 2.2em; text-shadow: 0 0 10px #0f0; letter-spacing: 3px; text-transform: uppercase; } button { background: rgba(0, 30, 0, 0.8); color: #0f0; border: 1px solid #0f0; padding: 10px 15px; margin: 8px 5px; border-radius: 4px; cursor: pointer; font-family: 'Courier New', monospace; transition: all 0.3s; } button:hover { background: rgba(0, 200, 0, 0.3); } p { margin: 5px 0; font-size: 1.1em; } .glitch-text { color: #f0f; animation: glitch 1s infinite; } @keyframes glitch { 0% { text-shadow: 3px 0 0px #0f0, -3px 0 0px #ff00c7; } 50% { text-shadow: -4px 0 0px #0f0, 4px 0 0px #ff00c7; } 100% { text-shadow: 2px 0 0px #0f0, -2px 0 0px #ff00c7; } } </style> </head> <body> <div id="title">GLITCHY AVATAR SOUNDSCAPE</div> <div id="scene-container"> <div id="controls"> <p>Click/drag avatars to create chaos</p> <p>Each reacts with unique visuals & sounds</p> <button id="add-btn">ADD AVATAR</button> <button id="break-all">BREAK ALL</button> <button id="mutate">MUTATE WORLD</button> </div> <canvas id="scene"></canvas> </div> <!-- Libraries --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.37/Tone.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.7.1/dist/gsap.min.js"></script> <script> // Extend Three.js with glitch materials and effects THREE.GlitchShader = { uniforms: { "tDiffuse": { value: null }, "time": { value: 0.0 }, "factor": { value: 0.1 }, "resolution": { value: new THREE.Vector2(window.innerWidth, window.innerHeight) } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, fragmentShader: ` uniform sampler2D tDiffuse; uniform float time; uniform float factor; uniform vec2 resolution; varying vec2 vUv; float rand(vec2 c){ return fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453); } void main() { vec2 uv = vUv; vec4 color = texture2D(tDiffuse, uv); // Random glitches if (rand(vec2(time * factor, uv.y)) > 0.95) { float dist = rand(vec2(time * factor, uv.x)) * 0.05; uv.x += dist * factor * 0.5; uv.y += dist * factor * 0.5; color = texture2D(tDiffuse, uv); color.r *= 0.4 + rand(uv) * 0.6; color.g *= 0.8 + rand(uv.yx) * 0.2; } // Color channel offset float offset = factor * sin(time * 0.01) * 0.01; if (rand(uv) > 0.9) { color.r = texture2D(tDiffuse, uv + vec2(offset, 0.0)).r; color.g = texture2D(tDiffuse, uv + vec2(-offset, -offset)).g; color.b = texture2D(tDiffuse, uv + vec2(0.0, offset)).b; } gl_FragColor = color; } ` }; // Main application class GlitchyScene { constructor() { // Setup this.initScene(); this.initLights(); this.initSoundEngine(); this.createAvatars(); this.setupEventListeners(); this.setupGlitchEffect(); // Start this.animate(); } initScene() { // Renderer this.renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('scene'), antialias: true, alpha: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(window.devicePixelRatio); // Scene this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x001003); // Camera this.camera = new THREE.PerspectiveCamera(75, window.innerWidth/innerHeight, 0.1, 1000); this.camera.position.z = 12; // Controls for dragging this.mouse = new THREE.Vector2(); this.raycaster = new THREE.Raycaster(); this.intersected = null; this.selected = null; // Object holders this.avatars = []; this.idleTime = 0; // Particle systems for shattering effect this.particleSprites = new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAcklEQVQYV2O0tbX9TwMAA5GKCgYFBGxzcHBj4eK6wMLGdhyIBZEsNhk9vbtn2djYz2LIc3NzS2hqan6CKYTYCNf5//9/DiDXDowVGRm5gOgcSFjpfjB+/vy51blz5zSQNcTWbnDB/PnzofIdyMdQBgAwWRkPFRSUEQAAAABJRU5ErkJggg=='); } initLights() { // Ambient light const ambient = new THREE.AmbientLight(0x222222); this.scene.add(ambient); // Directional lights const light = new THREE.DirectionalLight(0x00ffcc, 1.5); light.position.set(1, 1, 1); this.scene.add(light); const light2 = new THREE.DirectionalLight(0xff00aa, 0.8); light2.position.set(-1, -1, 1); this.scene.add(light2); // Point lights const pointLight = new THREE.PointLight(0x00e1ff, 1.2, 30); pointLight.position.set(0, 10, 2); this.scene.add(pointLight); } initSoundEngine() { // Set up Tone.js synthesizers this.synths = { synth1: new Tone.PolySynth({ oscillator: { type: 'fatsquare4', count: 4, spread: 30 }, envelope: { attack: 0.02, decay: 0.1, sustain: 0.3, release: 0.4 } }).toDestination(), synth2: new Tone.MonoSynth({ oscillator: { type: 'fmsquare', modulationType: 'sawtooth', modulationIndex: 5, harmonicity: 1.5 }, envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 1.2 }, filterEnvelope: { attack: 0.01, decay: 0.1, sustain: 0.5, release: 0.8, baseFrequency: 300, octaves: 4 } }).toDestination(), synth3: new Tone.MetalSynth({ frequency: 150, envelope: { attack: 0.001, decay: 0.1, release: 0.1 }, harmonicity: 8.5, modulationIndex: particlegravity, resonance: 400, octaves: 1.5 }).toDestination() }; // Effects chain this.compressor = new Tone.Compressor(-20, 4).toDestination(); this.pingPong = new Tone.PingPongDelay("8n", 0.2).connect(this.compressor); this.pitchShift = new Tone.PitchShift(-6).connect(this.pingPong); // Connect synths to effects Object.values(this.synths).forEach(synth => { synth.connect(this.pitchShift); }); } createAvatars(count = 5) { for (let i = 0; i < count; i++) { // Random position const x = (Math.random() - 0.5) * 20; const y = (Math.random() - 0.5) * 10; const z = (Math.random() - 0.5) * 12; // Random shape const shapeTypes = ['box', 'sphere', 'cone', 'torus']; const shapeType = shapeTypes[Math.floor(Math.random() * shapeTypes.length)]; // Create avatar object this.addAvatar(x, y, z, shapeType); } // Schedule random break events setTimeout(() => { if (this.avatars.length > 0) { const randomAvatar = this.avatars[Math.floor(Math.random() * this.avatars.length)]; this.breakAvatar(randomAvatar); } }, Math.random() * 10000 + 5000); } addAvatar(x, y, z, shapeType) { const colors = [0xff33aa, 0x38ffcc, 0x9a1eff, 0xffd83d, 0x42ffdb]; const mat = new THREE.MeshStandardMaterial({ color: colors[Math.floor(Math.random() * colors.length)], metalness: 0.2, roughness: 0.8, emissive: colors[Math.floor(Math.random() * colors.length)], emissiveIntensity: 0.3, wireframe: Math.random() > 0.7 }); let geometry; switch(shapeType) { case 'box': geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5); break; case 'sphere': geometry = new THREE.SphereGeometry(0.9, 20, 20); break; case 'cone': geometry = new THREE.ConeGeometry(1, 1.8, 32); break; case 'torus': geometry = new THREE.TorusGeometry(1, 0.4, 12, 24); break; } const mesh = new THREE.Mesh(geometry, mat); mesh.position.set(x, y, z); mesh.rotation.set( Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2 ); // Physics-like properties for movement mesh.velocity = new THREE.Vector3( (Math.random() - 0.5) * 0.02, (Math.random() - 0.5) * 0.02, (Math.random() - 0.5) * 0.02 ); mesh.originalPos = new THREE.Vector3(x, y, z); mesh.rotationSpeed = new THREE.Vector3( (Math.random() - 0.5) * 0.01, (Math.random() - 0.5) * 0.01, (Math.random() - 0.5) * 0.01 ); // Assign avatar const avatar = { mesh: mesh, shapeType: shapeType, synthType: ['synth1', 'synth2', 'synth3'][Math.floor(Math.random() * 3)], soundType: ['synth', 'break', 'hit', 'recombine'][Math.floor(Math.random() * 4)], broken: false, breaking: false, originalScale: mesh.scale.clone(), particleSystem: null }; this.scene.add(mesh); this.avatars.push(avatar); // Play a unique creation sound this.playAvatarSound(avatar, 'synth'); return avatar; } breakAvatar(avatar) { if (avatar.broken || avatar.breaking) return; avatar.breaking = true; this.playAvatarSound(avatar, 'break'); // Create particles for the shatter effect const particles = new THREE.BufferGeometry(); const count = 150; const positions = new Float32Array(count * 3); for (let i = 0; i < count * 3; i += 3) { positions[i] = (Math.random() - 0.5) * 2; positions[i+1] = (Math.random() - 0.5) * 2; positions[i+2] = (Math.random() - 0.5) * 2; } particles.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const particleMat = new THREE.PointsMaterial({ color: avatar.mesh.material.color, size: 0.1, map: this.particleSprites, alphaTest: 0.5, transparent: true, blending: THREE.AdditiveBlending }); const particleSystem = new THREE.Points(particles, particleMat); particleSystem.position.copy(avatar.mesh.position); particleSystem.scale.copy(avatar.mesh.originalScale); // Store particles in avatar avatar.particleSystem = particleSystem; avatar.broken = true; // Hide original mesh avatar.mesh.visible = false; this.scene.add(particleSystem); // Animate particles outwards const positionsArray = particleSystem.geometry.attributes.position.array; const originalPositions = positionsArray.slice(); avatar.particleVelocities = Array.from({ length: count }, () => new THREE.Vector3( (Math.random() - 0.5) * 0.05, (Math.random() - 0.5) * 0.05, (Math.random() - 0.5) * 0.05 ) ); // Start recombination after delay setTimeout(() => { avatar.broken = false; // Create transition animations const positions = particleSystem.geometry.attributes.position; const originalPosCount = originalPositions.length; const animateRecombine = () => { if (avatar.broken) return; let allReturned = true; for (let i = 0; i < originalPosCount; i += 3) { const index = i/3; // Move back towards center (original positions) positions.array[i] += (originalPositions[i] - positions.array[i]) * 0.05; positions.array[i+1] += (originalPositions[i+1] - positions.array[i+1]) * 0.05; positions.array[i+2] += (originalPositions[i+2] - positions.array[i+2]) * 0.05; // Check if reached original if (Math.abs(positions.array[i] - originalPositions[i]) > 0.01 || Math.abs(positions.array[i+1] - originalPositions[i+1]) > 0.01 || Math.abs(positions.array[i+2] - originalPositions[i+2]) > 0.01) { allReturned = false; } } positions.needsUpdate = true; if (allReturned) { // Recombination complete this.playAvatarSound(avatar, 'recombine'); avatar.breaking = false; avatar.mesh.visible = true; this.scene.remove(particleSystem); avatar.particleSystem = null; } else { requestAnimationFrame(animateRecombine); } }; animateRecombine(); }, 3000); } playAvatarSound(avatar, type) { // Select note based on avatar and sound type const notes = ['C4', 'D#4', 'G4', 'A4', 'B4', 'C5', 'D#5', 'F5', 'G5']; const note = notes[Math.floor(Math.random() * notes.length)]; const duration = Math.random() * 0.5 + 0.1; const velocity = Math.random() * 0.4 + 0.5; switch(type) { case 'synth': // Creation sound this.synths[avatar.synthType].triggerAttackRelease(note, duration, '+0.1', velocity); break; case 'break': // Shattering sound this.synths.synth3.triggerAttackRelease('C3', duration * 2, '+0.1', velocity * 1.2); break; case 'recombine': // Recombination sound this.synths.synth1.triggerAttackRelease([note, notes[Math.floor(Math.random() * 3)]], duration * 1.5, '+0.1', velocity * 0.8); break; case 'hit': // Interaction response this.synths[Math.random() > 0.5 ? 'synth2' : 'synth1'] .triggerAttackRelease(note, duration, '+0.01', velocity); break; } // Add extra audio glitches sometimes if (Math.random() > 0.6) { this.synths.synth3.triggerAttackRelease( Math.random() > 0.5 ? 'G1' : 'D#2', Math.random() * 0.3, '+0.2', Math.random() * 0.4 ); } } setupGlitchEffect() { // Create rendering targets for glitch effect this.glitchTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight); this.glitchMaterial = new THREE.ShaderMaterial({ uniforms: THREE.UniformsUtils.clone(THREE.GlitchShader.uniforms), vertexShader: THREE.GlitchShader.vertexShader, fragmentShader: THREE.GlitchShader.fragmentShader }); this.glitchMaterial.uniforms['tDiffuse'].value = this.glitchTarget.texture; this.glitchMaterial.uniforms['resolution'].value = new THREE.Vector2(window.innerWidth, window.innerHeight); // Create a full-screen plane for post-processing this.quad = new THREE.Mesh( new THREE.PlaneGeometry(2, 2), this.glitchMaterial ); this.scene.add(this.quad); } setupEventListeners() { // Window resize handler window.addEventListener('resize', () => { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); if (this.glitchTarget) { this.glitchTarget.setSize(window.innerWidth, window.innerHeight); } }); // Mouse interaction document.getElementById('scene').addEventListener('mousedown', (e) => { this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1; this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects( this.avatars.map(a => a.mesh) ); if (intersects.length > 0) { this.selected = intersects[0].object; // Find avatar from mesh const avatar = this.avatars.find(a => a.mesh === this.selected); if (!avatar) return; // Play sound on interaction this.playAvatarSound(avatar, 'hit'); // Add unpredictable physics effects avatar.mesh.velocity.x = (Math.random() - 0.5) * 0.2; avatar.mesh.velocity.y = Math.random() * 0.1; avatar.mesh.velocity.z = (Math.random() - 0.5) * 0.1; // Break avatar with random chance if (Math.random() > 0.85) { this.breakAvatar(avatar); } } }); // UI controls document.getElementById('add-btn').addEventListener('click', () => { const x = (Math.random() - 0.5) * 20; const y = 0; const z = (Math.random() - 0.5) * 6; const shapes = ['box', 'sphere', 'cone', 'torus']; const shapeType = shapes[Math.floor(Math.random() * shapes.length)]; this.addAvatar(x, y, z, shapeType); }); document.getElementById('break-all').addEventListener('click', () => { this.avatars.forEach(avatar => { if (!avatar.broken && !avatar.breaking) { this.breakAvatar(avatar); } }); }); document.getElementById('mutate').addEventListener('click', () => { // Randomize sound parameters this.pingPong.delayTime.value = Math.random() * 0.5; this.pingPong.wet.value = Math.random(); this.pitchShift.pitch = Math.random() * 12 - 6; // Add visual mutation this.camera.position.x = (Math.random() - 0.5) * 8; this.camera.position.y = (Math.random() - 0.5) * 4; // Add a random break for fun if (this.avatars.length > 0 && Math.random() > 0.3) { const avatar = this.avatars[Math.floor(Math.random() * this.avatars.length)]; if (!avatar.broken && !avatar.breaking) { this.breakAvatar(avatar); } } }); } animate() { requestAnimationFrame(() => this.animate()); // Update all avatars for (const avatar of this.avatars) { const mesh = avatar.mesh; // Only animate non-broken avatars if (!avatar.broken && !avatar.breaking) { // Rotation mesh.rotation.x += mesh.rotationSpeed.x; mesh.rotation.y += mesh.rotationSpeed.y; mesh.rotation.z += mesh.rotationSpeed.z; // Position with boundaries mesh.position.x += mesh.velocity.x; mesh.position.y += mesh.velocity.y; mesh.position.z += mesh.velocity.z; // Apply boundary constraints if (mesh.position.x > 12 || mesh.position.x < -12) mesh.velocity.x *= -1; if (mesh.position.y > 8 || mesh.position.y < -8) mesh.velocity.y *= -0.8; if (mesh.position.z > 8 || mesh.position.z < -8) mesh.velocity.z *= -1; mesh.position.x = Math.min(Math.max(mesh.position.x, -12), 12); mesh.position.y = Math.min(Math.max(mesh.position.y, -8), 8); mesh.position.z = Math.min(Math.max(mesh.position.z, -8), 8); } // Update particle systems if broken and not recombining if (avatar.broken && avatar.particleSystem && !avatar.breaking) { const positions = avatar.particleSystem.geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { positions[i] += avatar.particleVelocities[i/3].x; positions[i+1] += avatar.particleVelocities[i/3].y; positions[i+2] += avatar.particleVelocities[i/3].z; // Random movement changes if (Math.random() > 0.98) { avatar.particleVelocities[i/3].x += (Math.random() - 0.5) * 0.02; avatar.particleVelocities[i/3].y += (Math.random() - 0.5) * 0.02; avatar.particleVelocities[i/3].z += (Math.random() - 0.5) * 0.02; } } avatar.particleSystem.geometry.attributes.position.needsUpdate = true; } } // Idle behavior - trigger rare random breaks when no interaction this.idleTime++; if (this.idleTime > 1200 && this.avatars.length > 0 && Math.random() > 0.99) { if (this.avatars.length > 0) { const randomAvatar = this.avatars[Math.floor(Math.random() * this.avatars.length)]; if (!randomAvatar.broken && !randomAvatar.breaking) { this.breakAvatar(randomAvatar); } } this.idleTime = 0; } // Update glitch post-processing uniforms this.glitchMaterial.uniforms['time'].value = Date.now() / 1000; this.glitchMaterial.uniforms['factor'].value = 0.1 + Math.sin(Date.now() / 3000) * 0.1; // Render with glitch effect this.renderer.setRenderTarget(this.glitchTarget); this.renderer.render(this.scene, this.camera); this.renderer.setRenderTarget(null); this.renderer.render(this.scene, this.camera); } } // Start when loaded window.addEventListener('load', () => { // Start audio context on user interaction (required by browsers) const startAudio = () => { if (Tone.context.state !== 'running') { Tone.context.resume(); } // Remove event listener after first interaction document.removeEventListener('click', startAudio); document.removeEventListener('touchstart', startAudio); // Create the scene new GlitchyScene(); }; // Mobile compatibility document.addEventListener('click', startAudio); document.addEventListener('touchstart', startAudio); // Trigger on button click for visibility document.getElementById('controls').addEventListener('click', startAudio); }); </script> </body> </html> ``` This interactive experience creates a world of abstract, glitchy avatars that: 1. Each avatar has a unique geometric shape with random colors and movements 2. Interactions trigger unpredictable visual reactions and synth sounds 3. Avatars periodically shatter into pixel shards and recombine 4. All UI elements are rendered with a cyberpunk glitch aesthetic The application uses Three.js for all 3D rendering and physics-based movement. Tone.js creates the unique sound patches based on each avatar's properties. The glitch visual effects utilize custom shader materials combined with unpredictable motion behaviors. You can interact with the scene by: - Clicking avatars to hear their unique sound and cause unpredictable reactions - Adding more avatars with the ADD button - Shattering all avatars at once with BREAK ALL - Mutating the world to change audio parameters and camera position