Just imagine that this is flocks of birds from another galaxy, and not a smoothly animated quadtree. Click and drag to travel through time.
for(_=',x=Y,B+=w,C+=xV),U*G,f(a+1,$VUG=xY#$z,A#`+.5_)/2_,^eight=h+x*=d+w**w+a.=2c.function(=a){KfillRect([i]:b,F?=inner0,W,H)},clientX(stroke,e[i++]=~~=Math. UmoveTo(F?onmouse*x,w=Style:eUlineTo(b=~~ cos(t,setInterval(){ sin(t/for(b=[e=[g=[d=h=j=N=5]]],i=360;i--;g=)="hsl("+i+",100%,50%)";f=a,d,h,j,v){var w=j-dYv-h,y sqrt(wx*xUzk,Ak,B=d_,C=h_*x,Dl,El,G=x>>;N>a&&(w>>Ymqq-m`nrr-n$D,E#oss-o`puu-p$D,EV)Ua>1&&(i%9||beginPath=g[~~(400*t+y)%360])jvjvdhdh))},down=up=type[7]&&button+1},move?j+=L-:L=}h++,N=top.iter||N,t=new Date/1e3-j/10k13^l17^m=10UqUo3Us/3UnUrUpUuUF=f(i=F=1UK=W=widthWidth,H=hH)="rgba(.05)",36)F0>h?N--:h>140?N++:F,h=0},1e3)';g=/[-^-`#$UVY]/.exec(_);)with(_.split(g))_=join(shift());eval(_)
Zm9yKF89Jyx4PVksQis9dyxDKz14ViksVSpHLGYoYSsxLCRWVUc9eFkjJHosQSNgKy41XykvMl8sXmVpZ2h0Hz1oK3gqHj1kK3cqHSp3HBwrG2EuGj0yGWMuGGZ1bmN0aW9uKBc9F2Epe0sWGGZpbGwVFVJlY3QoFFtpXRM6YhMsRj8SPWlubmVyETAsEBAQVyxIKX0sDxpjbGllbnRYDigYc3Ryb2tlDCxlW2krK109fn4LPU1hdGguCVUYbW92ZVRvKEY/CG9ubW91c2UHKngsdz0GU3R5bGUFOmUTVRhsaW5lVG8oYhM9fn4ECWNvcyh0AyxzZXRJbnRlcnZhbCgXKXsCCXNpbih0LwFmb3IoYj1bZT1bZz1bZD1oPWo9Tj01XV1dLGk9MzYwO2ktLTtnEz0VBSkVBT0iaHNsKCIraSsiLDEwMCUsNTAlKSI7Zj0XYSxkLGgsaix2KXt2YXIgdz1qLWRZdi1oLHkJc3FydCh3G3gqeFV6HWssQR5rLEI9ZF8cLEM9aF8qeCxEHWwsRR5sLEc9eD4+GTtOPmEmJih3Pj4ZWW0bcQZxHC1tYG4bcgZyHC1uJEQsRSNvG3MGcxwtb2BwG3UGdRwtcCRELEVWKVVhPjEmJihpJTl8fBhiZWdpblBhdGgMDAU9Z1t+fig0MDAqdCt5KSUzNjBdKQhqEnYEagt2CGQSaARkC2gpKX0sB2Rvd249B3VwFj0adHlwZVs3XSYmGmJ1dHRvbisxfSwHbW92ZRY/ais9TC0OOhBMPQ59AmgrKyxOPXRvcC5pdGVyfHxOLHQ9bmV3IERhdGUvMWUzLWovMTAQawExM15sATE3Xm0BPTEwVXEDVW8BM1VzAy8zVW4BGVVyA1VwARlVdQNVRj1mKGk9EA9GPTFVFEs9EBBXPRp3aWR0aBFXaWR0aCxIPRpoHxFIHykCFQU9InJnYmEoEBAQLjA1KSIsFA8zNikCRhkwPmg/Ti0tOmg+MTQwP04rKzpGLGg9MH0sMWUzKSc7Zz0vWwEtH14tYCMkVVZZXS8uZXhlYyhfKTspd2l0aChfLnNwbGl0KGcpKV89am9pbihzaGlmdCgpKTtldmFsKF8p
// Initialize some variables and make a hue table to speed up rendering.
for (b = [e = [g = [d = h = j = N = 5]]], i = 360; i--; g[i] = c.fillStyle) c.fillStyle = "hsl(" + i + ",100%,50%)";
// This function was discovered after countless hours of playing around in processing with rendering various types of trees.
f = function (a, d, h, j, v) {
var w = j - d,
x = v - h,
y = Math.sqrt(w * w + x * x),
z = d + w * k,
A = h + x * k,
B = d + .5 * w,
C = h + .5 * x,
D = d + w * l,
E = h + x * l,
G = x >>= 2; // The shift here and the one below is what makes the partices squiggly. You can replace it with /= 4 for smooth partices.
N > a && ( // Only recurse if the recursion depth counter (a) is less than the max (N)
// This bit uses some trig identities to avoid actually doing any trig at all. In the end it results in much higher speeds and unreadable code.
w >>= 2,
x = m * w + q * x,
w = q * w - m * G,
f(a + 1, z, A, B += w, C += x),
G = x,
x = n * w + r * x,
w = r * w - n * G,
f(a + 1, D, E, B += w, C += x),
G = x,
x = o * w + s * x,
w = s * w - o * G,
f(a + 1, z, A, B += w, C += x),
G = x,
x = p * w + u * x,
w = u * w - p * G,
f(a + 1, D, E, B += w, C += x));
a > 1 && ( // Don't render the first iteration, it's lame. Just two partices moving diagonally back and forth.
i % 9 || c.beginPath(c.stroke(c.strokeStyle = g[~~ (400 * t + y) % 360])), // Only change the colour and stroke every 9 particles for a speed up.
c.moveTo(F?j:b[i], F?v:e[i]), c.lineTo(b[i] = ~~j, e[i++] = ~~v), // Draw particles. My quantizing to integer coords with ~~, we avoid a nasty
c.moveTo(F?d:b[i], F?h:e[i]), c.lineTo(b[i] = ~~d, e[i++] = ~~h)) // antialiasing bug in chrome that causes them to leave permanent trails.
},
/*
Chrome only
onmousemove = function (a) {
a.which ? j += L - a.clientX : 0, // If the mouse button is pressed, add the movement delta to the time offset
L = a.clientX // Set last mouse coord
},*/
/*
Firefox only
onmousemove = function (a) {
a.buttons ? j += L - a.clientX : 0, // If the mouse button is pressed, add the movement delta to the time offset
L = a.clientX // Set last mouse coord
},*/
/*
Chrome/Firefox only
onmousemove = function (a) {
(a.buttons + 1 ? a.buttons : a.which) ? j += L - a.clientX : 0, // If the mouse button is pressed, add the movement delta to the time offset
L = a.clientX // Set last mouse coord
},
*/
// Should work everywhere
onmousedown = onmouseup = function(a) {
K = a.type[7] && a.button + 1;
},
onmousemove = function(a) {
K ? j += L - a.clientX : 0,
L = a.clientX
},
setInterval(function () {
h++, // Frame count
N = top.iter || N, // Num iterations/recursion depth
t = new Date/1e3 - j/100,
// Some oscillators
k = Math.sin(t/13)/2 + .5,
l = Math.sin(t/17)/2 + .5,
// This is the precalcs that help with the trig avoidance later on.
m = Math.sin(t/=10), q = Math.cos(t),
o = Math.sin(t/3), s = Math.cos(t/3),
n = Math.sin(t/=2), r = Math.cos(t),
p = Math.sin(t/=2), u = Math.cos(t),
// Run the function, resetting particle count (i) and F which is set if the iteration count just changed.
F = f(i = 0, 0, 0, W, H)
}, F = 1),
c.fillRect(K=0, 0, W = a.width = innerWidth, H = a.height = innerHeight), // Clear to black initially.
// Fade out, if we do this less than every frame it's faster and we can fine tune the params.
setInterval(function () {
c.fillStyle = "rgba(0,0,0,.05)", c.fillRect(0, 0, W, H)
}, 36),
// Check/reset frame count every second then adjust iterations depending on it.
// Less than 20 FPS = less iters, more than 140 = more iters.
setInterval(function () {
F = 20 > h ? N-- : h > 140 ? N++ : F, h = 0
}, 1e3)