My first use of <canvas>; works only in browsers that support it. Written 2010-04-29 for a Rosetta Code task.
Seems to be much faster in Safari than Firefox 3.6.3.
Source code:
function brownian(canvasId, messageId) {
var canvas = document.getElementById(canvasId);
var ctx = canvas.getContext("2d");
// Options
var drawPos = true;
var seedResolution = 50;
var clearShade = 0; // 0..255
// Static state
var width = canvas.width;
var height = canvas.height;
var cx = width/2;
var cy = height/2;
var clearStyle = "rgba("+clearShade+", "+clearShade+", "+clearShade+", 1)";
// Utilities
function radius(x,y) {
return Math.sqrt((x-cx)*(x-cy)+(y-cx)*(y-cy));
}
function test(x, y) {
if (x < 0 || y < 0 || x >= width || y >= height)
return false;
var data = ctx.getImageData(x, y, 1, 1).data;
return data[0] != clearShade || data[1] != clearShade || data[2] != clearShade;
}
var shade = 120;
function setc(x, y, c) {
//var imgd = ctx.createImageData(1, 1);
//var pix = imgd.data;
//pix[0] = pix[1] = pix[2] = c == 255 ? 255 : shade;
//pix[3] = 255;
//shade = (shade + 1) % 254;
//ctx.putImageData(imgd, x, y);
//ctx.fillStyle = "rgba("+c+", "+c+", "+c+", 1)";
shade = (shade + 0.02) % 360;
if (c) {
ctx.fillStyle = "hsl("+shade+", 100%, 50%)";
} else {
ctx.fillStyle = clearStyle;
}
ctx.fillRect (x, y, 1, 1);
}
function set(x,y) {
setc(x,y,true);
}
function clear(x,y) {
setc(x,y,false);
}
// Initialize canvas to blank opaque
ctx.fillStyle = clearStyle;
ctx.fillRect (0, 0, width, height);
// Current position
var x;
var y;
// Farthest distance from center a particle has yet been placed.
var closeRadius = 1;
// Place seed
set(cx, cy);
// Choose a new random position for a particle (not necessarily unoccupied)
function newpos() {
// Wherever particles are injected, the tree will tend to grow faster
// toward it. Ideally, particles wander in from infinity; the best we
// could do is to have them wander in from the edge of the field.
// But in order to have the rendering occur in a reasonable time when
// the seed is small, without too much visible bias, we instead place
// the particles in a coarse grid. The final tree will cover every
// point on the grid.
//
// There's probably a better strategy than this.
x = Math.floor(Math.random()*(width/seedResolution))*seedResolution;
y = Math.floor(Math.random()*(height/seedResolution))*seedResolution;
}
newpos();
var animation;
animation = window.setInterval(function () {
if (drawPos) clear(x,y);
for (var i = 0; i < 10000; i++) {
var ox = x;
var oy = y;
// Changing this to use only the first four directions will result
// in a denser tree.
switch (Math.floor(Math.random()*8)) {
case 0: x++; break;
case 1: x--; break;
case 2: y++; break;
case 3: y--; break;
case 4: x++; y++; break;
case 5: x--; y++; break;
case 6: x++; y--; break;
case 7: x--; y--; break;
}
if (x < 0 || y < 0 ||
x >= width || y >= height ||
radius(x,y) > closeRadius+seedResolution+2) {
// wandered out of bounds or out of interesting range of the
// tree, so pick a new spot
var progress = 1000;
do {
newpos();
progress--;
} while ((test(x-1,y-1) || test(x,y-1) || test(x+1,y-1) ||
test(x-1,y ) || test(x,y ) || test(x+1,y ) ||
test(x-1,y+1) || test(x,y+1) || test(x+1,y+1)) && progress > 0);
if (progress <= 0) {
document.getElementById(messageId).appendChild(document.createTextNode("Stopped for lack of room."));
clearInterval(animation);
break;
}
}
if (test(x, y)) {
// hit something, mark where we came from and pick a new spot
set(ox,oy);
closeRadius = Math.max(closeRadius, radius(ox,oy));
newpos();
}
}
if (drawPos) set(x,y);
}, 1);
}