var requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.setTimeout(callback, 1000 / 60);
var Delaunay = (function() {
function Node(x, y, id) {
this.id = !isNaN(id) && isFinite(id) ? id : null;
return (dx < 0 ? -dx : dx) < 0.0001 && (dy < 0 ? -dy : dy) < 0.0001;
return '(x: ' + this.x + ', y: ' + this.y + ')';
var na = this.nodes, nb = edge.nodes;
var na0 = na[0], na1 = na[1], nb0 = nb[0], nb1 = nb[1];
return (na0.eq(nb0) && na1.eq(nb1)) || (na0.eq(nb1) && na1.eq(nb0));
function Triangle(p0, p1, p2,p3) {
this.nodes = [p0, p1, p2, p3];
this.edges = [new Edge(p0, p1,p3,p2), new Edge(p1, p2,p0,p3), new Edge(p2, p0,p3,p1)];
this._createCircumscribedCircle();
var nodes, id0, id1, id2, id3 ;
if (id0 !== null && id1 !== null && id2 !== null && id3 !== null) {
this.id = [id0, id1, id2, id3 ].sort().join('_');
_createCircumscribedCircle: function() {
var nodes, p0, p1, p2,p3,
ax = p1.x - p0.x, ay = p1.y - p0.y;
bx = p2.x - p0.x, by = p2.y - p0.y;
c = 2 * (ax * by - ay * bx);
t = (p1.x * p1.x - p0.x * p0.x + p1.y * p1.y - p0.y * p0.y);
u = (p2.x * p2.x - p0.x * p0.x + p2.y * p2.y - p0.y * p0.y);
if (!this._circle) this._circle = {};
circle.x = ((p2.y - p0.y) * t + (p0.y - p1.y) * u) / c;
circle.y = ((p0.x - p2.x) * t + (p1.x - p0.x) * u) / c;
circle.radiusSq = dx * dx + dy * dy;
circleContains: function(p) {
var circle, dx, dy, distSq;
distSq = dx * dx + dy * dy;
return distSq < circle.radiusSq;
function Delaunay(width, height) {
this.width = width = height ;
this.height = height = width ;
p1 = new Node(this.width, 0),
p2 = new Node(this.width, this.height),
p3 = new Node(0, this.height);
new Triangle(p0, p1, p2),
new Triangle(p0, p2, p3),
multipleInsert: function(m) {
for (var i = 1, len = m.length; i < len; i++) {
var triangles = this._triangles,
for (ilen = triangles.length, i = 0; i < ilen; i++) {
if (t.circleContains(p)) {
edges.push(t.edges[0], t.edges[1], t.edges[2]);
edgesLoop: for (ilen = edges.length, i = 0; i < ilen; i++) {
for (jlen = polygon.length, j = 0; j < jlen; j++) {
if (edge.eq(polygon[j])) {
for (ilen = polygon.length, i = 0; i < ilen; i++) {
temps.push(new Triangle(edge.nodes[0], edge.nodes[1], p));
getTriangles: function() {
return this._triangles.slice();
var Particle = (function(Node) {
getId = function() { return currentId++; };
function Particle(x, y, z) {
Node.call(this, x, y, getId());
Particle.prototype = new Node();
var BACKGROUND_COLOR = '#eff1ea',
'#666', '#F5F5F5', '#ccc', '#666', '#F5F5F5', '#ccc','#999', '#F5F5F5', '#ccc','#333', '#F5F5F5', '#fff','#333'
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiVFBQ2M8ABPfv33dkQAJMDDgA4////7FK4NRBugRAgAEAXhEHBXvZgh4AAAAASUVORK5CYII%3D',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB1JREFUeNpiUFBQ2P///38GEGYEETDAxIAEAAIMACllChoZz6oRAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1JREFUeNpiVFBQ2M/AwODIgAAgPgMTNkGYBIYgSDETNkGYDgxBdKNQ7AIIMABhpgcrohF6AgAAAABJRU5ErkJggg%3D%3D',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADJJREFUeNpi+P//PwMyVlBQ2M/EgAQUFRX3AylHJnQBEJsJXQAEGEFmIAvcv3+fASDAANwmFUHSvnUvAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAGCAYAAADkOT91AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpiUFBQ2P///38GGGZiQAOEBRhB+vCqAAgwAAmADR3HFFILAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEdJREFUeNpiUlBQ2A/EDP///wdjdD4jiAME+4HYERvNxAAByIIofEaQMYqKigy4ANiE+/fvwwVAbGQ+A50ciQ8wMRAAAAEGAKNCOWlhLo6PAAAAAElFTkSuQmCC'
screenWidth, screenHeight, screenMinSize,
gui, control, maxSpeedCtl, minSpeedCtl,
screenWidth = canvas.width = window.innerWidth;
screenHeight = canvas.height = window.innerHeight;
screenMinSize = Math.min(screenWidth, screenHeight);
centerX = screenWidth * 0.5;
centerY = screenHeight * 0.5;
context = canvas.getContext('2d');
context.strokeStyle = LINE_COLOR;
context.lineCap = context.lineJoin = 'round';
delaunay.width = screenWidth;
delaunay.height = screenHeight;
function addParticle(x, y) {
if (particles.length >= control.maxNum) {
var p = new Particle(x, y),
l = Math.random() * (control.maxSpeed - control.minSpeed) + control.minSpeed,
a = Math.random() * Math.PI * 2;
canvas = document.getElementById('c');
window.addEventListener('resize', resize, false);
mouse.x = screenWidth * 0.5;
mouse.y = screenHeight * 0.5;
delaunay = new Delaunay(screenWidth, screenHeight);
for (i = 0, len = control.maxNum; i < len; i++) {
addParticle(Math.random() * screenMinSize + centerX - screenMinSize * 0.5, Math.random() * screenMinSize + centerY - screenMinSize * 0.5);
var TWO_PI = Math.PI * 2,
now = new Date().getTime(),
triangles, t, id, p0, p1, p2,
if (now - time > control.spawnTime) {
addParticle(mouse.x, mouse.y);
ctx.fillStyle = BACKGROUND_COLOR;
ctx.fillRect(0, 0, screenWidth, screenHeight);
ctx.fillStyle = backgroundPattern;
ctx.fillRect(0, 0, screenWidth, screenHeight);
for (len = particles.length, i = 0; i < len; i++) {
if (p.vx < 0) p.vx *= -1;
if (p.vx > 0) p.vx *= -1;
if (p.vy < 0) p.vy *= -1;
if (p.vy > 0) p.vy *= -1;
triangles = delaunay.multipleInsert(particles).getTriangles();
for (len = triangles.length, i = 0; i < len; i++) {
if (id === null) continue;
ct[id] = FILL_COLORS[colorIndex];
colorIndex = (colorIndex + 1) % cl;
pt[id] = patterns[patternIndex];
patternIndex = (patternIndex + 1) % pl;
ctx.translate(p0.x, p0.y);
ctx.rotate(Math.atan2(p0.y - p1.y, p0.x - p1.x));
requestAnimationFrame(loop);
gui.add(control, 'maxNum', 0, 50).name('Max Num');
maxSpeedCtl = gui.add(control, 'maxSpeed', 0, 5).name('Max Speed').onChange(function() {
if (control.minSpeed > control.maxSpeed)
control.minSpeed = control.maxSpeed;
minSpeedCtl.updateDisplay();
minSpeedCtl = gui.add(control, 'minSpeed', 0, 5).name('Min Speed').onChange(function() {
if (control.minSpeed > control.maxSpeed)
control.maxSpeed = control.minSpeed;
maxSpeedCtl.updateDisplay();
gui.add(control, 'spawnTime', 50, 1000).name('Spawn Time');
count = PATTERNS_URL.length;
patterns.push(context.createPattern(e.target, 'repeat'));
backgroundPattern = patterns[Math.floor(patterns.length * Math.random())];
patterns.push('rgba(0, 0, 0, 0)');
canvas.addEventListener('mousemove', mouseMove, false);
time = new Date().getTime();
for (i = 0, len = PATTERNS_URL.length; i < len; i++) {
img.addEventListener('load', onLoad, false);
img.src = PATTERNS_URL[i];