Click to magnify.
Based on Reaction-diffusion worms by Cornus Ammonis.
Click to magnify.
Based on Reaction-diffusion worms by Cornus Ammonis.
<!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> |