CS409/CS809 Tutorial 10: Heightmaps

Setting up an OpenGL Project with the Obj Library of Files

1.      Start Microsoft Visual Studio 2013.  Select File / New / Project.  Under “Project Type”, choose Templates / Visual C++. Then choose a Win32 Console Application.  Change the name to Tutorial10 or some suitable name and change the location to a hard drive located on the computer (typically C or D).  You will need to remember this location for steps 3 and 5.  Make sure the “Create folder for project” checkbox is not checked and that the “Add to source control” is not checked.  Click OK.

·         If the Chose Location window keeps popping up again, you aren’t allowed to save files at the location you chose.  Select a different location, such as the desktop.

·         Do not put your program on one of the external drives like H.  The connection is slow, and your program may not compile correctly.

2.      Click Next> or Application Settings (they go to the same place).  Check the “Empty Project” box.  Make sure the “Security development lifecycle check” is not checked.  Click Finish.

3.      Go to http://www2.cs.uregina.ca/~anima/409/Terms/201810/Tutorials/index.html and download the Tutorial10.zip and ObjLibrary.zip files to your project directory.

4.      Unzip the 2 zip files.  You will have a folder called ObjLibrary and 12 other files.

5.      In Visual C++, go to Project/Add Existing Item… and select main10.cpp (and holding control, continue to select), Sleep.h, Sleep.cpp, and GetGlut.h.

6.      Ensure that Solution Explorer is visible (View / Solution Explorer). In the Solution Explorer, select Tutorial10 (the project name) and right click it.  Choose Add / New Filter.  A new folder icon should appear in the Solution Explorer.  Name it ObjLibrary.  (This creates an imaginary folder in the project, but it would not create a Windows folder on the computer).

7.      In the Solution Explorer, right click on the ObjLibrary folder icon and select Add / Existing item.  Go into the ObjLibrary folder on Windows and select all files.  The files should appear in the Solution Explorer inside the ObjLibrary folder.  You should get 26 files, although you will not be using most of them for this tutorial.

8.      Compile and run the original program.  You should see 2 windows: a text console and a graphical window labeled “Constructing a Heightmap”.  There is a set of positive X/Y/Z axes and a wire-frame cube in the middle of this second window.  Exit the windows.  Closing the text window will close the graphical window, and usually vice versa.

9.      For the remainder of the instructions, it is assumed that you compile and run regularly so that you can see what each change does.

Generate the Heightmap Geometry

10.  Add this code to the display function:
  glColor3d(1.0, 1.0, 1.0);
  glBegin(GL_TRIANGLE_STRIP);
       glVertex3d(1.0, 0.0, 0.0);
       glVertex3d(0.0, 0.0, 0.0);
       glVertex3d(1.0, 0.0, 1.0);
       glVertex3d(0.0, 0.0, 1.0);
  glEnd();
You should see a large white square partly inside the purple wireframe cube.  It is composed of 2 triangles.  A triangle strip uses each consecutive set of 3 vertexes to draw a triangle.  This means N vertexes results in N - 2 triangles.  The triangles are specified with a winding order alternating between counter-clockwise and clockwise.  This turns out to be very conveniently for making strips.

11.  Add this code to the display function immediately before the glEnd line for the tetrahedron:
       glVertex3d(1.0, 0.0, 2.0);
       glVertex3d(0.0, 0.0, 2.0);
       glVertex3d(1.0, 0.0, 3.0);
       glVertex3d(0.0, 0.0, 3.0);
       glVertex3d(1.0, 0.0, 4.0);
       glVertex3d(0.0, 0.0, 4.0);
       glVertex3d(1.0, 0.0, 5.0);
       glVertex3d(0.0, 0.0, 5.0);
       glVertex3d(1.0, 0.0, 6.0);
       glVertex3d(0.0, 0.0, 6.0);
       glVertex3d(1.0, 0.0, 7.0);
       glVertex3d(0.0, 0.0, 7.0);
       glVertex3d(1.0, 0.0, 8.0);
       glVertex3d(0.0, 0.0, 8.0);
The square should now be replaced by a long rectangle composed of 14 triangles.

12.  Observe that the vertex lines are repeated pairs.  We can replace them with a loop:
  glBegin(GL_TRIANGLE_STRIP);
       for(unsigned int z = 0; z <= 8; z++)
       {
            glVertex3d(1.0, 0.0, z);
            glVertex3d(0.0, 0.0, z);
       }
  glEnd();
The rectangle should appear the same as before.

13.  Add an outer loop to display many strips:
  for(unsigned int x0 = 0; x0 < 8; x0++)
  {
       unsigned int x1 = x0 + 1;
       glBegin(GL_TRIANGLE_STRIP);
            for(unsigned int z = 0; z <= 8; z++)
            {
                 glVertex3d(x1, 0.0, z);
                 glVertex3d(x0, 0.0, z);
            }
       glEnd();
  }
You should now see a large white square.  Note that the inner loop has a <= condition, while the outer loop has a < condition.

Apply a Texture to the Heightmap

14.  Add an #include for "ObjLibrary/TextureManager.h"
#include "ObjLibrary/TextureManager.h"
Add a
using directive for ObjLibrary after the using directive for std:
using namespace ObjLibrary;

15.  Before the code that draws the heightmap, use the glEnable function to enable textures:
  glEnable(GL_TEXTURE_2D);
After the code that displays the heightmap, use the glDisable function to disable textures
  glDisable(GL_TEXTURE_2D);

16.  After enabling textures, use the activate function in the TextureManager namespace to activate the "rainbow.bmp" file:
  TextureManager::activate("rainbow.bmp");
At this point, the square should appear as a single non-white colour.  Note that there is no need to ever deactivate a texture.  OpenGL always has exactly one active texture and activating another one replaces it.  If textures are not enabled, the active texture is ignored.

17.  Add commands to the inner loop to set the texture coordinates:
                 glTexCoord2d(x1, z);
                 glVertex3d(x1, 0.0, z);
                 glTexCoord2d(x0, z);
                 glVertex3d(x0, 0.0, z);
Make sure that the drawing colour is set to white when you use textures.  Otherwise your textures will display incorrectly.  You should see a rainbow square repeated across the heightmap.  Each rainbow square covers a square made from 2 triangles.

Add Heights to the Heightmap

18.  Add a global constant for the heightmap size after the function prototypes:
const int HEIGHTMAP_SIZE = 8;
Also add a global 2D array of floats for the heightmap heights:
float heights[HEIGHTMAP_SIZE + 1][HEIGHTMAP_SIZE + 1];

19.  Add code in the init function to initialize the heights array:
  for(unsigned int x = 0; x <= HEIGHTMAP_SIZE; x++)
       for(unsigned int z = 0; z <= HEIGHTMAP_SIZE; z++)
       {
            heights[x][z] = (x % 2) * 0.5f -
                             z * z  * 0.05f;
       }
These heights were chosen to give the heightmap a shape other than flat.  The heightmap will be able to display any height values.

20.  Change the code that displays the heightmaps to use the values in heights as the Y coordinates for the vertexes:
  for(unsigned int x0 = 0; x0 < HEIGHTMAP_SIZE; x0++)
  {
       unsigned int x1 = x0 + 1;
       glBegin(GL_TRIANGLE_STRIP);
            for(unsigned int z = 0; z <= HEIGHTMAP_SIZE; z++)
            {
                 glTexCoord2d(x1, z);
                 glVertex3d(x1, heights[x1][z], z);
                 glTexCoord2d(x0, z);
                 glVertex3d(x0, heights[x0][z], z);
            }
       glEnd();
  }
The heightmap should now slope down towards you with roof-like ridges along the slope.

21.  Change the scale of the texture so that it is only displayed once across the heightmap:
  for(unsigned int x0 = 0; x0 < HEIGHTMAP_SIZE; x0++)
  {
       unsigned int x1 = x0 + 1;
       float tex_x0 = (float)(x0) / HEIGHTMAP_SIZE;
       float tex_x1 = (float)(x1) / HEIGHTMAP_SIZE;
       glBegin(GL_TRIANGLE_STRIP);
            for(unsigned int z = 0; z <= HEIGHTMAP_SIZE; z++)
            {
                 float tex_z = (float)(z) / HEIGHTMAP_SIZE;
                 glTexCoord2d(tex_x1, tex_z);
                 glVertex3d(x1, heights[x1][z], z);
                 glTexCoord2d(tex_x0, tex_z);
                 glVertex3d(x0, heights[x0][z], z);
            }
       glEnd();
  }
You should be able to see the shape of the heightmap from the different colours in the texture.

22.  Move the heightmap to be centered on the origin.  Use the HEIGHTMAP_SIZE constant.  Remember to Push/Pop the matrix.

Create a DisplayList for the Heightmap

23.  Add an #include for "ObjLibrary/DisplayList.h"
#include "ObjLibrary/DisplayList.h"

24.  Declare a global variable (before the main function) of the DisplayList type:
DisplayList heightmap_list;

25.  In the init function, call the activate function in the texture_manager namespace for each texture you will use to load them into video memory.  If you do not do this, the display list will reload the same texture each time it is drawn.  In this case, we want to activate the rainbow.bmp  texture:
  TextureManager::activate("rainbow.bmp");
Put this line after you calculate the values for the heights array.

