Skip to content

Instantly share code, notes, and snippets.

@robinhouston
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robinhouston/e4d6b3b501e4a2811f9f to your computer and use it in GitHub Desktop.
Save robinhouston/e4d6b3b501e4a2811f9f to your computer and use it in GitHub Desktop.
Ammonis worms
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ammonis worms with magnifier</title>
<style>
body { margin: 0; }
#canvas { cursor: -webkit-zoom-in; cursor: -moz-zoom-in; cursor: zoom-in; }
#canvas.magnifying { cursor: none; }
#magnifier { border: 1px solid #333; padding: 8px; position: absolute; width: 400px; height: 400px; top: 0; left: 0; background: white; pointer-events: none; }
</style>
</head>
<body>
<script>
var W = 256, H = 256, N = W*H; // Tile dimensions
var doc = document.documentElement,
canvas = document.createElement("canvas"),
cx = canvas.getContext("2d"),
tile = document.createElement("canvas"),
tcx = tile.getContext("2d"),
magnifier = document.createElement("canvas"),
mcx = magnifier.getContext("2d"),
mag_x, mag_y,
magnifying = false;
// Parameters from:
// https://reaction-diffusion.googlecode.com/svn/trunk/Ready/Patterns/Experiments/MandelbrotWorms_CornusAmmonis.vti
var clampMin = -10,
clampMax = 10,
D_a = 0.001,
D_b = 0.004,
abClampMin = -100, // Use 100 rather than 50 to get
abClampMax = 100; // smaller faster-moving worms.
function resizeCanvas() {
canvas.width = doc.clientWidth;
canvas.height = doc.clientHeight;
}
function init() {
tile.width = W;
tile.height = H;
canvas.id = "canvas";
magnifier.id = "magnifier";
magnifier.width = magnifier.height = 100;
window.addEventListener("resize", function() {
resizeCanvas();
drawTiles();
}, false);
resizeCanvas();
document.body.appendChild(canvas);
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mouseup", mouseUp, false);
initReactionDiffusion();
startReactionDiffusion();
}
function mouseDown(e) {
canvas.addEventListener("mousemove", mouseMove, false);
document.body.appendChild(magnifier);
mouseMove(e);
magnifying = true;
canvas.className = "magnifying";
}
function mouseMove(e) {
var s = magnifier.style,
x = Math.max(0, Math.min(canvas.width - 418, (e.x || e.clientX) - 200)),
y = Math.max(0, Math.min(canvas.height - 418, (e.y || e.clientY) - 200));
s.transform = s.webkitTransform = s.MozTransform = s.OTransform = s.msTransform =
"translate(" + x + "px," + y + "px)";
mag_x = Math.max(0, Math.min(canvas.width - 100, (e.x || e.clientX) - 50));
mag_y = Math.max(0, Math.min(canvas.height - 100, (e.y || e.clientY) - 50));
}
function mouseUp(e) {
canvas.removeEventListener("mousemove", mouseMove);
document.body.removeChild(magnifier);
magnifying = false;
canvas.className = null;
}
function drawTiles() {
renderTile();
cx.fillStyle = cx.createPattern(tile, "repeat");
cx.fillRect(0, 0, canvas.width, canvas.height);
if (magnifying) {
var x = mag_x % tile.width,
y = mag_y % tile.height,
w = magnifier.width,
h = magnifier.height;
mcx.clearRect(0, 0, w, h);
mcx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
}
}
var α, β, af, α1, β1, α2, β2, α3, β3, α4, β4, α_temp, β_temp;
function initReactionDiffusion() {
α = []; β = [];
for (var i=0; i<N; i++) {
α[i] = Math.random() * (abClampMax - abClampMin) + abClampMin;
β[i] = Math.random() * (abClampMax - abClampMin) + abClampMin;
}
α1 = []; α2 = []; α3 = []; α4 = [];
β1 = []; β2 = []; β3 = []; β4 = [];
α_temp = []; β_temp = [];
}
function frame() {
rk4(f, 0.5);
drawTiles();
af = requestAnimationFrame(frame);
}
function startReactionDiffusion() {
af = requestAnimationFrame(frame);
}
function renderTile() {
var imd = tcx.createImageData(W, H),
d = imd.data;
for (var i=0; i<N; i++) {
var v = ((abClampMax - α[i]) / (abClampMax - abClampMin) * 255)|0;
d[4*i + 0] = v;
d[4*i + 1] = v;
d[4*i + 2] = v;
d[4*i + 3] = 0xFF;
}
tcx.putImageData(imd, 0, 0);
}
function f(α_in, β_in, c, δα, δβ, δα_out, δβ_out) {
var α, β;
if (c == 0) {
α = α_in;
β = β_in;
} else {
α = α_temp;
β = β_temp;
for (var i=0; i<N; i++) {
α[i] = α_in[i] + c*δα[i];
β[i] = β_in[i] + c*δβ[i];
}
}
for (var y=0; y<H; y++) {
for (var x=0; x<W; x++) {
var i = W*y + x,
px = (x+W-1)%W, sx = (x+1)%W,
py = (y+H-1)%H, sy = (y+1)%H;
var laplacian_a = α[W*py+x] + α[W*y+sx] + α[W*sy+x] + α[W*y+px] - 4*α[i],
laplacian_b = β[W*py+x] + β[W*y+sx] + β[W*sy+x] + β[W*y+px] - 4*β[i];
δα_out[i] = (laplacian_a % 1.0) + Math.max(clampMin, Math.min(clampMax,
(D_a * (α[i]*α[i] - β[i]*β[i])) + laplacian_b));
δβ_out[i] = (laplacian_a % 1.0) + Math.max(clampMin, Math.min(clampMax,
(D_b * (2*α[i]*β[i])) - laplacian_a));
}
}
}
// Fourth-order Runge-Kutta method
function rk4(f, h) {
f(α, β, 0, null, null, α1, β1);
f(α, β, h/2, α1, β1, α2, β2);
f(α, β, h/2, α2, β2, α3, β3);
f(α, β, h, α3, β3, α4, β4);
for (var i=0; i<N; i++) {
α[i] = Math.max(abClampMin, Math.min(abClampMax, α[i] + (h/6) * (α1[i] + 2*α2[i] + 2*α3[i] + α4[i]) ));
β[i] = Math.max(abClampMin, Math.min(abClampMax, β[i] + (h/6) * (β1[i] + 2*β2[i] + 2*β3[i] + β4[i]) ));
}
}
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment