Flocc

Agent-based modeling in JavaScript in the browser or on the server. [v0.4.8]

Terrain

Overview

The flocc.Terrain class (from here on, just Terrain) is a 2d grid that can act as background data for Agents in an Environment. When used with a CanvasRenderer, each coordinate in the terrain is rendered as a single pixel, providing a background ‘map’ that adds spatial complexity to the environment.

Instantiating

Terrain must be instantiated with a width and height, along with as an (optional) configuration object.

const terrain = new Terrain(100, 100);

After instantiating, make sure to tell the environment to recognize the terrain by calling environment.use. If you don’t, then the terrain will not be rendered!

environment.use(terrain);

Options

A Terrain can be instantiated with a set of configuration options that determine its color mode and how it updates with each tick of the environment.

const options = {
  async: boolean = false,
  grayscale: boolean = false,
  scale: number = 1
};
const terrain = new Terrain(100, 100, options);

async

By default, terrains are instantiated in synchronous mode. If there is an update rule in effect, and the terrain is in the default synchronous mode, then all coordinate value updates will take place simultaneously, after the update rule has ran. In synchronous mode, a coordinate can only update its own value, by returning a new pixel value.

terrain.addRule((x, y) => {
// turns every coordinate completely red if the x-value is
// greater than 200, completely blue if the x-value is less
// than 100, and leaves them unchanged in between
terrain.addRule((x, y) => {
  if (x > 200) {
    return {
      r: 255,
      g: 0, 
      b: 0,
      a: 255
    }
  } else if (x < 100) {
    return {
      r: 0,
      g: 0,
      b: 255,
      a: 255
    }
  }
});
});

If the async option is set to true, then the terrain will operate in async mode. In async mode, the effects of update rules occur sequentially over every coordinate in the terrain. Coordinates can update not only their own value, but those of other coordinates, by calling terrain.set.

In async mode, the order of update rule execution may become important. By default, a terrain will loop over cells from right to left and top to bottom. However, a random order will be less likely to introduce bias into the simulation. A terrain can loop over its cells in (pseudo-) random order by having its environment tick with the configuration object: environment.tick({ randomizeOrder: true });. See Environment documentation for more info.

terrain.addRule((x, y) => {
  // if the coordinate's red value is greater than 100,
  // increments its right neighbor's blue value
  if (terrain.sample(x, y).r > 100) {
    const neighborPixel = terrain.sample(x + 1, y);
    terrain.set(x + 1, y, {
      r: neighborPixel.r,
      g: neighborPixel.g,
      b: neighborPixel.b + 1,
      a: neighborPixel.a
    });
  }
});

grayscale

By default, terrains are instantiated in color mode, with each coordinate value represented by a pixel-like object with r, g, b, and a key-value pairs (red, green, blue, and alpha/opacity). Values will always be numbers between 0 and 255. For example, a red coordinate at (100, 100) may look like this:

const pixel = terrain.sample(100, 100);
// { r: 255, g: 0, b: 0, a: 255 };

If the grayscale option is set to true, then the terrain will be in grayscale mode, with each coordinate value represented by a single number between 0 (black) and 255 (white). A light gray coordinate at (50, 75) may look like this:

const pixel = terrain.sample(50, 75); // 200

scale

A terrain represents each coordinate as a single pixel on screen. By increasing the scale option, the terrain can be visualized with a larger coordinate size. Below are two terrains with scale set to 1 and 5, respectively.


scale = 1

scale = 5

Increasing the scale can make it easier to identify how terrain coordinates are updating over time and more carefully examine their dynamics.

Methods

.addRule(fn)

Add an update rule that will be called with every environment.tick. The update rule should be a function with x and y arguments that returns either a pixel-like object or a number, depending on if you are in color or grayscale mode.* A terrain may only have one update rule.

terrain.addRule((x, y) => {
  // on a 200x200 terrain, this update rule will always
  // set pixel values as shown on the right
  return {
    r: x + y > 100 ? 255 : 0,
    g: 0,
    b: x + y < 200 ? 255 : 0,
    a: 255
  };
});

Terrain update rule

*Depending on if you are in synchronous or async mode, coordinates may either update only their own values or those of other coordinates — see the options above for more details.

.init(fn)

.init is identical to .addRule, except that it is only called manually, for example right after instantiating the terrain. Call this method if a terrain should have coordinate values set before the first environment.tick.

.load(path, callback)

Load image data onto the terrain. path should be the URL to an image, such as https://flocc.network/path/to/image.jpg. Since it takes time to request remote images and draw them to the terrain, this method runs asynchronously. By passing a callback as a second parameter — a function that is called once the image has successfully loaded — you can ensure that the terrain is ready before running your simulation.

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

// by passing `run` as a callback function, it will not be called
// (and start the simulation running) until the image has loaded
terrain.load('https://flocc.network/path/to/image.jpg', run);

.sample(x, y)

Get the value of the coordinate (x, y). In color mode, this will return a pixel-like object with r, g, b, and a keys that have number values in the range 0-255. In grayscale mode, it will return a single number in the range 0-255.

If you try to sample a coordinate outside of the width/height bounds, it will treat the terrain as a torus — looping it around. For example, calling terrain.sample(101, 101); when the terrain has width 100 and height 100 will return the value at (1, 1).

.set(x, y, pixel | number)

Set the coordinate at (x, y)‘s value to pixel (in color mode) or a number (in grayscale). This function should only be called in async mode — it will do nothing if called in synchronous mode.

.neighbors(x, y, radius = 1, moore = false);

Given a coordinate in the terrain, this method returns an array containing the values of the neighboring coordinates (pixel-like objects or numbers). By default, this method will only look within a radius of 1 (neighboring coordinates immediately to the north, south, east, and west). By increasing the radius parameter, more neighboring agents will be included.

This method also defaults to using the von Neumann neighborhood, which looks for all coordinates within radius by Manhattan distance. If the third parameter is set to true, it will use the Moore neighborhood instead, which looks for all agents within a square of width/length 2 * radius + 1 of the given agent.


von Neumann neighborhood

Moore neighborhood

Images from Wolfram MathWorld