infinite-agents-public/threejs_viz/threejs_viz_8.html

238 lines
8.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js - Particle Wave System</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #000000;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
font-size: 14px;
max-width: 400px;
z-index: 100;
backdrop-filter: blur(10px);
border: 1px solid rgba(100, 200, 255, 0.3);
}
#info h2 {
margin: 0 0 10px 0;
font-size: 18px;
color: #4fc3f7;
}
#info p {
margin: 5px 0;
line-height: 1.5;
}
#info .web-source {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,0.3);
font-size: 12px;
opacity: 0.8;
}
#info .web-source a {
color: #4fc3f7;
text-decoration: none;
}
#info .web-source a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div id="info">
<h2>Particle Wave System</h2>
<p><strong>Technique:</strong> BufferGeometry with Points for dynamic particle waves</p>
<p><strong>Learning:</strong> Creating a grid of particles using BufferGeometry position attributes, then animating them with sine/cosine wave functions for fluid motion. Performance optimized with attribute updates.</p>
<div class="web-source">
<strong>Web Source:</strong><br>
<a href="https://threejs.org/examples/#webgl_points_waves" target="_blank">Three.js Particle Waves Example</a><br>
<em>Applied: BufferGeometry with position attributes, Points/PointsMaterial setup, sine wave animation with needsUpdate flag for smooth 60fps performance with 10,000+ particles</em>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
// Scene setup
let camera, scene, renderer;
let particles;
let particleCount;
const SEPARATION = 100;
const AMPLITUDE = 100;
const WIDTH = 80;
const HEIGHT = 80;
// Animation variables
let count = 0;
init();
animate();
function init() {
// Camera setup
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.set(0, 300, 1000);
camera.lookAt(0, 0, 0);
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0x000000, 1, 10000);
// Renderer (WebGL)
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Create particle wave visualization
createParticleWave();
// Add ambient lighting for particles
const ambientLight = new THREE.AmbientLight(0x222222);
scene.add(ambientLight);
// Add directional light for depth
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Handle resize
window.addEventListener('resize', onWindowResize);
}
function createParticleWave() {
// Calculate total particle count for grid
particleCount = WIDTH * HEIGHT;
// Create BufferGeometry for particles
// This is the key technique from the web source:
// Using BufferGeometry with position attributes for full control
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
// Initialize particle positions in a grid
let i = 0;
for (let ix = 0; ix < WIDTH; ix++) {
for (let iy = 0; iy < HEIGHT; iy++) {
// Position particles in a grid pattern
positions[i * 3] = ix * SEPARATION - ((WIDTH * SEPARATION) / 2);
positions[i * 3 + 1] = 0; // Y will be animated
positions[i * 3 + 2] = iy * SEPARATION - ((HEIGHT * SEPARATION) / 2);
// Create gradient colors from cyan to magenta
const hue = (ix / WIDTH) * 0.5 + 0.5; // 0.5 to 1.0 (cyan to magenta)
const color = new THREE.Color();
color.setHSL(hue, 1.0, 0.5);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
i++;
}
}
// Set position attribute - this is the core of BufferGeometry particle control
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// Create PointsMaterial for particle rendering
// Size controls particle visibility, vertexColors enables per-particle coloring
const material = new THREE.PointsMaterial({
size: 3,
vertexColors: true,
transparent: true,
opacity: 0.8,
sizeAttenuation: true,
blending: THREE.AdditiveBlending
});
// Create Points object - this combines geometry and material
particles = new THREE.Points(geometry, material);
scene.add(particles);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
// Animation logic using learned wave techniques
// This is the key learning: updating particle positions with sine/cosine waves
count += 0.1;
const positions = particles.geometry.attributes.position.array;
// Animate each particle with wave motion
let i = 0;
for (let ix = 0; ix < WIDTH; ix++) {
for (let iy = 0; iy < HEIGHT; iy++) {
// Apply sine wave formula from web research
// Multiple sine waves with different frequencies create complex motion
const wave1 = Math.sin((ix / WIDTH) * Math.PI * 4 + count);
const wave2 = Math.cos((iy / HEIGHT) * Math.PI * 4 + count);
const wave3 = Math.sin((ix / WIDTH + iy / HEIGHT) * Math.PI * 2 + count * 0.5);
// Combine multiple waves for rich, organic motion
positions[i * 3 + 1] = (wave1 + wave2 + wave3) * AMPLITUDE;
i++;
}
}
// CRITICAL: Mark position attribute as needing update
// This is the performance technique learned from research
particles.geometry.attributes.position.needsUpdate = true;
// Rotate entire particle system for dynamic viewing angle
particles.rotation.y = count * 0.02;
// Subtle camera movement for immersion
camera.position.x = Math.sin(count * 0.05) * 200;
camera.position.z = 1000 + Math.cos(count * 0.05) * 200;
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
</script>
</body>
</html>