Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?


Failed to load latest commit information.
Latest commit message
Commit time

JavaScript version of my Polygon Map Generator.

  • Most of the algorithms are reimplemented rather than ported, so there are some minor differences, but it mostly follows what’s described on this page.
  • The data structures are rather different. The mesh connectivity is separate from the generated map (elevation, rivers, biomes, etc.). The original project uses an “array of struct” approach whereas this one uses a “struct of arrays” approach (see explanation).
  • The naming convention for the data is x_property_y where x and y are r, s, or t indicating the type of the input (x) and output (y). For example, t_downslope_s would be an array indexed by a t (triangle) id, and returning an s (side) id.
  • The maps are created with 0 ≤ x ≤ 1000, 0 ≤ y ≤ 1000.

This repository contains the map generation algorithms but not the code for UI or rendering. I only used it for this one project so it’s not a general purpose library.


The way I used this library was with Poisson Disc to create points for the mesh, and a seeded random number library.

const SimplexNoise =  require('simplex-noise');
const DualMesh =      require('@redblobgames/dual-mesh');
const MeshBuilder =   require('@redblobgames/dual-mesh/create');
const Map =           require('@redblobgames/mapgen2');
const Poisson =       require('poisson-disk-sampling');
const {makeRandInt} = require('@redblobgames/prng');

let mesh = new DualMesh(
    new MeshBuilder()
        .addPoisson(Poisson, 100)

let map = new Map(mesh,
                      amplitude: 0.2,
                      length: 4,
                      seed: 12345

    noise: new SimplexNoise(),
    shape: {round: 0.5, inflate: 0.4, amplitudes: [1/2, 1/4, 1/8, 1/16]},
    numRivers: 30,
    drainageSeed: 0,
    riverSeed: 0,
    noisyEdge: {length: 10, amplitude: 0.2, seed: 0},
    biomeBias: {north_temperature: 0, south_temperature: 0, moisture: 0},

If you want to use your own points for the mesh, keep in mind that they are expected to be in the range 0 ≤ x ≤ 1000, 0 ≤ y ≤ 1000:

const points = [[250, 250], [750, 250], [750, 750], [250, 750]];
let mesh = new DualMesh(
    new MeshBuilder()

The code is written assuming we need to use seeds for repeatable results, but if you don’t care about seeded random numbers, you can use the built-in random number function instead of makeRandInt from my library:

function makeRandInt (_ignoreSeed) {
    return N => Math.round(Math.random() * N);

You can pass in your own noise function instead of using the SimplexNoise library:

noise: {noise2D(nx, ny) { return  }}

However, the code as written doesn’t have a way to pass in your own height map or a water/land assignment function. You can modify the assign_r_water function if you want to use your own water/land shape.


If your project needs polygon data:

let polygons = [];
for (let r = 0; r < map.mesh.numSolidRegions; r++) {
       biome: map.r_biome[r],
       vertices: map.mesh.r_circulate_t([], r)
                         .map((t) => map.t_pos([], t))

If you want the noisy edges instead, see map.s_lines[s] for the line segments that should be used instead of side s.

If your game needs the polygons split into triangles:

let triangles = [];
for (let s = 0; s < map.mesh.numSolidSides; s++) {
    let r = map.mesh.s_begin_r(s),
        t1 = map.mesh.s_inner_t(s),
        t2 = map.mesh.s_outer_t(s);
       biome: map.r_biome[r],
       indices: [
          map.r_pos([], r),
          map.t_pos([], t1),
          map.t_pos([], t2),

If your game needs the polygons split into tiles:

The map coordinates are 0 to 1000. Scale these to the desired size of the map. Use a polygon rasterization library like points-in-polygon to get a list of tiles for each polygon. I have not tried this yet.