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> |