<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/controls/OrbitControls.js"></script>
<button id="btnRepeat" style="position: absolute; margin: 5px;">Repeat</button>
body {
overflow: hidden;
margin: 0;
}
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
1,
1000
);
camera.position.set(0, 8, 13);
camera.lookAt(0, 5, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x101010);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", onWindowResize, false);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var rMin = 5;
var rMax = 15;
var hMin = 0;
var hMax = 5;
var centerH = 6;
var delayMax = 1; // seconds
var colors = [
0xd50f30,
0x039bdc,
0xfc5411,
0x1a2683,
0xfbe23d,
0x12923b
];
var c = new THREE.Color();
var MAX_POINTS = 5000;
var pointsCount = 0;
var points = []; //3
var delay = []; //1
var color = []; //3
var turns = []; //1
var turnsMin = 5;
var turnsMax = 10;
var angle = [];
while( pointsCount < MAX_POINTS){
let a = THREE.Math.randFloat(0, Math.PI * 2);
let vec = new THREE.Vector3().setFromCylindricalCoords(
THREE.Math.randFloat(rMin, rMax),
a,
THREE.Math.randFloat(hMin, hMax)
);
points.push(vec);
delay.push(THREE.Math.randFloat(-delayMax,0));
c.set(colors[THREE.Math.randInt(0, colors.length - 1)]);
color.push(c.r, c.g, c.b);
angle.push(a);
turns.push(THREE.Math.randFloat(turnsMin, turnsMax));
pointsCount ++;
}
var pointsTornadoGeom = new THREE.BufferGeometry().setFromPoints(points);
pointsTornadoGeom.setAttribute("color", new THREE.BufferAttribute(new Float32Array(color), 3));
pointsTornadoGeom.setAttribute("delay", new THREE.BufferAttribute(new Float32Array(delay), 1));
pointsTornadoGeom.setAttribute("angle", new THREE.BufferAttribute(new Float32Array(angle), 1));
pointsTornadoGeom.setAttribute("turns", new THREE.BufferAttribute(new Float32Array(turns), 1));
var pointsTornadoMat = new THREE.PointsMaterial(
{
vertexColors: THREE.VertexColors,
size: 0.2,
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/sprites/ball.png", tex => {
tex.center.setScalar(0.5);
tex.rotation = -Math.PI * 0.5;
}),
alphaTest: 0.5
}
);
var uniformsTornado = {
time: {value: 0},
centerH: {value: centerH},
duration: {value: 10 - delayMax}, // seconds
turnsMax: {value: turnsMax}
};
pointsTornadoMat.onBeforeCompile = shader => {
shader.uniforms.time = uniformsTornado.time;
shader.uniforms.centerH = uniformsTornado.centerH;
shader.uniforms.duration = uniformsTornado.duration;
shader.uniforms.turnsMax = uniformsTornado.turnsMax;
shader.vertexShader = `
uniform float time;
uniform float centerH;
uniform float duration;
uniform float turnsMax;
attribute float delay;
attribute float angle;
attribute float turns;
varying float vRatio;
` + shader.vertexShader;
shader.vertexShader = shader.vertexShader.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
float t = time + delay;
t = t < 0. ? 0. : t;
float tRatio = 0.;
tRatio = clamp( (t * (turnsMax / turns)) / duration, 0., 1.);
vRatio = step(0.01, tRatio) - step(0.999, tRatio);
float durationA = PI * 2. * turns * tRatio;;
float A = angle + durationA;
transformed.x = cos(A);
transformed.z = sin(-A);
float l = length(position.xz);
transformed.xz *= l * mix(1., 0., tRatio);
transformed.y = mix(position.y, centerH, tRatio);
`
);
shader.vertexShader = shader.vertexShader.replace(
`gl_PointSize = size;`,
`gl_PointSize = size * smoothstep(0.01, 0.1, tRatio);`
);
shader.fragmentShader = `
varying float vRatio;
` + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
`#include <clipping_planes_fragment>`,
`
if (floor(vRatio + 0.5) < 1.) discard;
#include <clipping_planes_fragment>`
);
};
var pointsTornado = new THREE.Points(pointsTornadoGeom, pointsTornadoMat);
scene.add(pointsTornado);
var clock = new THREE.Clock();
var t = 0;
btnRepeat.addEventListener("click", event => {t = 0;}, false);
renderer.setAnimationLoop(() => {
t += clock.getDelta();
uniformsTornado.time.value = t;
renderer.render(scene, camera);
});
function onWindowResize() {
var width = window.innerWidth;
var height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.