Jump to content


Photo

Generating terrain by using a heightmap


  • Please log in to reply
24 replies to this topic

#1 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 24 March 2011 - 07:52 PM

Generating terrain by using a heightmap

This is a translated version of my Dutch tutorial. If you are Dutch, you might prefer to read that one, because it is probably much better formulated than this one.

There already are a bunch of heightmap scripts around, so what's the advantage of these scripts?
Well, first of all, this script uses trianglelists instead of trianglestrips, which increases speed.
Second, this script defines the normals correctly, which allows you to use lighting.


Table of contents

  • Table of contents
  • What is a heightmap(and how does it work?)?
  • Making a heightmap
  • Reading a heightmap
  • Drawing the terrain
  • Finding the z-value of a position on the terrain
  • Drawing a texture over the entire terrain, instead of repeating it every cell.
  • Examples
  • FAQ
  • Final Notes



What is a heightmap(and how does it work?)?
A heightmap is a black and white(usually), topdown print of a landscape. This is a simple example: http://easy-upload.nl/f/1o4n9NPt (I can't use the IMG tag: 'You are not allowed to use that image extension on this board.' Wut? It's PNG :[ )
The heightmap is being read, and converted to an array containing the z-value of each gridpoint. These z-value's are based on the 'value'(Like in Hue, Saturation, Value) of the gridpoint(pixel).
In pseudocode it works like this:

for each pixel
{
 Height[ x, y] = Value;
 Height[ x, y] /= 255; // 255 == white, the maximum
 Height[ x, y] *= max height; // The maximal height you want
}

The maximum and minimum are now 0 and 'max height'.
A black pixel represents 0: 0/255*max height = 0.
A white pixel represents 'max height': 255/255*max height = max height.


Making a heightmap
The more creative part. You can make a heightmap by making a black sprite and enthusiasticly start drawing grey pixels on it, but that makes it really hard to get a nice result.
Instead, use a Terrain Editor, such as EarthSculptor(free version is good enough). You can export a heightmap by clicking Export>Something-with-Heightmap.


Reading a heightmap
So how are we going to read the heightmap?

First of all, we have to define two variables: gridsize and gridparts.

gridsize will be the size of a cel in our heightmap. The height/width of a triangle. Just like the gridsize in GM's room editor.

gridparts is the number of cels, horizontal and vertical. Remember: your heightmap image should always be 1 pixel bigger then the number of cels you want, since a pixel represents the height of a corner of a triangle, not a cel itself.
So if you want a 50x50 terrain, use a heightmap of 51x51 pixels.

Here is an image for those of you who didn't understand:
http://easy-upload.nl/f/2eHk63Rk

The total size of the terrain is equal to gridsize*gridparts, but the size of the heightmap is gridparts+1.
In my script, the value of gridparts is based on the size of the sprite(instead of manually defining it), and gridsize is based on room_width and gridparts.

So, this is the first part of our script:
// terrain_create(heightmap)
//
// heightmap: the sprite to be used as heightmap

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

Now we should read the height value's. First we draw the sprite using draw_sprite(which means you'll have to execute this script before initialising D3D(by calling d3d_start()). Then we'll read the value's and calclulate the height.
draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) { 
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}
As you can see, I added argument1. This is the maximum height('white'). Our script is now:
// terrain_create(heightmap,maxheight)
//
// heightmap: de sprite van de heightmap die gebruikt moet worden
// maxheight: de maximale hoogte van het terrain

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) { 
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}
Now we have the height value's. But we still don't have a model to draw. So now we are going to construct a model using trianglelists:
globalvar terrain;
terrain = d3d_model_create();

for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i,j+1);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i,j);
   }
  d3d_model_primitive_end(terrain);
 }
I used another script, terrain_get_normal. This scripts calculates the normals, so your models get shaded correctly when Lighting is enabled. Here it is:
// terrain_get_normal(x,y)
// the result is saved in global.xx, global.yy and global.zz
globalvar gridsize;
var d;
global.xx = terrain_get_z(argument0-gridsize,argument1)-terrain_get_z(argument0+gridsize,argument1);
global.yy = terrain_get_z(argument0,argument1-gridsize)-terrain_get_z(argument0,argument1+gridsize);
global.zz = gridsize*2;
d = sqrt(sqr(global.xx)+sqr(global.yy)+sqr(global.zz));
global.xx /= d;
global.yy /= d;
global.zz /= d;
This script also uses another scipt, terrain_get_z. This will be explained later in this tutorial.

The final script:
// terrain_create(heightmap,maxheight)
//
// heightmap: de sprite van de heightmap die gebruikt moet worden
// maxheight: de maximale hoogte van het terrain

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) { 
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}

globalvar terrain;
terrain = d3d_model_create();

for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i,j+1);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i,j);
   }
  d3d_model_primitive_end(terrain);
 }


Drawing the terrain
All what's left is drawing the terrain:
// terrain_draw(tex)
//
// tex: the texture to be applied to the model
//
// Note: use background_get_texture(tex) on the model first.

texture_set_repeat(true);
d3d_model_draw(terrain,0,0,0,argument0);
(this is probably the most complicated script in the entire tutorial)


Finding the z-value of a position on the terrain
But we're not finished yet. It would be nice if you were able to make your character(or whatever) stand 'on' the heightmap/

We will need another script for that. Maybe you remember we made a global array height. I did not var(var height;) it on purpose, because we'll be using it now.

First we determine on which cel we are:

var gridx, gridy;
gridx=floor(argument0/gridsize)
gridy=floor(argument1/gridsize)


Then we look where on the cel the position is:

var offsetx, offsety;
offsetx= argument0-gridsize*gridx
offsety= argument1-gridsize*gridy


Then we request the z-positions of the corners of the cel:

var z1, z2, z3, z4;
z1=height[gridx,gridy]
z2=height[gridx+1,gridy]
z3=height[gridx+1,gridy+1]
z4=height[gridx,gridy+1]


And using these variables we calculate the z:

var zz;
if offsetx>offsety
zz=z1 - offsetx*(z1-z2)/gridsize - offsety*(z2-z3)/gridsize
else
zz=z1 - offsetx*(z4-z3)/gridsize - offsety*(z1-z4)/gridsize


The final script:
// terrain_get_z(x,y)
//
// x: the x-position
// y: err..

globalvar gridparts, gridsize, height;
var gridx, gridy, offsetx, offsety, z1, z2, z3, z4, zz;

gridx = max(0,min(gridparts-1,floor(argument0/gridsize))); //min/max stuff so you won't get errors if the position is outside the terrain
gridy = max(0,min(gridparts-1,floor(argument1/gridsize)));

offsetx = argument0-gridsize*gridx;
offsety = argument1-gridsize*gridy;

z1=height[gridx,gridy];
z2=height[gridx+1,gridy];
z3=height[gridx+1,gridy+1];
z4=height[gridx,gridy+1];

if offsetx>offsety
 zz=z1 - offsetx*(z1-z2)/gridsize - offsety*(z2-z3)/gridsize;
else 
 zz=z1 - offsetx*(z4-z3)/gridsize - offsety*(z1-z4)/gridsize;

return zz;


Drawing a texture over the entire terrain, instead of repeating it every cell.
To do this, replace the lines with the d3d_model_vertex_normal_texture stuff by:
for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i/gridparts,(j+1)/gridparts);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i/gridparts,j/gridparts);
   }
  d3d_model_primitive_end(terrain);
 }
}


Examples
Here are two examples how to use this ingame:

Example 1
Download
This example shows the basics.

Example 2
Download
This example shows how to make the player walk on the terrain, and how to deal with gravity.


FAQ
A short FAQ.
(got a question? Feel free to ask)

Q: My game crashes when loading the heightmap.
A: Your heightmap is probably to big. The bigger the better of course, but loading time increases exponentially. Use a size of maximal 51*51, 31*31 preferred.

Q: My game lags when drawing the terrain.
A: Your texture is probably to big. Use maximal 1024*1024.

Q: My terrain isn't drawn.
A: Well, the scripts work. Download one of the example's, and look for differences between your and my code.


Final Notes
Heightmap-based terrain can be darn nice, but also darn slow.
Enjoy, but use with care.


Sincerly,
BlueMoon
  • 2

#2 lemonheadsg

lemonheadsg

    GMC Member

  • GMC Member
  • 186 posts

Posted 24 March 2011 - 08:11 PM

This is very, very impressive, it shows just hot powerful game maker can be. I haven't tested this but it seems like it would be more powerful with a 3d DLL like gmogre, but besides that this is a great example, and I can already seem dozens of games being made of this. Great job BlooMoonProductions!
  • 0

#3 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 24 March 2011 - 08:40 PM

Thanks! GM is more powerful then a lot of people seem to think, indeed.
But.. What did you call me? D=<
  • 0

#4 Alvare

Alvare

    Allrounder

  • GMC Member
  • 1299 posts
  • Version:GM:Studio

Posted 24 March 2011 - 08:45 PM

Thanks! GM is more powerful then a lot of people seem to think, indeed.
But.. What did you call me? D=<

Lol.. hahahahahah :lol:
  • 0

#5 The Scorpion

The Scorpion

    GMC Member

  • GMC Member
  • 421 posts
  • Version:GM8

Posted 25 March 2011 - 07:45 AM

Haven't I seen a LOT of simular engines ? :huh:
This have been made like years ago! (certainly Brett14)
The only thing I appreciate is the Normal calculation system!
Any way, May be I've missed Something Above!

Edited by The Scorpion, 25 March 2011 - 07:49 AM.

  • 0

#6 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 25 March 2011 - 08:46 AM

I know:

There already are a bunch of heightmap scripts around, so what's the advantage of these scripts?
Well, first of all, this script uses trianglelists instead of trianglestrips, which increases speed.
Second, this script defines the normals correctly, which allows you to use lighting.


  • 0

#7 The Scorpion

The Scorpion

    GMC Member

  • GMC Member
  • 421 posts
  • Version:GM8

Posted 25 March 2011 - 09:17 AM

Ok, then, keep it UP! why don't you add muli tex support!
It may Advance your Engine! (I've got an Idea if interrested)

PLUS why do you use (terrain_get_z) in (get_normal)? get your values directly from the grid, it will increase speed of rendering the terrain :)
  • 0

#8 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 25 March 2011 - 09:29 AM

I'm planning to rewrite the scripts soon. I will merge the terrain_create en terrain_create_stretched scripts to one script with hrepeat and vrepeat arguments. I'll also take a look at speed issue's.
  • 0

#9 youaredead

youaredead

    GMC Member

  • New Member
  • 33 posts

Posted 23 April 2011 - 07:55 AM

The links don't work!
  • 0

#10 Alvare

Alvare

    Allrounder

  • GMC Member
  • 1299 posts
  • Version:GM:Studio

Posted 23 May 2011 - 07:15 PM

This deserves a bump.
  • 0

#11 jhett6

jhett6

    GMC Member

  • New Member
  • 54 posts

Posted 18 June 2011 - 06:35 PM

can one of you guys just make this engine for me than send me it? my email is: jhett6@hotmail.com THX!!!
  • 0

#12 Alvare

Alvare

    Allrounder

  • GMC Member
  • 1299 posts
  • Version:GM:Studio

Posted 19 June 2011 - 10:04 AM

can one of you guys just make this engine for me than send me it? my email is: jhett6@hotmail.com THX!!!

Just take a look at what there is written above. B-)
  • 0

#13 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 19 June 2011 - 11:30 AM

Here's the second example: http://updo.nl/file/73c19dcd.gmk
  • 0

#14 youaredead

youaredead

    GMC Member

  • New Member
  • 33 posts

Posted 09 July 2011 - 10:52 AM

You're awesome!
  • 0

#15 sneaky666

sneaky666

    536e65616b79

  • GMC Member
  • 1047 posts
  • Version:GM:Studio

Posted 21 July 2011 - 12:26 AM

is this still being updated?
  • 0

#16 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 05 August 2011 - 07:53 PM

I'm working on it.
  • 0

#17 bbman1999

bbman1999

    GMC Member

  • GMC Member
  • 187 posts
  • Version:GM8

Posted 19 August 2011 - 04:03 AM

humm i just wanted a way to make a fake 3d thing by using blocks and i wasn't really looking for this kind of thing.... how do i make it so i use blocks and stuff?
  • 0

#18 TheSnidr

TheSnidr

    That guy

  • Global Moderators
  • 3019 posts
  • Version:GM:Studio

Posted 06 November 2011 - 01:01 PM

Thanks a lot for this tutorial! It's been a while since I used it, but I did for some reason not comment sooner. This helped me a lot in understanding how heightmaps work and how to read them, and I've recommended this tutorial to others several times. Good job!

Edited by TheSnidr, 06 November 2011 - 01:02 PM.

  • 0

#19 goldage5

goldage5

    GMC Member

  • GMC Member
  • 152 posts

Posted 04 January 2012 - 01:30 AM

Both of the links to the examples do not work for me.
  • 0

#20 Yal

Yal

    Gun Princess

  • Global Moderators
  • 8497 posts
  • Version:GM:Studio

Posted 08 February 2012 - 09:07 AM

Both of the links to the examples do not work for me.

And what you're really saying is:

I don't want to learn using heightmaps. I just want to download code someone else has written and use that because I'm too lazy to do it myself

Hey, all the code is in the original topic, you should be able to copy-paste that with little effort.


Broken links are always a nuiscance; I'd recommend the original poster to use a safer site (with infinite hosting time) to host on; my suggestion is www.box.net which is supported by Google.
  • 0

#21 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 09 February 2012 - 02:15 AM

Broken links are always a nuiscance; I'd recommend the original poster to use a safer site (with infinite hosting time) to host on; my suggestion is www.box.net which is supported by Google.


The service I used at that moment seemed very reliable. Sorry about the files not being available.

I should have the files somewhere, and I will upload them sooner or later, but I'm just so busy at the moment ..
  • 0

#22 Pandaboy

Pandaboy

    GMC Member

  • GMC Member
  • 1164 posts
  • Version:GM:Studio

Posted 28 November 2012 - 08:02 PM

please update images and download link, it's all broken.
Use dropbox, it's reliable.

Edited by Pandaboy, 28 November 2012 - 08:03 PM.

  • 0

#23 cadekaito

cadekaito

    GMC Member

  • GMC Member
  • 76 posts
  • Version:GM:Studio

Posted 01 August 2013 - 02:06 AM

I tried this, but when it finally loads, all it shows is a white screen. Do you know what im doing wrong?


  • 0

#24 Dsbrowder

Dsbrowder

    GMC Member

  • GMC Member
  • 51 posts
  • Version:GM:Studio

Posted 06 August 2013 - 01:49 PM

Hi,

Could not get any of this to work in Gamemaker studio pro , Numerous errors.

 

Has anyone been able to get it to work in Gamemaker studio? If so  perhaps you could upload a working file.

 

All links are broken.


  • 0

#25 Nark

Nark

    GMC Member

  • GMC Member
  • 170 posts
  • Version:GM:Studio

Posted 04 October 2013 - 11:32 AM

Just gotta say the past 2 days I've been banging my head off the table trying to get an entities Z from terrain, aswell as trying to get my normals correctly calculated.  Reading through these scripts has helped me fix both of these issues.  Thanks a lot. :)


  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users