Built a 3D Morphing Engine with 8,000 Particles! ✨ (Three.js)
Youtube link Click me
In this blog i have share the source code of my youtube video
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Morphing Particle Animation</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
font-family: Arial, sans-serif;
}
#canvas-container {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="canvas-container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 4;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 1);
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Particle system
const particleCount = 8000;
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
const targetPositions = new Float32Array(particleCount * 3);
const startPositions = new Float32Array(particleCount * 3);
// Shape generators
const shapes = {
sphere: () => {
const pos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
const phi = Math.acos(1 - 2 * (i + 0.5) / particleCount);
const theta = Math.PI * (1 + Math.sqrt(5)) * i;
const radius = 1 + Math.random() * 0.2;
pos[i3] = radius * Math.sin(phi) * Math.cos(theta);
pos[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
pos[i3 + 2] = radius * Math.cos(phi);
}
return pos;
},
humanoid: () => {
const pos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
const t = i / particleCount;
if (t < 0.25) { // Head
const phi = Math.random() * Math.PI * 2;
const theta = Math.random() * Math.PI;
const r = 0.3;
pos[i3] = r * Math.sin(theta) * Math.cos(phi);
pos[i3 + 1] = 1.2 + r * Math.cos(theta);
pos[i3 + 2] = r * Math.sin(theta) * Math.sin(phi);
} else if (t < 0.5) { // Body
const angle = Math.random() * Math.PI * 2;
const radius = Math.random() * 0.3;
const height = Math.random() * 1.0;
pos[i3] = radius * Math.cos(angle);
pos[i3 + 1] = 0.2 + height * 0.8;
pos[i3 + 2] = radius * Math.sin(angle);
} else if (t < 0.7) { // Arms
const side = Math.random() < 0.5 ? -1 : 1;
const armLength = Math.random();
pos[i3] = side * (0.3 + armLength * 0.6);
pos[i3 + 1] = 0.8 - armLength * 0.4;
pos[i3 + 2] = (Math.random() - 0.5) * 0.2;
} else { // Legs
const side = Math.random() < 0.5 ? -1 : 1;
const legLength = Math.random();
pos[i3] = side * 0.15;
pos[i3 + 1] = 0.2 - legLength * 0.8;
pos[i3 + 2] = (Math.random() - 0.5) * 0.2;
}
}
return pos;
},
torus: () => {
const pos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
const u = (i / particleCount) * Math.PI * 2;
const v = ((i * 17) % particleCount / particleCount) * Math.PI * 2;
const R = 1;
const r = 0.4;
pos[i3] = (R + r * Math.cos(v)) * Math.cos(u);
pos[i3 + 1] = (R + r * Math.cos(v)) * Math.sin(u);
pos[i3 + 2] = r * Math.sin(v);
}
return pos;
},
helix: () => {
const pos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
const t = (i / particleCount) * Math.PI * 8;
const radius = 0.5 + Math.random() * 0.2;
pos[i3] = radius * Math.cos(t);
pos[i3 + 1] = (i / particleCount - 0.5) * 4;
pos[i3 + 2] = radius * Math.sin(t);
}
return pos;
},
cube: () => {
const pos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
pos[i3] = (Math.random() - 0.5) * 2;
pos[i3 + 1] = (Math.random() - 0.5) * 2;
pos[i3 + 2] = (Math.random() - 0.5) * 2;
}
return pos;
}
};
const shapeNames = Object.keys(shapes);
// Initialize particles
const initialShape = shapes.sphere();
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
positions[i3] = initialShape[i3];
positions[i3 + 1] = initialShape[i3 + 1];
positions[i3 + 2] = initialShape[i3 + 2];
startPositions[i3] = positions[i3];
startPositions[i3 + 1] = positions[i3 + 1];
startPositions[i3 + 2] = positions[i3 + 2];
// Blue-ish colors with variation
colors[i3] = 0.2 + Math.random() * 0.3;
colors[i3 + 1] = 0.4 + Math.random() * 0.4;
colors[i3 + 2] = 0.8 + Math.random() * 0.2;
sizes[i] = Math.random() * 2 + 1;
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const material = new THREE.PointsMaterial({
size: 0.015,
vertexColors: true,
transparent: true,
opacity: 0.9,
sizeAttenuation: true,
blending: THREE.AdditiveBlending
});
const particles = new THREE.Points(geometry, material);
scene.add(particles);
// Animation state
let time = 0;
let shapeIndex = 0;
let lastShapeChange = 0;
const transitionDuration = 3; // seconds
const holdDuration = 2; // seconds
// Set initial target
const firstTarget = shapes[shapeNames[1]]();
for (let i = 0; i < particleCount * 3; i++) {
targetPositions[i] = firstTarget[i];
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
time += 0.016; // ~60fps
const elapsed = time - lastShapeChange;
const totalCycle = transitionDuration + holdDuration;
// Check if we need to switch to next shape
if (elapsed > totalCycle) {
lastShapeChange = time;
shapeIndex = (shapeIndex + 1) % shapeNames.length;
// Update start positions
for (let i = 0; i < particleCount * 3; i++) {
startPositions[i] = positions[i];
}
// Set new target
const nextShape = shapes[shapeNames[shapeIndex]]();
for (let i = 0; i < particleCount * 3; i++) {
targetPositions[i] = nextShape[i];
}
}
// Calculate morph progress (0 to 1)
if (elapsed < transitionDuration) {
const progress = elapsed / transitionDuration;
// Smooth easing
const eased = progress < 0.5
? 2 * progress * progress
: 1-Math.pow(-2 * progress + 2, 2) /2
//Interpolate positions
for (let i=0; i < particleCount * 3; i++) {
positions[i] = startPositions[i] + (targetPositions[i] - startPositions[i]) * eased;
}
geometry.attributes.position.needsUpdate = true;
}
//Gentle rotation
particles.rotation.y += 0.002;
particles.rotation.x = Math.sin(time * 0.1) * 0.1;
renderer.render(scene, camera);
}
animate();
//Handle resizing
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>

Comments
Post a Comment