26.  Immediately after the activate line in the init function, use the begin and end function in the DisplayList class to define the display list.  Any drawing commands between these two calls will be stored in video memory instead of being executed.  Move the code to draw the heightmap from the draw function to between these commands:
  heightmap_list.begin();
       glEnable(GL_TEXTURE_2D);
       TextureManager::activate("rainbow.bmp");
       glColor3d(1.0, 1.0, 1.0);
       for(unsigned int x0 = 0; x0 < HEIGHTMAP_SIZE; x0++)
       {
            unsigned int x1 = x0 + 1;
            float tex_x0 = (float)(x0) / HEIGHTMAP_SIZE;
            float tex_x1 = (float)(x1) / HEIGHTMAP_SIZE;
            glBegin(GL_TRIANGLE_STRIP);
                 for(unsigned int z = 0; z <= HEIGHTMAP_SIZE; z++)
                 {
                      float tex_z = (float)(z) / HEIGHTMAP_SIZE;
                      glTexCoord2d(tex_x1, tex_z);
                      glVertex3d(x1, heights[x1][z], z);
                      glTexCoord2d(tex_x0, tex_z);
                      glVertex3d(x0, heights[x0][z], z);
                 }
            glEnd();
       }
       glDisable(GL_TEXTURE_2D);
  heightmap_list.end();

27.  In the display function, use the draw function in the DisplayList class to display the heightmap:
       heightmap_list.draw();
You will still need the
pushMatrix, glTranslate, and popMatrix commands.

28.  Make an initHeightmapHeights function that calculates the heights array.  Call this function from init after initDisplay.  Remember to add a function prototype.

29.  Make an initHeightmapDisplayList function that loads therainbow.bmp texture and initializes the heightmap_list display list.  Call this function from init after initHeightmapHeights.  Remember to add a function prototype.

Exercises

30.  Change the heightmap size so that it is composed of 12 x 12 squares instead of 8 x 8.

31.  Change the texture to ground.bmp and repeat it 3 times across the heightmap in each direction.  Use a variable to control how many times the texture is repeated.

32.  Scale the heightmap to be 10 x 10 in size in the X and Z directions.  Do not scale the Y direction.  Use HEIGHTMAP_SIZE to determine the scaling needed.  Change the horizontal position of the heightmap to keep it centered on the origin.

33.  Change the program to display 16 heightmaps arranged in a 4 x 4 grid.  The edges of the heightmaps should be side by side in X and Z coordinates.  However, they will not touch in the Z direction because the heights will be different.

34.  Change the formula for calculating the heights array.  Start by creating a 1D array of floats with size HEIGHTMAP_SIZE / 2 + 1.  The first 2 elements of this array should be 0.0f.  The remaining elements should each be equal to the previous element plus a random number between 0.0f and 2.0f.  Then copy the values from the new array into the heights array to make a series of concentric squares.

0

0

0

0

0

0

0

0

0

0

0

0

0

0

1

1

1

1

1

1

1

1

1

1

1

0

0

1

2

2

2

2

2

2

2

2

2

1

0

0

1

2

3

3

3

3

3

3

3

2

1

0

0

1

2

3

4

4

4

4

4

3

2

1

0

0

1

2

3

4

5

5

5

4

3

2

1

0

0

1

2

3

4

5

6

5

4

3

2

1

0

0

1

2

3

4

5

5

5

4

3

2

1

0

0

1

2

3

4

4

4

4

4

3

2

1

0

0

1

2

3

3

3

3

3

3

3

2

1

0

0

1

2

2

2

2

2

2

2

2

2

1

0

0

1

1

1

1

1

1

1

1

1

1

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

Find a formula that calculates which element to copy base on the position in the height array.  Do not just use 13 * 13 = 169 assignment statements. You should get a grid of 16 pyramid-like things, although only 9 will be visble.

35.  Setting heights[0][0] to -2.0f.  You should see one corner of each heoghtmap slope sharply downward.

36.  Change the heights array to be only HEIGHTMAP_SIZE in each dimension instead of HEIGHTMAP_SIZE + 1.  The heightmap should repeat the 0 element in place of the removed values.

0, 0

1, 0

2, 0

3, 0

0, 0

0, 1

1, 0

2, 1

3, 1

0, 1

0, 2

1, 2

2, 2

3, 2

0, 2

0, 3

1, 3

2, 3

3, 3

0, 3

0, 0

1, 0

2, 0

3, 0

0, 0

Now all four corners of the heightmap should slope downward, leaving a steep pit.  Hint: Use the modulus operator (%).

37.  Why is the corners pit a hexagon in shape?  Hint: Remember that each square in the heightmap is made of 2 triangles.  Why can't the heightmap be made of quadrilaterals?

38.  Convert the heightmap to a class.  The heights array and the display list should become member variables.  Convert the initHeightmapHeights and initHeightmapDisplayList functions to member functions.  Add a draw function to display the heightmap.