Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
114 views
in Technique[技术] by (71.8m points)

c# - Gradient Circles for Map Generator

So I'm making a map generator that makes random islands. It uses Perlin Noise at the heart of the generator and then a method using circles with gradients to make the islands.

The circle method creates a number of circles in the centerish of the map with a gradient from a colour starting at 64 down to 0. The issue is that this method is creating a un-natrual look at parts of the map with circular edges. When the perlin noise is generated for a pixel it will get that pixel on the gradient map and then mutliply it by the blue value.

So if the perlin noise gives a one on pixel 1, 5 and the blue value on the gradient map is 54 it will output a noise value of 54. If the perlin noise on pixel 130, 560 is 0.5 and the gradient colour value is 64 then the noise value of 32.

Here is what I am getting:

Picture of what I am getting

There is two key points to the code, the perlin bit:

noise = NoiseGenerator.Noise(x, y);
double gradColour = getGradColour(x, y).B;
double addedNoise = noise * gradColour;
double gradNoise = addedNoise;// - gradColour;

And then the gradient map generator:

    public static void DrawGrad(float X, float Y, float R, Color C1, Color C2)
    {
        Graphics g = Graphics.FromImage(imgGrad);
        GraphicsPath path = new GraphicsPath();
        path.AddEllipse(X, Y, R, R);

        PathGradientBrush pathGrBrush = new PathGradientBrush(path);
        pathGrBrush.CenterColor = C1;

        Color[] colours = { C2 };
        pathGrBrush.SurroundColors = colours;
        g.FillEllipse(pathGrBrush, X, Y, R, R);
        //g.FillEllipse(Brushes.Red, X, Y, R, R);
        g.Flush();
    }

        int amount = rnd.Next(25, 30);
        for (int i = 0; i < amount / 4; i++)
        {
            float X = rnd.Next(-800, 1748);
            float Y = rnd.Next(-800, 1748);
            float R = rnd.Next(1000, 1200);
            DrawGrad(X, Y, R, Color.FromArgb(255, 0, 0, rnd.Next(15, 20)), Color.FromArgb(0, 0, 0, 0));
        }
        for (int i = 0; i < amount; i++)
        {
            double positionDiv = 1.98;
            double X1 = rnd.Next(0, 450) / positionDiv;
            double Y1 = rnd.Next(0, 450) / positionDiv;
            double R1 = rnd.Next(300, 650) / 4;
            float R = (float)R1;
            float X = (float)X1;
            float Y = (float)Y1;

            while (X + R > 1004)
            {
                X = 924 - R;
            }
            while (Y + R > 1004)
            {
                Y = 924 - R;
            }
            if(X < 30)
            {
                X = 30;
            }
            if(Y < 30)
            {
                Y = 30;
            }
            DrawGrad(X, Y, R, Color.FromArgb(255, 0, 0, rnd.Next(40, 64)), Color.FromArgb(0, 0, 0, rnd.Next(13, 17)));
        }

I was just wondering if anyone else knows any other methods in C# that could create an island using perlin noise? Any advice would be greatly appreciated.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...