Lighthouse3d.com

 Terrain Tutorial Index A TGA Library A Simple TGA Library TGA lib Source Height Maps Height Maps from Images Artificial Terrain Generation The Fault Algorithm Implementation Details Two Simple Variations The Circles Algorithm Smoothing Smoothing Matrix filters API details

# Terrain Tutorial

## Height Maps From Images

Now that we know how to load an image lets see how to create and draw a height map. This section presents a set of functions from the terrain library that deal with height maps. First lets see how we can build a height map from an image.

There are several ways to interpret an image as an height map. If the image is greyscale the process is more or less straightforward, a pixels intensity corresponed to a height.

If the image is RGB then we can convert it to greyscale using the function tgaRGBtoGreyscale from the tgalib (see the tgalib section). Then we can use the Greyscale as before.

The other possible type is RGBA, in which case we can create a height map from the alpha channel, and use the RGB for the terrain colors.

Before presenting the functions lets take a look at the possible status returned:
• TERRAIN_ERROR_MEMORY_PROBLEM - Oops, no memory to hold the terrain data
• TERRAIN_ERROR_NOT_SAVED - There was a problem saving the file
• TERRAIN_ERROR_NOT_INITIALISED - occurs when a function to act upon the terrain is called but there is no terrain!
• TERRAIN_OK - This what we want, no errors!
• We need some variables to hold the terrain data:
• terrainGridWidth - the number of heights available on the x axis.
• terrainGridLength - the number of heights available on the y axis.
• terrainHeights - A one-dimensional array containing the heights
• terrainColors - A one-dimensional array containing the RGB colors for each height
• terrainNormals - A one-dimensional array containing the normals for each height
• Note that the terrainColors and terrainNormals arrays contain 3 times the number of components of terrainHeights. This is because we don't store the location in the XZ plane of each height in the array.

The function to create the height map based on an image has the following signature:

Parameters:
filename - the name of the image file
normals - a non-zero value specifies that normals are to be calculated, zero means that we don't want normals

The value returned by this function indicates if the operation was successful or not. This function computes the heights as the pixels intensity scaled down to the interval [0,1]. The code that follows shows the steps required to create the height map. The image loading is performed by the tgalib presented in the previous sections.

```    ```
int terrainLoadFromImage(char *filename, int normals) {

tgaInfo *info;
int mode,aux;
float pointHeight;

// if a terrain already exists, destroy it.
if (terrainHeights != NULL)
terrainDestroy();

// load the image, using the tgalib

// check to see if the image was properly loaded
// remember: only greyscale, RGB or RGBA noncompressed images
if (info->status != TGA_OK)
return(info->status);

// if the image is RGB, convert it to greyscale.
// mode will store the image's number of components
mode = info->pixelDepth / 8;
if (mode == 3) {
tgaRGBtoGreyscale(info);
mode = 1;
}

// set the width and height of the terrain
terrainGridWidth = info->width;
terrainGridLength = info->height;

// alocate memory for the terrain, and check for errors
terrainHeights = (float *)malloc(terrainGridWidth *
terrainGridLength *
sizeof(float));
if (terrainHeights == NULL)
return(TERRAIN_ERROR_MEMORY_PROBLEM);

// allocate memory for the normals, and check for errors
if (normals) {
terrainNormals = (float *)malloc(terrainGridWidth *
terrainGridLength *
sizeof(float) * 3);
if (terrainNormals == NULL)
return(TERRAIN_ERROR_MEMORY_PROBLEM);
}
else
terrainNormals = NULL;

// if mode = RGBA then allocate memory for colors, and check for errors
if (mode == 4) {
terrainColors = (float *)malloc(terrainGridWidth *
terrainGridLength *
sizeof(float)*3);
if (terrainColors == NULL)
return(TERRAIN_ERROR_MEMORY_PROBLEM);
}
else
terrainColors = NULL;

// fill arrays
for (int i = 0 ; i < terrainGridLength; i++)
for (int j = 0;j < terrainGridWidth; j++) {

// compute the height as a value between 0.0 and 1.0
aux = mode*(i*terrainGridWidth + j)
pointHeight = info->imageData[aux+(mode-1)] / 256.0;
terrainHeights[i*terrainGridWidth + j] = pointHeight;

// if mode = RGBA then fill the colors array as well
if (mode==4) {
terrainColors[3*(i*terrainGridWidth + j)] =
info->imageData[aux] / 256.0;
terrainColors[3*(i*terrainGridWidth + j)+1] =
info->imageData[aux+1]/256.0;
terrainColors[3*(i*terrainGridWidth + j)+2] =
info->imageData[aux+2]/256.0;
}
}
// if we want normals then compute them
if (normals)
terrainComputeNormals();

// free the image's memory

return(TERRAIN_OK);
}
```
```

The normals computation will be dealt in future sections.

