Thursday, August 29, 2013

Random Terrain Generation (RTG) With Matrices And Calculation Cells


In my previous tutorial I wrote about how we can make use of Perlin Noise for terrain generation. Perlin Noise is used because it gives totally random results, however the RTG motor I wrote was really slow since it requires to take information directly from the monitor. In this tutorial, I will show you how to simulate the same method without calling information from the screen, but using matrices instead. This way we will get much more fast results with just a little matrice knowledge.

How To Generate Terrain With Matrices And Calculation Cells?
First of all if we try to define what a matrice is, we can say that it is a two-dimensional number table. For example:
[5] is a one-dimensional number with one number,
[5   2] is a two-dimensional number table with a size of 1x2. Same way;
|5   2|
|4   7|  is also a two-dimensional number table with a size of 2x2.

While creating terrains, first of all we will create a matrice but way more larger than the ones in the examples, maybe some 256x256 sized-ones. But to make it easier to understand I will explain everything with a 8x8 matrice. So think about a 8x8 matrice of which all numbers are zero.

For the next step, we will assign random numbers from a limited interval for each component of this matrice. I will use [0,32] interval. I want to create an island, therefore I will assign numbers only to the red area I've shown above, and leave the ones on the outside as zero. After assigning numbers, I will be something like this:

The next step is using canculation cells. Canculation cell is a function that applies a specific operation over a specific area of any matrice. For this example I will use a 3x3 sized calculation cell, which will add up all the numbers in a 3x3 area and assign the total number to the component in the middle of the calculation cell. This calculation cell will be applied all over our matrice to generate a new matrice of higher numbers.

Above on the left, the green areas with red midpoint are our calculation cells. As you see for the cells on the corners, most of the numbers left outside. For the ones on the sides, most of the numbers are inside the matrice. And for the middle ones all of the numbers are in the cell. And above on the right is our second matrice that is generated after the calculation cell operations are finished. You must note that the one on the right and the first matrice we created are TOTALLY DIFFERENT. That means you must create a second matrice for the results of calculation cells. If you record the results of the calculation cells on the source matrice, that will give a problematic terrain generation.

But How Did It Work?
Let us take a look at the calculation cell on the top-left corner. What our calculation cell does is adding up all the numbers inside itself, and then assigning the total number at the midpoint of the result matrice. So, the top-left cell added up 0+0+0+18 = 18, and assigned it to the red component of the result matrice, which can be seen as an 18 in a yellow box on the right matrice.

For the one at the left side:  0+20+0+7+0+28 = 55' is total number and 55 is added to the midpoint.

And fot the middle cell: 24+30+14+15+1+17+8+14+27=150, which can also be seen on the right as assigned to the midpoint.

This operation will be done for each component in the matrice, which means for a 8x8 matrice, calculation will be done 64 times..

How Do We Generate Terrain From This Matrice?
We will use the same logic as the one in Perlin method. We will define a sea level, for example 90, and assign every component below this as sea, above this as land. For our 8x8 matrice, I applied a color scale as follows and achieved the result:

I know it doesn't look beautiful. This is because a 8x8 matrice gives a really low resolution. As out matrice gets larger (for example a 225x255 sized matrice), the resolution gets larger and we get better terrains. I generated a contour map for our matrice and this way enhanced the resolution 16 times:

Applying This Method To Game Maker
In Game Maker, we use GRID's instead of matrices. For the calculation cell operations, we will use ds_grid_get_mean command and define a "cell range" variable.

For the first step we generate a sprite and an object, which we will use as "empty space". The color of the sprite doesn't matter, however it is better to make it square. I used a 16x16 white sprite and named my object as empty.

Now create a new script and name it as terraingen. Now we will assign some variables in the beginning to make it easier to change later. These are my variables:

nrange = 32;    //upper boundary of the random numbers.
range = 5;       //range of the calculation cell.
srange = 9;     //range of the second level calculation cell*
border = 3;    //the thickness of the area that will be left as zero (for island-like generations)
tilesize = 16;   //size of our tiles, which is same as sprite size.

*You don't have to create a secondary calculation cell here, however the more you use calculation cells, the better your terrain becomes.

Now create three grids (the actual number is n+1, where n is your calculation cell number, I have 2 cells, therefore I created 3 grids). The first one is our randomly generated source matrice, the second one is our result matrice for first calculation cell operations, and the third one is our result matrice for secondary calcualtion operations.

global.grid1 = ds_grid_create(room_width/tilesize,room_height/tilesize);
global.grid2 = ds_grid_create(room_width/tilesize,room_height/tilesize);
global.grid3 = ds_grid_create(room_width/tilesize,room_height/tilesize);

This way we created three different grids of which sizes' depend on our empty object's sprite size.
After we filled our room with our empty object (see: Useful Code Groups) now we will assign random numbers to our matrice. With a "double for loop" we can do this operation:
for (i=border;i<room_width/tilesize-border;i+=1) //horizontal working for loop
{
  for (j=border;j<room_height/tilesize-border;j+=1) //vertical working for loop
  {

    ds_grid_set(global.grid1,i,j,random(nrange));  //function that assigns random numbers
  }
}


The code above: increases i and j variables one-by-one from (border, grid size - border) interval and does ds_grid_set operation for each (i,j) coordinate inside the grid. Here, ds_grid_set operation assigns a random number from (0,32) interval for (i,j) coordinates.

Now the next step is creating our calculation cell and making it work. We will create another double-for-loop, however this time our ds_grid_set function will write over our "second grid". And our function that calculation cells operate is ds_grid_get_mean.

for (i=0;i<room_width/tilesize;i+=1)
{
   for (j=0;j<room_height/tilesize;j+=1)
     {

       ds_grid_set(global.grid2,i,j,ds_grid_get_mean(global.grid1,i-range,j-range,i+range,j+range));
     }
}


The ds_grid_get_mean function does not work as same as the one in our 8x8 matrice example. This grid function adds up all the number in the area it works on, and then divides the total to the total component number inside the area, which actually means it calculates mean value. For example when our range is 2, that means we will move 2 cells further from the middle point in four direction. If (i,j) = (8,8), than the function will work as:
ds_grid_set(global.grid2,8,8,ds_grid_get_mean(global.grid1,6,6,10,10));
This function uses the following coordinates for the operation:
(6,6)    (7,6)   (8,6)   (9,6)   (10,6)
(6,7)    (7,7)   (8,7)   (9,7)   (10,7)
(6,8)    (7,8)   (8,8)   (9,8)   (10,8)
(6,9)    (7,9)   (8,9)   (9,9)   (10,9)
(6,10) (7,10) (8,10) (9,10) (10,10)

which actually is a 5x5 matrice. Since a 5x5 matrice conist of 25 components, the function divides the total number to 25 after adding all the given components up, and assign the result to the midpoint at grid2 which is (i,j) = (8,8)

Now we will do the exact same considering grid2 as our source and grid3 destination, while using srange as our new range.

for (i=0;i<room_width/tilesize;i+=1)
{
   for (j=0;j<room_height/tilesize;j+=1)
     {

       ds_grid_set(global.grid3,i,j,10*ds_grid_get_mean(global.grid1,i-srange,j-srange,i+srange,j+srange));
     }
}

As you see we multiplied the value by 10. We did this do decrease decimal values and make better calculations, so it is not essential.

After all these, we now creates our random matrice global.grid3 using calculation cells. All these calculations take only one or two seconds, which is really fast. The speed here depends on the number of multiple calculation cell operations and range values for each.

Now it comes to create a terrain from this grid. We will now open our empty object, add a create event, add Execute Code inside and write this inside the code:
alarm[0]=2;
We have to do this because we don't want the code we will write into the empty object to be executed before the grid operations end. If the code started immediately it would try to get data from the uncalculated cells of our grid, which would cause glitches. To avoid this, we make only 2 frame delay with alarm function.

Now we will add an Alarm event with Alam0 and add an Execute Code operation again. The code we will write here will do assignments depending on elevation which is actually our calculated grid3's values. The codes I used and explanations are as follows:
kolo = ds_grid_get(global.grid1,x/16,y/16); //reads data from grid3, assigns to a temporary variable
w=159;  //sea upper limit
sa=160.5;  //sand upper limit
gr=164;  //grass upper limit
mo=167;  //moutain upper limit
bmo=169; //high mountain upper limit

(kolo < w)
{instance_change(water1,true);}  //if below sea limit, transform into water

if (kolo > w) and (kolo < sa)  
{instance_change(sand,true);}  //if between sea and sand limits, transform into sand

if (kolo > sa) and (kolo < gr)
{instance_change(grass,true);}  //if between sand and grass limits, transform into grass

if (kolo > gr) and (kolo < mo)
{instance_change(mount,true);}  //if between grass and mountain limits, transform into mountain

if (kolo > mo)
{instance_change(bigmount,true);}  //if above mountain limits, transform into high mountain


Now let us create an empty room, for example a 3600x3600 sized one! And then make it run your terraingen script. You can create a new controller object and put into the room or just add into room's creation code. The room size should be proportional to the your tilesize variable to get better results. You can try different range and srange values to see how it differs. My results are as follows for three different variable couples.

    range=5;srange=9;
    range=9;srange=9;
    range=6;srange=15;

Thursday, August 1, 2013

Random Terrain Generation Using Perlin Noise


Noise... I know It doesn't sound good, but the noise I will talk about is something a little different, actually an "useful" noise.

What Is Perlin Noise?
Noise is not only something that is auditory, and Perlin Noise is not an auditory noise, but a visual one. On empty channels of our oldy televisions, we used to see repeatedly moving black and white points on the screen, that are called "static" or "noise". Perlin Noise takes one of those pointy pictures, stacks them on top of another a few times this way it generates a cloud-like grayscale image out of the noise. It sounds a little complex huh?

Stack them on top of another?
Well, this doesn't sound good, either, but if you don't know what Perlin Noise is, you will be surprised when you see the results. First of all, I will show you a 240x240 pixel sized black-white noise. Then I will choose a smaller (actually half) sized area inside it. Then choose even smaller one (quarter), and smaller (1/8), and so on.



The figure on the left shows the areas I choose, and on the right is my 240x240 noise. Now let's choose an area of 240x240 pixels from the noise, which will cover the whole noise, and name it as n0. Then choose a smaller one, 120x120 pixels, and name it n1. Following this method, we will pick our n2, n3, n4 ,n5 and n6.




Now we will expand all the smaller squares into the size of the largest one: 240x240, which means we will have 7 images at same size. Then we will put these images on top of another, however we want all the images to be visible a the same time. To accomplish this, we will change the alpha (transparency) values of our images. Most graphic editors use alpha values between 0 and 255, but in Game Maker this interval is 0 and 1. If an image has an alpha value of 0, that means the image is totally invisible, and 1 means it is visible. We have 7 images, therefore if we give each image an alpha value of 0.14, when we stack them up the image will have a total of 0.98 alpha, which is clearly visible. After changing alpha values and stacking the images, the resulting image is like the one on the right, which is actually likable.

How to create terrain from Perlin Noise?
I've explained how does a Perlin Noise created and what it looks like. To create a terrain out of this grayscale image, we will assign different values for different shades of grey. Think about any pixel on the Perlin Noise; we will check how "white" that pixel is and assign a value between 0 and 255 accordingly. Considering 0 is totally black and 255 white, we will decide the sea level; let's make it 140 for now. From now on, we know that any pixel that has a white-ness below 140 will be considered as sea and painted blue. And any pixel above 140 will be ground and painted green. This way, we will be created a terrain-looking image from plain noise. This method can be applied in 3D too, but of course it will be more complex. Minecraft uses Perlin Noise to generate random 3D maps.


Generating Perlin Noise Via Game Maker
Firstly we should create a noise, which is pretty easy.Create a spriteless control object, it will be needed later. Next create a 1x1 white sprite and create an "pixel" object with this sprite. For the object, add "Create" event and insert an "Execude Code". Inside the code write the following:
image_blend = choose(c_white,c_black);
This will make the pixel object automatically paint itself to either white or white after created. Like I told at Useful Code Groups page, fill your room with pixel object using control object you created before. Now create a square room, and put only your control object here. When you start your game, you would see your created noise. By adding a "Restart Game" command into your control object for any key on the keyboard, you can restart game and see that it is totally random.

Next step is dividing our noise into smaller pieces and save each piece as a new sprite. Doing this is a little complex, I hope I can explain clearly. We said that our first noise is n0, we can assign it as a sprite with this code:
global.noise0 = sprite_create_from_screen(0,0,room_width,room_height,false,false,0,0);
This command takes an area from the screen and saves it as a new sprite for the name "global.noise0". Now we have a global sprite whose left-top corner is at (x,y)=(0,0) and size is the same as the room. Making the name "global" is important, this way we will be able to call it from any object.

Repeating the same thing we will create our noise1, but this time left-top point should be random and size of the square should be the half of the noise0's. Now be careful: imagine that we divided the image n0 into four equal pieces. If our smaller n1 square's left-top corner stays inside the left-top piece of n0, then the n1 square will always be inside n0. See the left figure below: there is our n1 red square with a star at left-top corner. Now imagine you hold that star inside the yellowish area and move the red square anywhere with it. If star doesn't leave the yellowish area, red square never gets out of the n0. Same logic works differently for other smaller squares, see image on the right for noise2.

Then when defining the new square, we should assign the left-top corner in a random place between 0 and A/2, and make the size of the square A/2. We can use irandom(x) command to assign random wholenumbers.

global.noise1 = sprite_create_from_screen(irandom(room_width/2),irandom(room_height/2),room_width/2,room_height/2,false,false,0,0);

When creating noise2, we should be careful that side's width will be A/4 and motion area for the left-top corner will be larger since size of the square will be smaller (check figure on the right above). Now the area for the left-top corner will be between 0 and 3*A/4. If you are good at math, here is a general formula for this considering N is noise number:

global.noise(N)= sprite_create_from_screen(irandom((2^(N) - 1)*room_width/(2^(N)),irandom((2^(N) - 1)*room_height/(2^(N))),room_width/(2^(N)),room_height/(2^(N)),false,false,0,0);

Using this formula, we achieve the following codes below:
global.noise0 = sprite_create_from_screen(0,0,room_width,room_height,false,false,0,0);
global.noise1 = sprite_create_from_screen(irandom(room_width/2),irandom(room_height/2),room_width/2,room_height/2,false,false,0,0);
global.noise2 = sprite_create_from_screen(irandom(3*room_width/4),irandom(3*room_height/4),room_width/4,room_height/4,false,false,0,0);
global.noise3 = sprite_create_from_screen(irandom(7*room_width/8),irandom(7*room_height/8),room_width/8,room_height/8,false,false,0,0);
global.noise4 = sprite_create_from_screen(irandom(15*room_width/16),irandom(15*room_height/16),room_width/16,room_height/16,false,false,0,0);
global.noise5 = sprite_create_from_screen(irandom(31*room_width/32),irandom(31*room_height/32),room_width/32,room_height/32,false,false,0,0);
global.noise6 = sprite_create_from_screen(irandom(63*room_width/64),irandom(63*room_height/64),room_width/64,room_height/64,false,false,0,0);

If you don't want to write too long, you can write the code for noise0 and then use a FOR LOOP for the rest of the noises. Something like: for (N=1;N<7;N+=1){GENERAL FORMULA}

We created sprites from the noise, now we don't need all these pixel objects, create a new room or delete them all. with your control object. Now it's time to stack these sprites with changing their alpha values and sizes; then we need draw_sprite_stretched_ext command. Technically the alpha value for each should be 0.14, but the values I gave below gives the best results:
draw_sprite_stretched_ext(global.noise0,0,0,0,room_width,room_height,c_white,0.2);
draw_sprite_stretched_ext(global.noise1,0,0,0,room_width,room_height,c_white,0.2);
draw_sprite_stretched_ext(global.noise2,0,0,0,room_width,room_height,c_white,0.3);
draw_sprite_stretched_ext(global.noise3,0,0,0,room_width,room_height,c_white,0.3);
draw_sprite_stretched_ext(global.noise4,0,0,0,room_width,room_height,c_white,0.5);
draw_sprite_stretched_ext(global.noise5,0,0,0,room_width,room_height,c_white,0.5);
draw_sprite_stretched_ext(global.noise6,0,0,0,room_width,room_height,c_white,0.4);

Numbers at the end of the each code are alpha values. Giving different numbers will alter your perlin noise, for example making alpha for noise0 will make your noise more ribbed.

Perlin Gürültüsünden Arazi Elde Etmek
Now we will choose a sea level and paint the noise accordingly. This step will be hard for both Game Maker and your computer. I'm sure there are simplier methods to do these, but my capacity is not enough for now -_-'. I use a room with sizes 320x320, which makes 102400 pixel object total. Making 102400 object re-color themselves takes 3 - 5 minutes. It takes long because while controlling the "whiteness" of a pixel, Game Maker directly uses your monitor to work and achieve data about the pixel. Of course, if your computer is faster, the time will be shorter.

There are a lot of alternatives for coloring. I've created another control object to create a height map, too. It runs itself after coloring finished and gives some height visually. This is the code I use:

if (p < room_width)  //If my control variable is smaller then room width;
{
for (i=0;i<room_width;i+=1) //scans the room horizontally
{
for (j=p;j<(p+1);j+=1)  //scans the pixels at line i
{
pointed = color_get_red(draw_getpixel(i,j));   //checks the color of the pixel at coordinates (i,j)
global.tema[i,j] = pointed;  //saves (i,j) pixels color value to the "tema" matrix for height map

if (pointed<140)   //if that value is below 140
{instance_create(i,j,water);}   //creates a water object
else
{instance_create(i,j,ground);}   //if not, creates a ground object instead
}
}
p += 1;  //restarts the process by adding 1 to the control variable
alarm[0] = 2;
}
else
{global.terra = 1;} //if control variable is larger than the room width, starts the height map

Using my "tema" matrix I define the height, and accordingly make water and ground objects lighter or darker. I also put a collision check command inside the ground objects that will transform them into sand objects if they are near water. Here are the results:



I think it looks good, but it is really slow. I'm working on a more comfortable and fast system about this these days, and it goes pretty well. I will write about it in my next entry. You can send email to me if you have questions, requests or recommendations, I appreciate it. :)

You can find the GMK file here:
Perlin_Gurultusu_Rastgele_Arazi.gmk