<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3D Wave Frequency Morphing</title>
<style>
body {
margin: 0;
background-color: #000000;
font-family: sans-serif;
color: #c0c0c0;
}
#animationWindow {
position: relative;
width: 1000px;
height: 1000px;
background-color: rgba(0, 0, 0, 0.8);
border: 1px solid #c0c0c0;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div id="animationWindow">
<canvas id="bgCanvas"></canvas>
</div>
<script>
const animationWindow = document.getElementById('animationWindow');
const canvas = document.getElementById('bgCanvas');
const ctx = canvas.getContext('2d');
let particles = [];
let shapes = [];
let currentShapeIndex, targetShapeIndex;
let currentShapeData, targetShapeData;
let progress = 0;
let morphSpeed = 0.004;
let angleX = 0;
let angleY = 0;
let particleCount = 25000;
class Particle {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.projectedX = 0;
this.projectedY = 0;
this.size = 1;
}
project(x, y, z) {
const fov = 1000;
const dist = fov + z;
this.projectedX = (x * fov) / dist + canvas.width / 2;
this.projectedY = (y * fov) / dist + canvas.height / 2;
this.size = Math.max(0, (1 - z / 1000) * 1.5);
}
draw() {
if (this.projectedX < 0 || this.projectedX > canvas.width || this.projectedY < 0 || this.projectedY > canvas.height) {
return;
}
ctx.beginPath();
ctx.arc(this.projectedX, this.projectedY, this.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(192, 192, 192, ${this.size / 1.5})`;
ctx.fill();
}
}
// --- Shape Generation: Wave Frequencies ---
function generateShapes() {
shapes.push(createWaveFrequency1); // Low frequency
shapes.push(createWaveFrequency2); // Medium frequency (radial)
shapes.push(createWaveFrequency3); // High frequency (radial)
shapes.push(createWaveFrequency4); // Interference pattern
shapes.push(createWaveFrequency5); // High-frequency grid pattern
}
// Base function to create a grid of particles
function createParticleGrid() {
const tempParticles = [];
const size = 800;
const step = Math.sqrt((size * size) / particleCount);
for (let x = -size / 2; x < size / 2; x += step) {
for (let z = -size / 2; z < size / 2; z += step) {
tempParticles.push({x: x, y: 0, z: z});
}
}
return tempParticles;
}
// Frequency 1: A very simple, low-frequency planar wave.
function createWaveFrequency1() {
const tempParticles = createParticleGrid();
tempParticles.forEach(p => {
p.y = Math.sin(p.x * 0.015) * 100;
});
return tempParticles;
}
// Frequency 2: A medium-frequency wave radiating from the center.
function createWaveFrequency2() {
const tempParticles = createParticleGrid();
tempParticles.forEach(p => {
const distance = Math.sqrt(p.x * p.x + p.z * p.z);
p.y = Math.sin(distance * 0.03) * 100;
});
return tempParticles;
}
// Frequency 3: A higher-frequency wave radiating from the center.
function createWaveFrequency3() {
const tempParticles = createParticleGrid();
tempParticles.forEach(p => {
const distance = Math.sqrt(p.x * p.x + p.z * p.z);
p.y = Math.sin(distance * 0.06) * 100;
});
return tempParticles;
}
// Frequency 4: An interference pattern created by two waves crossing.
function createWaveFrequency4() {
const tempParticles = createParticleGrid();
tempParticles.forEach(p => {
p.y = (Math.sin(p.x * 0.05) + Math.cos(p.z * 0.05)) * 80;
});
return tempParticles;
}
// Frequency 5: A very high-frequency, complex grid pattern.
function createWaveFrequency5() {
const tempParticles = createParticleGrid();
tempParticles.forEach(p => {
p.y = (Math.sin(p.x * 0.1) * Math.sin(p.z * 0.1)) * 100;
});
return tempParticles;
}
// --- Core Logic ---
function morph() {
progress += morphSpeed;
if (progress >= 1) {
progress = 0;
currentShapeIndex = targetShapeIndex;
do {
targetShapeIndex = Math.floor(Math.random() * shapes.length);
} while (targetShapeIndex === currentShapeIndex);
currentShapeData = shapes[currentShapeIndex]();
targetShapeData = shapes[targetShapeIndex]();
}
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
if (currentShapeData[i] && targetShapeData[i]) {
const start = currentShapeData[i];
const end = targetShapeData[i];
p.x = start.x + (end.x - start.x) * progress;
p.y = start.y + (end.y - start.y) * progress;
p.z = start.z + (end.z - start.z) * progress;
}
}
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
morph();
angleX += 0.001;
angleY += 0.002;
const cosX = Math.cos(angleX);
const sinX = Math.sin(angleX);
const cosY = Math.cos(angleY);
const sinY = Math.sin(angleY);
particles.forEach(p => {
const x1 = p.x * cosY - p.z * sinY;
const z1 = p.x * sinY + p.z * cosY;
const y2 = p.y * cosX - z1 * sinX;
const z2 = p.y * sinX + z1 * cosX;
p.project(x1, y2, z2);
p.draw();
});
}
function initialize() {
canvas.width = animationWindow.clientWidth;
canvas.height = animationWindow.clientHeight;
particles = [];
shapes = [];
generateShapes();
currentShapeIndex = Math.floor(Math.random() * shapes.length);
do {
targetShapeIndex = Math.floor(Math.random() * shapes.length);
} while (targetShapeIndex === currentShapeIndex);
currentShapeData = shapes[currentShapeIndex]();
targetShapeData = shapes[targetShapeIndex]();
// Ensure particle array matches data length
const initialData = shapes[currentShapeIndex]();
particleCount = initialData.length;
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle(initialData[i].x, initialData[i].y, initialData[i].z));
}
}
window.onload = () => {
initialize();
animate();
};
</script>
</body>
</html>