238 lines
8.5 KiB
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>
|