Before going into the rendering function lets take a look at two other functions to scale the terrain. The first one scales the heights of the terrain. As mentioned before when creating a height map from an image, the heights are in the interval [0,1]. In the general case we'll probably want to specify the minimum and maximum heights. The terrain lib provides a function to this end with the following syntax:

int terrainScale(float min,float max);

Parameters:
min - the minimum height
max - the maximum height

This functions rescales all the heights to fit in the interval [min, max]. There is only one detail which is worth pointing out in this function: if using normals it is necessary to rescale them. This is because the scale applied is not uniform, i.e., it is not equal for all the axes, and therefore the normals previously computed are no longer correct. The function will recompute the normals after scaling the terrain.

The other scaling function defines the interval between two adjacent grid points. By default the distance is 1.0.

int terrainDim(float stepWidth, float stepLength);

Parameters:
stepWidth - the x distance between two grid points
stepLength - the z distance between two grid points

So now that we have a terrain, lets render it! The terrain library creates a display list using triangle strips for the height map. This function takes 3 parameters, which represent the origin of the terrain. If all parameters are zero then the terrain will be created at the local origin. Otherwise we can use these parameters to place the terrain in any position.

This function looks at the normals array to decide if using materials or just glColor3f. Therefore, normals should not be required when creating the height map if the application is not going to use lighting, otherwise the colors will be lost. Similarly strange results may occur if lighting is enabled in the application but the normals are not computed.

The syntax of the rendering function is as follows:

int terrainCreateDL(float xOffset, float yOffset, float zOffset);

Parameters:
xOffset - specifies the center of the terrain on the x direction
yOffset - specifies the y value of the points with zero height

Next the code of the function is presented.

```    ```
int terrainCreateDL(float xOffset, float yOffset, float zOffset) {

GLuint terrainDL;
float startW,startL;
int i,j,aux;

// compute the initial point of the terrain on the XZ plane
startW = terrainGridWidth / 2.0 - terrainGridWidth;
startL = - terrainGridLength / 2.0 + terrainGridLength;

// Create the id for the display list
terrainDL = glGenLists(1);

// create the display list
glNewList(terrainDL,GL_COMPILE);

// test for normals
if (terrainNormals != NULL) {
glColorMaterial(GL_FRONT, GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}

// generate n-1 strips, where n = terrainGridLength
// for each vertex test if colors and normals are enabled
for (i = 0 ; i < terrainGridLength-1; i++) {
glBegin(GL_TRIANGLE_STRIP);
for (j = 0;j < terrainGridWidth; j++) {
aux = 3*((i+1)*terrainGridWidth + j);
if (terrainColors != NULL)
glColor3f(terrainColors[aux],
terrainColors[aux+1],
terrainColors[aux+2]);
if (terrainNormals != NULL)
glNormal3f(terrainNormals[aux],
terrainNormals[aux+1],
terrainNormals[aux+2]);
glVertex3f(
startW + j + xOffset,
terrainHeights[(i+1)*terrainGridWidth + (j)] + yOffset,
startL - (i+1) + zOffset);

aux = 3*(i*terrainGridWidth + j);
if (terrainColors != NULL)
glColor3f(terrainColors[aux],
terrainColors[aux+1],
terrainColors[aux+2]);
if (terrainNormals != NULL)
glNormal3f(terrainNormals[aux],
terrainNormals[aux+1],
terrainNormals[aux+2]);
glVertex3f(
startW + j + xOffset,
terrainHeights[(i)*terrainGridWidth + j] + yOffset,
startL - i + zOffset);
}
glEnd();
}
glEndList();

// return the list index so that the application can use it
return(terrainDL);
}
```
```

In an application using the terrain lib we must first create the terrain and the display list.

```    ```
//load an image and compute the normals
if ((status = terrainLoadFromImage("3dtech.tga",1)) != TERRAIN_OK)
// do something, there was an error
else
//scale the terrain so that the height varies from
// -10 to 30
terrainScale(-10,30);
// create a terrain centered on the origin
myDisplayList = terrainCreateDL(0,0,0);

```
```

Then, when rendering just call the display list

```    ```
glCallList(myDisplayList);
```
```

The last function to be presented in here provides a way to get the height for a particular XZ point. The syntax is as follows:

float terrainGetHeight(int x, int z);

Parameters:
x - the x coordinate assuming that the terrain is centered on the XZ plane, and the the spacing between points is 1.
z - the z coordinate assuming that the terrain is centered on the XZ plane, and the the spacing between points is 1.

The return value is the height at the requested point.

A simple example application is provided in here. The application is based on the glut examples provided in the glut tutorial. Use the arrow keys to navigate in the terrain. The mouse, when a button is pressed, will allow you to freely look at the scene and change your orientation. Pressing the arrow keys after the mouse button is pressed will speed up your movement. An image is provided as an example as well as some screen shots taken with the application.

The image from where the height map is created

The rendered height map

A detail of the height map