Gradient Circles for Map Generator

As I mentioned in the comment diamond and square is much easier with good enough results. So the algorithm:

  1. configure generation properties

    Here you need to have set of parameters like min,max elevation, sea level, elevation ranges for vegetation, sand/rock/dirt, etc, slope parameters etc.

  2. create terrain height map I call it zed[][]

    For this you need slightly modified Diamond&Square algorithm. The problem is this algorithm produces “inland” like terrain.

    To adjust it so it produces island like terrains you need to initialize it with lowest possible elevation in corners. Also you need to ignore the first diamond step and initialize the mid point with some random value instead (not average of corners). And last after each square iteration correct the border points to the minimal (underwater) elevation (or some random value near it).

    To achieve the good output I use approximately range <-2^15 , 2^16> while generation. After this I find min and max elevation in the generated terrain and rescale to configured elevation ranges.

    Do not forget that Diamond and square need map of resolution (2^n)+1 !!!

  3. create surface map I call it typ[][]

    When terrain map is finished you can add elevation based features like these in ascending order:

    • watter,sand,vegetation type,mountine rocks,snow

    Then add parameters based on slope of terrain

    • rocks

    Then you can add additional things like (based on some rules):

    • rivers,streams,watter-falls,building,roads,…

I do it in C++ like this:

void map_random(int _xs,int _ys)
    {
    // config
    int h0=-1000,h1=3000;       // [m] terrain elevation range
    int h_water= 0;             // [m] sea level
    int h_sand=15;              // [m] sand level
    int h_evergreen=1500;       // [m] evergreen level
    int h_snow=2000;            // [m] snow level
    int h_rock=1800;            // [m] mountine rock level
    float a_rock=60.0;          // [deg] mountine rock slope
    float d_pixel=15.0;         // [m] pixel size
    bool _island=true;

    // types
    enum _cover_enum
        {
        _cover_none=0,
        _cover_water,
        _cover_snow,
        _covers,
        _cover_shift=0,
        _cover_mask=15,
        };
    DWORD _cover[_covers]=
        {
        //  RRGGBB
        0x00000000,     // none
        0x00004080,     // water
        0x008F8F8F,     // snow
        };
    enum _terrain_enum
        {
        _terrain_enum_beg=-1,
        _terrain_dirt,
        _terrain_sand,
        _terrain_rock,
        _terrains,
        _terrain_shift=4,
        _terrain_mask=15,
        };
    DWORD _terrain[_terrains]=
        {
        //  RRGGBB
        0x00301510,     // dirt
        0x00EEC49A,     // sand
        0x00777777,     // rock
        };
    enum _flora_enum
        {
        _flora_enum_beg=-1,
        _flora_none,
        _flora_grass,
        _flora_hardwood,
        _flora_evergreen,
        _flora_deadwood,
        _floras,
        _flora_shift=8,
        _flora_mask=15,
        };
    DWORD _flora[_floras]=
        {
        //  RRGGBB
        0x00000000,     // none
        0x007F7F3F,     // grass
        0x001FFF1F,     // hardwood
        0x00007F00,     // evergreen
        0x007F3F1F,     // deadwood
        };

    // variables
    float a,b; int c,t,f;
    int x,y,z,xx,yy,mxs,mys,dx,dy,dx2,dy2,r,r2;
    int **ter=NULL,**typ=NULL;
    Randomize();
    // align resolution to power of 2
    for (mxs=1;mxs+1<_xs;mxs<<=1); if (mxs<3) mxs=3;
    for (mys=1;mys+1<_ys;mys<<=1); if (mys<3) mys=3;
    ter=new int*[mys+1]; for (y=0;y<=mys;y++) ter[y]=new int[mxs+1];
    typ=new int*[mys+1]; for (y=0;y<=mys;y++) typ[y]=new int[mxs+1];

    // [Terrain]

    // diamond & square random height map -> ter[][]
    dx=mxs; dx2=dx>>1; r=1<<16;                     // init step,half step and randomness
    dy=mys; dy2=dy>>1; r2=r>>1;
    // set corners values
    if (_island)
        {
        t=-r2;
        ter[  0][  0]=t;
        ter[  0][mxs]=t;
        ter[mys][  0]=t;
        ter[mys][mxs]=t;
        ter[dy2][dx2]=r2;
        }
    else{
        ter[  0][  0]=Random(r);
        ter[  0][mxs]=Random(r);
        ter[mys][  0]=Random(r);
        ter[mys][mxs]=Random(r);
        }
    for (;dx2|dy2;dx=dx2,dx2>>=1,dy=dy2,dy2>>=1)    // subdivide step until full image is filled
        {
        if (!dx) dx=1;
        if (!dy) dy=1;
        // diamond (skip first one for islands)
        if ((!_island)||(dx!=mxs))
         for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
          for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
           ter[y][x]=((ter[y-dy2][x-dx2]+ter[y-dy2][x+dx2]+ter[y+dy2][x-dx2]+ter[y+dy2][x+dx2])>>2)+Random(r)-r2;
        // square
        for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
         for (x=dx ,xx=mxs-dx ;x<=xx;x+=dx)
          ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
        for (y=dy ,yy=mys-dy ;y<=yy;y+=dy)
         for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
          ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
        for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
            {
            y=  0; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y+dy2][x])/3)+Random(r)-r2;
            y=mys; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x])/3)+Random(r)-r2;
            }
        for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
            {
            x=  0; ter[y][x]=((ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
            x=mxs; ter[y][x]=((ter[y][x-dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
            }

        // adjust border
        if (_island)
            {
            for (y=0;y<=mys;y+=dy2) { ter[y][0]=t; ter[y][mxs]=t; }
            for (x=0;x<=mxs;x+=dx2) { ter[0][x]=t; ter[mys][x]=t; }
            }

        // adjust randomness
        // r=(r*100)>>8; if (r<2) r=2; r2=r>>1;
        r>>=1; if (r<2) r=2; r2=r>>1;
        }
    // rescale to <h0,h1>
    xx=ter[0][0]; yy=xx;
    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
        {
        z=ter[y][x];
        if (xx>z) xx=z;
        if (yy<z) yy=z;
        }
    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
      ter[y][x]=h0+(((ter[y][x]-xx)*(h1-h0))/(yy-xx));

    // [Surface]

    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
        {
        z=ter[y][x];
        // max slope [deg]
        a=atan2(ter[y][x+1]-z,d_pixel);
        b=atan2(ter[y+1][x]-z,d_pixel);
        if (a<b) a=b; a*=180.0/M_PI;

        c=_cover_none;
        if (z<=h_water) c=_cover_water;
        if (z>=h_snow ) c=_cover_snow;

        t=_terrain_dirt;
        if (z<=h_sand)  t=_terrain_sand;
        if (z>=h_rock)  t=_terrain_rock;
        if (a>=a_rock)  t=_terrain_rock;

        f=_flora_none;
        if (t==_terrain_dirt)
            {
            r=Random(100);
            if (r>10) f=_flora_grass;
            if (r>50)
                {
                if (z>h_evergreen) f=_flora_evergreen;
                else{
                    r=Random(h_evergreen);
                    if (r<=z) f=_flora_evergreen;
                    else      f=_flora_hardwood;
                    }
                }
            if (r<5) f=_flora_deadwood;
            }
        typ[y][x]=(c<<_cover_shift)|(t<<_terrain_shift)|(f<<_flora_shift);
        }

    // [copy data] rewrite this part to suite your needs it just compute color based on type of terrain and height
    // ter[][] is elevation in meters
    // typ[][] is surface type
/*
    for (y=0;y<_ys;y++)
     for (x=0;x<_xs;x++)
       pic.p[y][x].dd=(((ter[y][x]-h0)*255)/(h1-h0))*0x00010101;
    for (y=0;y<_ys;y++)
     for (x=0;x<_xs;x++)
        {
        r=typ[y][x];
        c=(r>>  _cover_shift)&  _cover_mask;
        t=(r>>_terrain_shift)&_terrain_mask;
        f=(r>>  _flora_shift)&  _flora_mask;
               r=_terrain[t];
        if (c) r=  _cover[c];
        if (c==_cover_water)
            {
            xx=256-((ter[y][x]<<7)/h0);
            yy=int(r>>16)&255; yy=(yy*xx)>>8; r=(r&0x0000FFFF)|(yy<<16);
            yy=int(r>> 8)&255; yy=(yy*xx)>>8; r=(r&0x00FF00FF)|(yy<< 8);
            yy=int(r    )&255; yy=(yy*xx)>>8; r=(r&0x00FFFF00)|(yy    );
            }
        if (f){ if (c) r|=_flora[f]; else r=_flora[f]; };
        pic.p[y][x+_xs].dd=r;
        }
*/    
    // free ter[][],typ[][]
    for (y=0;y<=mys;y++) delete[] ter[y]; delete[] ter; ter=NULL;
    for (y=0;y<=mys;y++) delete[] typ[y]; delete[] typ; typ=NULL;
    }

The output with current settings is like this:

example

[Notes]

This approach usually produce only single big hill on the island. (Inland is generated OK) If you want more of them you can create more terrain maps and average them together.

I do following instead: I set the middle point to max height and ignore first diamond pass. After the first square pass I set the middle point back to some random value. This adds the possibility of more central hills then just one. Using this approach and adding lighting (ambient + normal shading) to preview and slight tweaking of pixel size (35m) I got this result:

example

On rare occasion this can generate inland like map (if the central area is too small. To handle it you can scan corners for watter. if there is land generate again or add some bias for central points randomness in first pass.

You can play with the code for example add rivers:

  1. find topest hill
  2. get random location close/around it
  3. set it to river type
  4. find smallest height neighbor pixel not set to river type
  5. if it is on edge of map or set to sea/water type stop otherwise loop #3

    If you want more then one rivers then do not forget to use some temp type for already done rivers so the algorithm can work properly. You can also increase the river volume with distance from start… Here is the result:

    rivers

    After this you should also equalize the formed lakes water level.

Leave a Comment