Skip to content

Instantly share code, notes, and snippets.

@robinhouston
Created February 9, 2015 15:29
Show Gist options
  • Save robinhouston/c902705ae9bc2e09316b to your computer and use it in GitHub Desktop.
Save robinhouston/c902705ae9bc2e09316b to your computer and use it in GitHub Desktop.
Constant flux
<!DOCTYPE html>
<meta charset="utf-8">
<title>Flux</title>
<script src="http://bl.ocks.org/robinhouston/raw/6096562/rAF.js" charset="utf-8"></script>
<script src="streamer.js" charset="utf-8"></script>
<style>
html, body { margin: 0; }
canvas { background-color: #4C4C4C; margin: 8px; }
</style>
<canvas width=960 height=500></canvas>
<script>
var NUM_STREAMS = 16000,
MAX_AGE = 30,
FADE_RATE = 0.8,
BORDER = 100;
var canvas = document.getElementsByTagName("canvas")[0],
cx = canvas.getContext("2d");
canvas.width = window.innerWidth - 16;
canvas.height = window.innerHeight - 16;
var streamer = Streamer({
canvas: canvas,
num_streams: NUM_STREAMS,
max_age: MAX_AGE,
fade_rate: FADE_RATE,
border: BORDER,
randomPoint: fluxDensity,
velocity: flux
});
cx.strokeStyle = "rgba(255,255,255,0.6)";
cx.fillStyle = "rgba(255,255,255,0.05)";
cx.fillRect(0, 0, canvas.width, canvas.height);
streamer.start();
// * Vector field transformers
// Rotate the vector field by θ
function rotate(θ, f) {
var cos_θ = Math.cos(θ), sin_θ = Math.sin(θ);
return function(x, y, t) {
return f(x*cos_θ - y*sin_θ, x*sin_θ + y*cos_θ, t);
};
}
// Transform the vector field f under the mobius transformation z ↦ (z-1)/(z+1)
function double_spiral(f) {
return function(x, y, t) {
// Let (x0, y0) be the inverse image of (x, y) ...
var r0 = (x-1)*(x-1) + y*y,
x0 = (1 - x*x - y*y) / r0,
y0 = 2*y / r0,
// Find the velocity v0 at (x0, y0)
v0 = f(x0, y0, t),
vx = v0[0], vy = v0[1];
// and hit v0 with the Jacobian at (x0, y0)
var denom = Math.pow((x0+1)*(x0+1) + y0*y0, 2) / 4,
a = 2 * (y0*y0 - (x0+1)*(x0+1)),
b = 4*(x0+1)*y0;
return [
( a * vx - b * vy ) / denom,
( b * vx + a * vy ) / denom
];
};
}
// Transform a (vector / scalar) field from origin-centred coordinates to pixel coords
function to_px(n, f) {
return function(x_px, y_px, t) {
var divisor = Math.min(canvas.width, canvas.height)/n,
x = (x_px - canvas.width/2) / divisor,
y = (y_px - canvas.height/2) / divisor;
return f(x, y, t);
};
}
// * Primitive vector fields
function spiral(r, theta) {
var log_r = Math.log(r);
return function (x, y, t) {
return [
x*log_r - y*theta,
x*theta + y*log_r
];
};
}
function julia(dx, dy) {
return function(x, y, t) {
return [
(x*x - y*y + dx - x),
( 2*x*y + dy - y)
];
}
}
var minX = +Infinity, maxX = -Infinity;
function flux(x, y) {
return [ 0, (x / canvas.width + 0.1) * 9 ];
}
function fluxDensity(w, h, border) {
return function() {
var r = Math.random(),
x = Math.round(0.1 * (Math.pow(11, r) - 1) * (w + 2*border)) - border,
y = Math.round(Math.random() * (h + 2*border)) - border;
return [ x, y ];
};
}
</script>
function Streamer(options) {
if (!options.hasOwnProperty("canvas")) throw "The 'canvas' option must be specified";
if (!options.hasOwnProperty("velocity")) throw "The 'velocity' option must be specified";
var canvas = options.canvas,
cx = canvas.getContext("2d"),
w = canvas.width, h = canvas.height,
num_streams = options.num_streams || 1000,
ms_per_repeat = options.ms_per_repeat || 1000,
max_age = options.max_age || 10,
fade_rate = options.fade_rate || 0.02,
border = options.border || 100,
velocity = options.velocity,
randomPoint = (options.randomPoint || uniformlyRandomPoint)(w, h, border);
function uniformlyRandomPoint(w, h, border) {
console.log("w = " + w + ", h = " + h + ", border = " + border);
return function() {
var x = Math.round(Math.random() * (w + 2*border)) - border,
y = Math.round(Math.random() * (h + 2*border)) - border;
return [ x, y ];
};
}
function fadeCanvas(alpha) {
cx.save();
cx.globalAlpha = alpha;
cx.globalCompositeOperation = "copy";
cx.drawImage(canvas, 0, 0);
cx.restore();
}
var streams = initStreams();
function initStreams() {
var streams = [];
for (var i=0; i<num_streams; i++) {
var p = randomPoint();
streams.push([
p[0], p[1], Math.round(Math.random() * max_age) + 1, p[0], p[1]
]);
}
return streams;
}
function frame(t) {
fadeCanvas(1 - fade_rate);
cx.save();
cx.setTransform(1, 0, 0, 1, 0, 0);
for (var i=0; i<streams.length; i++) {
var stream = streams[i];
if (stream[2] == 0) {
stream[0] = stream[3];
stream[1] = stream[4];
stream[2] = MAX_AGE;
}
var v = velocity(stream[0], stream[1], t);
if (v[0] <= w && v[1] <= h) {
cx.beginPath();
cx.moveTo(stream[0], stream[1]);
cx.lineTo(stream[0] + v[0], stream[1] + v[1]);
cx.stroke();
stream[0] += v[0];
stream[1] += v[1];
}
stream[2]--;
}
cx.restore();
}
var first_timestamp, animation_frame_id;
function loop(timestamp) {
if (!first_timestamp) first_timestamp = timestamp;
frame(((timestamp - first_timestamp) % ms_per_repeat) / ms_per_repeat);
animation_frame_id = requestAnimationFrame(loop);
}
return {
"start": function() { animation_frame_id = requestAnimationFrame(loop); },
"stop": function() { cancelAnimationFrame(animation_frame_id); }
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment