Last active
February 4, 2019 14:48
-
-
Save johnburnmurdoch/87f26ebff0609915e7c2fb9a535e36af to your computer and use it in GitHub Desktop.
Animating 50,000 points with WebGL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html class="no-js" lang=""> | |
<head> | |
<meta charset="utf-8"> | |
<title>Animating 50,000 points with WebGL</title> | |
<meta name="description" content="Animating 50,000 points with WebGL"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script src=https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.0.0/pixi.js></script> | |
</head> | |
<body> | |
<div id=graphic></div> | |
<script type-'text/javascript'> | |
let nDots = 50000, | |
dots = [], | |
stopAnimation = false, | |
WebGL = true, | |
PR = devicePixelRatio || 1, | |
width = document.body.clientWidth, | |
height = window.innerHeight-16, | |
scaledWidth = width*PR, | |
scaledHeight = height*PR, | |
caontainer, | |
renderer, | |
circleTexture, | |
canvas, | |
context; | |
function phyllotaxis(radius) { | |
var theta = Math.PI * (3 - Math.sqrt(5)); | |
return function(i) { | |
var r = radius * Math.sqrt(i), a = theta * i; | |
return { | |
x: width / 2 + r * Math.cos(a), | |
y: height / 2 + r * Math.sin(a) | |
}; | |
}; | |
} | |
function gridLayout(points, gridWidth) { | |
const pointHeight = pointWidth = ((33*0.16)); | |
const pointsPerRow = Math.floor(width / pointWidth); | |
const numRows = points.length / pointsPerRow; | |
return function(i){ | |
return { | |
x: (pointWidth * (i % pointsPerRow)) + pointWidth, | |
y: (pointHeight * Math.floor(i / pointsPerRow)) + pointHeight | |
}; | |
}; | |
} | |
if(WebGL == true){ | |
// Create the canvas | |
renderer = new PIXI.autoDetectRenderer(width, height, | |
{ | |
backgroundColor: 0xFFFFFF, | |
// transparent: true, | |
antialias: true, | |
resolution: 1 | |
} | |
); | |
// Add to the document | |
document.getElementById('graphic').appendChild(renderer.view); | |
// Create the root of the scene graph | |
container = new PIXI.Container(0xFFFFFF); | |
renderer.render(container); | |
// Create a texture to be used as a Sprite from a white circle png image | |
circleTexture = new PIXI.Texture.fromImage("http://johnburnmurdoch.github.io/images/circle.png"); | |
} | |
if(WebGL == false){ | |
canvas = document.createElement('canvas'); | |
document.getElementById('graphic').appendChild(canvas); | |
canvas.setAttribute('width', scaledWidth); | |
canvas.setAttribute('height', scaledHeight); | |
canvas.style.width = `${width}px`; | |
canvas.style.height = `${height}px`; | |
context = canvas.getContext("2d"); | |
context.scale(PR, PR); | |
context.clearRect(0,0,scaledWidth,scaledHeight); | |
// context.globalCompositeOperation = 'multiply'; | |
} | |
// Get the visible size back to 1000 px | |
// renderer.view.style.width = (width*0.5) + 'px'; | |
// renderer.view.style.height = (height*0.5) + 'px'; | |
const data = Array.from({length: nDots}, (d,i) => i).map(phyllotaxis(2.5)).map((d,i) => { | |
let opacity = 1-(i/nDots); | |
// let opacity = Math.random(); | |
return{ | |
x: d.x, | |
y: d.y, | |
opacity: opacity, | |
color: WebGL ? ((240 << 16) + (0 << 8) + 0) : `hsla(0, 100%, 50%, ${opacity})`, | |
// size: Math.random() * 0.15 | |
size: 0.11 | |
} | |
}); | |
const data1 = Array.from({length: nDots}, (d,i) => i).map(gridLayout(width)).map(d => { | |
let opacity = Math.random(); | |
return{ | |
x: d.x, | |
y: d.y | |
} | |
}); | |
const data2 = Array.from({length: nDots}, d => { | |
return{ | |
x: Math.random()*width, | |
y: Math.random()*height | |
} | |
}); | |
function render() { | |
data.forEach((d,i) => { | |
// Set all characterstics of the circle | |
let dot = new PIXI.Sprite(circleTexture); | |
dot.tint = d.color; | |
dot.blendMode = PIXI.BLEND_MODES.MULTIPLY; | |
dot.anchor.x = 0.5; | |
dot.anchor.y = 0.5; | |
dot.position.x = d.x; | |
dot.position.y = d.y; | |
dot.scale.x = dot.scale.y = d.size; | |
dot.alpha = d.opacity; | |
// Save the circle | |
dots[i] = dot; | |
if(WebGL == false){ | |
context.fillStyle = d.color; | |
context.beginPath(); | |
context.arc(d.x, d.y, (33*0.12)/PR, 0, Math.PI * 2); | |
context.fill(); | |
}else{ | |
// Add to the container | |
container.addChild(dot); | |
} | |
}); | |
if(WebGL == true){ | |
renderer.render(container); | |
} | |
// setTimeout(_ => { | |
// animate(); | |
// }, 1000); | |
} | |
setTimeout(_ => { | |
render(); | |
}, 100); | |
const fps = 10; | |
const tweenTime = 5; | |
const tweenFrames = fps * tweenTime; | |
let animate, | |
frame = 0, | |
progress = 0; | |
animate = function() { | |
// Track progress as proportion of frames completed | |
frame++; | |
// frame = ++frame % tweenFrames; | |
progress = (frame / tweenFrames) || 0; | |
// console.log(frame, progress); | |
if(WebGL == false){ | |
context.clearRect(0,0,scaledWidth,scaledHeight); | |
} | |
for (var i = 0; i < dots.length; i++) { | |
// Trial and testing has taught me that it's best to | |
// do all of these values separately | |
x0 = data[i].x; | |
x1 = data1[i].x; | |
y0 = data[i].y; | |
y1 = data1[i].y; | |
// Interpolate between them | |
xInt = x0 + ((x1 - x0) * progress); | |
yInt = y0 + ((y1 - y0) * progress); | |
if(WebGL == false){ | |
context.fillStyle = data[i].color; | |
context.beginPath(); | |
context.arc(xInt, yInt, (33*0.12)/PR, 0, Math.PI * 2); | |
context.fill(); | |
}else{ | |
dots[i].position.x = xInt; | |
dots[i].position.y = yInt; | |
} | |
} | |
// Cue up next frame then render the updates | |
if(WebGL == true){ | |
renderer.render(container); | |
} | |
if(frame >= (tweenFrames)) stopAnimation = true; | |
if(!stopAnimation) requestAnimationFrame(animate); | |
}; | |
document.getElementsByTagName('body')[0].addEventListener('touchstart', animate); | |
document.getElementsByTagName('body')[0].addEventListener('dblclick', animate); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment