Skip to content

Instantly share code, notes, and snippets.

@eupston
Created May 9, 2023 15:09
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save eupston/b613f43995131173b865cd7a53dd2e2b to your computer and use it in GitHub Desktop.
Save eupston/b613f43995131173b865cd7a53dd2e2b to your computer and use it in GitHub Desktop.
morphing particle effect
<!DOCTYPE html>
<html>
<head>
<title>Three.js Morph</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/build/three.min.js"></script>
<script>
var renderer, scene, camera, particle;
var morphs = [];
var numParticles = 10000;
var counter = 0;
var direction = 1;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var moveDistance = 100; // Distance to move the closest particle
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 500;
var sphereGeometry = new THREE.SphereBufferGeometry(200, 50, 50);
var torusGeometry = new THREE.TorusGeometry(200, 60, 40, 100);
var vertices = sphereGeometry.attributes.position.array;
var torusVertices = torusGeometry.attributes.position.array;
var geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
var material = new THREE.PointsMaterial({
color: 0xFFFFFF,
size: 2,
opacity: 1,
transparent: false
});
particle = new THREE.Points(geometry, material);
scene.add(particle);
for (var i = 0; i < numParticles; i++) {
morphs[i] = {
position: new THREE.Vector3().fromBufferAttribute(particle.geometry.attributes.position, i),
targetPosition: new THREE.Vector3(torusVertices[i*3], torusVertices[i*3+1], torusVertices[i*3+2])
};
}
// Create a transparent plane for the intersection tests
var geometry = new THREE.PlaneGeometry(2000, 2000, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0 });
plane = new THREE.Mesh(geometry, material);
scene.add(plane);
window.addEventListener('mousemove', onMouseMove, false);
}
function onMouseMove(event) {
// Calculate mouse position in normalized device coordinates (-1 to +1) for both components
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function generateSprite() {
var canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
var context = canvas.getContext('2d');
var gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop( 0, 'rgba(255,255,255,1)' );
gradient.addColorStop( 0.2, 'rgba(0,255,255,1)' );
gradient.addColorStop( 0.4, 'rgba(0,0,64,1)' );
gradient.addColorStop( 1, 'rgba(0,0,0,1)' );
context.fillStyle = gradient;
context.fillRect( 0, 0, canvas.width, canvas.height );
var texture = new THREE.CanvasTexture(canvas);
return texture;
}
function animate() {
requestAnimationFrame( animate );
render();
}
var counter = 0;
var direction = 1;
function animate() {
requestAnimationFrame( animate );
counter++;
// Switch direction every 200 frames
if (counter % 1000 === 0) {
direction *= -1;
}
render();
}
function render() {
var positions = particle.geometry.attributes.position;
// Update the raycaster with the mouse position
raycaster.setFromCamera(mouse, camera);
var positions = particle.geometry.attributes.position;
// Update the raycaster with the mouse position
raycaster.setFromCamera(mouse, camera);
// Calculate objects intersecting the raycaster's picking ray
var intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
// Find the closest particle to the intersection point
var closestParticle = null;
var closestDistance = Infinity;
var intersectionPoint = intersects[0].point;
for (var i = 0; i < numParticles; i++) {
var particlePosition = new THREE.Vector3(
positions.array[i*3],
positions.array[i*3 + 1],
positions.array[i*3 + 2]
);
var distance = particlePosition.distanceTo(intersectionPoint);
if (distance < closestDistance) {
closestParticle = i;
closestDistance = distance;
}
}
// Move the closest particle
if (closestParticle !== null) {
positions.array[closestParticle*3 + 2] += moveDistance;
}
}
for (var i = 0; i < numParticles; i++) {
var targetPosition;
// Choose target position based on direction
if (direction === 1) {
targetPosition = morphs[i].targetPosition;
} else {
targetPosition = morphs[i].position;
}
positions.array[i*3] += (targetPosition.x - positions.array[i*3]) * 0.005;
positions.array[i*3 + 1] += (targetPosition.y - positions.array[i*3 + 1]) * 0.005;
positions.array[i*3 + 2] += (targetPosition.z - positions.array[i*3 + 2]) * 0.005;
}
positions.needsUpdate = true;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment