2011/05/11

Drawing with Cellular Automata and HTML5 Canvas

This weekend I set out to do some canvas drawings. The first one ended up being the centerpiece of my home page. It was inspired by the leaves of a tree on my street. Oh it's good to be back in the deciduous loving climate.

The second drawing was inspired by a recent campout in Death Valley. The contrast of the blue sky, desert floor, and red coleman tent leave a strong imprint on me every time.

The end result should resemble the picture on the right. The page load time is a few seconds on my MBP and it looks quite a lot better in Chrome. The canvas size is determined at load so if you have the horses, full-screen your browser.

I say resemble because the drawing is generated using canvas and cellular automata, and derived from a random data set every time it is rendered.

One of the difficulties of drawing a picture of landscape is the seemingly random variations found in it. Not totally random because each element interacts and affects the others. The stone pattern on the drawing was the first thing I set out to solve.

Initially I was hoping to hit on some semi-random scatter pattern using PHI and a lot of trial and error. This produced a fairly uniform random scatter of stones. What I wanted to draw resembled clumps more than anything.

While scatter patterning away I happened to over hear a gentlemen at the coffee shop mentioning cellular automata and emergent systems as they explain the state of the universe. It's kindof a hippie coffee shop but this guy is clearly in a league of his own. At first I dismissed it and then the idea grew on me that I could generate these clumps with CA. I had used CA for Conway's game of life and a few other toy systems to experiment in the past and it was always fun.

So I generated a map of pixels that were randomly turned on. I weighted the initial data set to be very switched on (baby!). My reasoning was that I wanted grouping to occur within a few mutations and a seriously overcrowded starting set should do just that. The size of the canvas element was prohibitively large so I added a resolution factor. It causes striations in the starting set view but it doesn't affect the CA mutations.

# set up psuedo random cell set
for (var i=0; i < total; i++) {
    if (Math.random() > .2) {
        cells[i] = 1;
    }
}
with resolution factor:
Let's grind this set down with Conway's game of life rules and see what comes out:
// mutate m number of times
while (m--) {
    var next = {};
    for (p in cells) { next[p] = 1; }

    for (var i=0; i < total; i++) {
        var above = i-w < 0 ? i-w+total : i-w,  // wrap
            below = i+w > total ? i+w-total : i+w, // wrap
            neighbors = [
                above-1,above,above+1,
                i-1,i+1,
                below-1,below,below+1
            ],
            active = 0,
            j = neighbors.length-1;

        while (j--) {
            var t = typeof cells[neighbors[j]] === 'number';
            active += (t && cells[neighbors[j]] === 1)|0;
        }
        // rule check
        // Any dead cell with exactly three live neighbours
        //   becomes a live cell.
        if (active === 3 && typeof cells[i] === 'undefined') {
            next[i] = 1;
        }
        // Any live cell with two or three live neighbours
        //   lives on to the next generation.
        // Any live cell with fewer than two live neighbours
        //   dies, as if caused by underpopulation.
        // Any live cell with more than three live neighbours
        //   dies, as if by overcrowding.
        else if (active < 2 || active > 3) {
            delete next[i];
        }
    }

    var cells = {};
    for (p in next) { cells[p] = 1; }
}

As you can see the first pass cleared out a bunch of em. The resolution factor has spaced the pixels apart but you can see the basics of game of life players emerging. By pass 3 it's in a much more spare state but still larger groups than I want. By pass 8 the groups have moved away from each other a bit more and formed a decent pattern. Since this pattern is generated fresh from a random data set every time, we just need something that's right most of the time.

What I like about this approach is that it gives some credence to the hippie coffee shop guy's words. I don't think the world runs on CA rules, but iterative, interactive rules seem to give rise to interesting "random" patterns.

The dataset produced from these mutations can now be used to render a rock pattern with a little magic for the coloring/size.

var styles = ['rgba(116,116,108,.7);','rgba(87,93,55,.9);'];

for (var i in cells) {
    c.beginPath();
    var x = (i*r)%w,
    y = (i*r)/w|0;
    c.strokeStyle = styles[i%15===0|0];
    c.lineWidth = Math.max(30,50*(y/h));
    c.arc(x,y,3,0,360,false);
    c.stroke();
}

tl dr;

Using Cellular Automata rules to mutate random datasets can produce interesting and unique landscape patterns. This approach was super useful for me in drawing an impressionist picture of my favorite camping spot.

I wrote a simple tool to experiment with different factors for generating these patterns.

And here's the picture that is generated from random CA patterns.