Published
Edited
Jul 27, 2018
Insert cell
Insert cell
showParticles(particlesA)
Insert cell
particlesA = basicParticles(10000)
Insert cell
basicParticles = function(n){
const material = new THREE.ShaderMaterial({
vertexShader: `
#include <common>

attribute float particleID;
uniform float time; // in seconds

float _seed = 0.;
float _seed2 = 0.;
float random() {
_seed2 += 0.1; // auto-increment so each call to random() gives a different result
return rand( vec2( _seed, _seed2 ) );
}

void main() {
_seed = particleID/100. + 2.; // the seed is based upon the id of the particle, so each particle will be different

float t = mod( time, 1. + random()*2. ); // loop every 1 to 3 seconds

vec3 v = vec3( 2.*random() - 1., random()*0.5 + 0.5, 2.*random() - 1. );
vec3 a = vec3( 0., -1., 0.);
vec3 p = v * t + 0.5 * a * t * t; // projectile motion

gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 ); // convert 3D point p into clip space
gl_PointSize = 3.; // particle size in pixels
}`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1., 0., 0., 1.); // red = 1, blue = 0, green = 0, alpha = 1
}`,
uniforms: {
time: { value: 0 } // time in seconds, updated in showParticles()
}
})
let ids = new Float32Array(n)
for (let i = 0; i < n; i++) ids[i] = i
let positions = new Float32Array(3*n).fill(0) // each particle position is (x,y,z) which uses 3 floats
const geometry = new THREE.BufferGeometry()
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)) // unused, but keeps Three.js happy
geometry.addAttribute('particleID', new THREE.BufferAttribute(ids, 1))
return new THREE.Points(geometry, material)
}
Insert cell
Insert cell
Insert cell
particlesB = basicParticles(1)
Insert cell
showParticles(particlesB)
Insert cell
Insert cell
nonRepeatingParticles = function(n){
const material = new THREE.ShaderMaterial({
vertexShader: `
#include <common>

attribute float particleID;
uniform float time; // in seconds

float _seed = 0.;
float _seed2 = 0.;
float random() {
_seed2 += 0.1; // auto-increment so each call to random() gives a different result
return rand( vec2( _seed, _seed2 ) );
}

void main() {
_seed = particleID/100. + 2.; // the seed is based upon the id of the particle, so each particle will be different

// **START NEW CODE**
float lifeTime = 1. + random()*2.; // loop every 1 to 3 seconds, this will be constant for each particle
float t = mod( time, lifeTime );
float loop = floor( time/lifeTime );
_seed = particleID/100. + loop/10000. + 2.; // use a loop based seed for the other random numbers
// **END NEW CODE**

vec3 v = vec3( 2.*random() - 1., random()*0.5 + 0.5, 2.*random() - 1. );
vec3 a = vec3( 0., -1., 0.);
vec3 p = v * t + 0.5 * a * t * t; // projectile motion

gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 ); // convert 3D point p into clip space
gl_PointSize = 3.; // particle size in pixels
}`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1., 0., 0., 1.); // red = 1, blue = 0, green = 0, alpha = 1
}`,
uniforms: {
time: { value: 0 } // time in seconds, updated in showParticles()
}
})
let ids = new Float32Array(n)
for (let i = 0; i < n; i++) ids[i] = i
let positions = new Float32Array(3*n).fill(0) // each particle position is (x,y,z) which uses 3 floats
const geometry = new THREE.BufferGeometry()
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)) // unused, but keeps Three.js happy
geometry.addAttribute('particleID', new THREE.BufferAttribute(ids, 1))
return new THREE.Points(geometry, material)
}
Insert cell
particlesC = nonRepeatingParticles(1)
Insert cell
showParticles(particlesC)
Insert cell
Insert cell
particlesD = nonRepeatingParticles(10000)
Insert cell
showParticles(particlesD)
Insert cell
showParticles = function(particleSystem) {
const renderer = new THREE.WebGLRenderer({antialias: true});
const scene = new THREE.Scene()
const fov = 45;
const aspect = width / height;
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
const particleSystemClone = particleSystem.clone()

camera.position.z = 3;
scene.background = new THREE.Color()

scene.add(particleSystemClone)

renderer.setSize(width, height);
renderer.setPixelRatio(devicePixelRatio);
let time = 10 // start slightly in the future, so our particle system is full

return {
next() {
particleSystemClone.material.uniforms.time.value = time
time += 0.01
renderer.render(scene, camera)
return { done: false, value: renderer.domElement }
},
return() {
renderer.dispose()
}
}
}
Insert cell
height = 300
Insert cell
THREE = {
const THREE = await require("three@0.90.0/build/three.min.js");
if (!THREE.OrbitControls) { // If not previously loaded.
const module = window.module = {exports: undefined};
THREE.OrbitControls = await require("three-orbit-controls@82.1.0/index.js").catch(() => module.exports(THREE));
}
return THREE;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more