📄 Forest Fire - README.txt
×

Forest Fire

EcologyPhysics CanvasRendererTerrain

The Forest Fire model is a classic demonstration of percolation—the study of how connectivity in a system determines whether a process (here, fire) can spread across it. Trees are randomly distributed on a grid according to a density parameter (PERCENT_FULL). Fire ignites on one edge and propagates to orthogonally adjacent trees (north, south, east, west) each tick.

The model exhibits a critical threshold around 59% density. Below this threshold, the forest is too sparse for fire to percolate across the grid—isolated clusters burn out independently. Above the threshold, trees form a connected network that allows fire to sweep through nearly the entire landscape. This phase transition is sharp: small changes in density near the critical point produce dramatic differences in outcome. The same mathematics governs phenomena from epidemic spread to the conductivity of composite materials.

💻 forestfire.js - Interactive Editor
×
import { Environment, Terrain, Colors, CanvasRenderer } from "flocc";

/* ----- PARAMETERS ----- */

const PERCENT_FULL = 0.59;

/* ----------------------- */

let [width, height] = [window.innerWidth, window.innerHeight];
const scale = (Math.min(width, height) / 200) | 0;
while (width % scale !== 0) width++;
while (height % scale !== 0) height++;

const environment = new Environment({ width, height });
const renderer = new CanvasRenderer(environment, {
  width,
  height
});
renderer.mount("#container");

const terrain = new Terrain(width / scale, height / scale, {
  scale
});
environment.use(terrain);

const empty = Colors.BLACK;
// Colors.GREEN is a little too dark for this
const notOnFire = { r: 0, g: 240, b: 20, a: 255 };
const onFire = Colors.RED;

function isOnFire(p) {
  return p.r === onFire.r && p.g === onFire.g && p.b === onFire.b;
}

function isEmpty(x, y) {
  const p = terrain.sample(x, y);
  return p.r === empty.r && p.g === empty.g && p.b === empty.b;
}

function isNotEmpty(x, y) {
  const p = terrain.sample(x, y);
  return p.r === notOnFire.r && p.g === notOnFire.g && p.b === notOnFire.b;
}

function setup() {
  terrain.init((x, y) => {
    if (Math.random() > PERCENT_FULL) return empty;
    return notOnFire;
  });
  terrain.init((x, y) => {
    if (isNotEmpty(x, y) && x === 0) return onFire;
  });
  terrain.addRule(function(x, y) {
    if (isEmpty(x, y)) return;
    const neighbors = terrain.neighbors(x, y);
    for (let i = 0; i < neighbors.length; i++) {
      if (isOnFire(neighbors[i])) return onFire;
    }
  });
}

function run() {
  environment.tick();
  window.requestAnimationFrame(run);
}

setup();
run();

Edit the code on the left · See results on the right