Skip to content

Instantly share code, notes, and snippets.

@robinhouston
Created July 28, 2013 14:16
Show Gist options
  • Save robinhouston/6098746 to your computer and use it in GitHub Desktop.
Save robinhouston/6098746 to your computer and use it in GitHub Desktop.
Double stumbling blocks
<!DOCTYPE html>
<meta charset="utf-8">
<title>Double stumbling blocks</title>
<script src="http://bl.ocks.org/robinhouston/raw/6096562/rAF.js" charset="utf-8"></script>
<script src="http://bl.ocks.org/robinhouston/raw/6096562/doyle.js" charset="utf-8"></script>
<canvas width=960 height=500></canvas>
<script>
// Initialisation
var canvas = document.getElementsByTagName("canvas")[0],
context = canvas.getContext("2d");
// Complex arithmetic
function cmul(w, z) {
return [
w[0]*z[0] - w[1]*z[1],
w[0]*z[1] + w[1]*z[0]
];
}
function csub(w, z) {
return [ w[0]-z[0], w[1]-z[1] ];
}
function modulus(p) {
return Math.sqrt(p[0]*p[0] + p[1]*p[1]);
}
function crecip(z) {
var d = z[0]*z[0] + z[1]*z[1];
return [z[0]/d, -z[1]/d];
}
function cpow(z, n) {
var mod = Math.pow(modulus(z), n), arg = Math.atan2(z[1], z[0]) * n;
return [ mod*Math.cos(arg), mod*Math.sin(arg) ];
}
// Möbius transformation that maps (0, 1, ∞) to (-1, 0, 1)
function transform_point(p) {
var x = p[0], y = p[1];
var denom = (x+1)*(x+1) + y*y;
return [ (x*x - 1 + y*y)/denom, 2*y/denom ];
}
// Doyle spiral drawing
function spiral(r, start_point, delta, gamma, options) {
var recip_delta = crecip(delta),
mod_delta = modulus(delta),
mod_recip_delta = 1/mod_delta,
color_index = options.i,
colors = options.fill,
min_d = options.min_d,
max_d = options.max_d,
delta_gamma = cmul(delta, gamma),
delta_over_gamma = cmul(delta, crecip(gamma)),
gamma_over_delta = cmul(gamma, crecip(delta));
// Spiral inwards
for (var q = start_point, mod_q = modulus(q);
mod_q > min_d;
q = cmul(q, recip_delta), mod_q *= mod_recip_delta
) {
color_index = (color_index + colors.length - 1) % colors.length;
}
// Spiral outwards
for (;mod_q < max_d; q = cmul(q, delta), mod_q *= mod_delta, color_index = (color_index + 1) % colors.length) {
var quad = [transform_point(q)];
if (modulus(quad[0]) > 10) continue;
if (color_index == 0)
quad.push(transform_point(cmul(q, delta_over_gamma)));
quad.push(transform_point(cmul(q, delta)));
if (color_index == 2)
quad.push(transform_point(cmul(q, delta_gamma)));
quad.push(transform_point(cmul(q, gamma)));
if (color_index == 1)
quad.push(transform_point(cmul(q, gamma_over_delta)));
if (modulus(csub(quad[0], quad[2])) > 5) continue;
context.fillStyle = colors[color_index];
context.beginPath();
context.moveTo(quad[0][0], quad[0][1]);
context.lineTo(quad[1][0], quad[1][1]);
context.lineTo(quad[2][0], quad[2][1]);
context.lineTo(quad[3][0], quad[3][1]);
context.closePath();
context.fill();
}
}
// Animation
var p = 9, q = 24;
var root = doyle(p, q);
var ms_per_repeat = 750;
function frame(t) {
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(Math.round(canvas.width/2), cy = Math.round(canvas.height/2));
context.scale(200, 200);
context.rotate(Math.PI / 12);
var min_d = 1e-3, max_d = 1e3;
var start = cpow(root.a, t);
for (var i=0; i<q; i++) {
spiral(root.r, start, root.a, root.b, {
fill: ["#050505", "#D2D2D2", "#676767"], i: (2*i)%3,
min_d: min_d, max_d: max_d
});
start = cmul(start, root.b);
}
}
var first_timestamp;
function loop(timestamp) {
if (!first_timestamp) first_timestamp = timestamp;
frame(((timestamp - first_timestamp) % (3*ms_per_repeat)) / ms_per_repeat);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment