Created
February 9, 2015 15:29
-
-
Save robinhouston/c902705ae9bc2e09316b to your computer and use it in GitHub Desktop.
Constant flux
This file contains hidden or 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> | |
<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> |
This file contains hidden or 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
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