Lesson 1: Creating terrains with heightmaps

Terrians can be represented by an array of numbers.  For example, a flat terrain that is 4x4 can be represented by a one-dimensional array of size 16:

double [] heightMap = { 0,0,0,0,
                        0,0,0,0,
                        0,0,0,0,
                        0,0,0,0 };

There is a special class called EnvTerrain that takes such an array as its the constructor.  An object of EnvTerrain can be added to the Env3D enviroment, as follows:

import env3d.advanced.EnvAdvanced;
import env3d.advanced.EnvTerrain;
import env3d.Env;

public class Game
{
    public Game() {
        
    }
    
    public void play() {        
        // The EnvAdvanced class is a subclass of Env.  An object of the EnvAdvanced class
        // creates an environment without a default room.  
        // It also allows more advanced features like reflections and shadows.
        Env env = new EnvAdvanced();
        
        // Creates a height map of size 8x8.  With a little hill in the middle.
        double heightMap [] = { 0,0,0,0,0,0,0,0,
                                0,0,0,0,0,0,0,0,
                                0,0,0,0,0,0,0,0,
                                0,0,0,3,3,0,0,0,
                                0,0,0,3,3,0,0,0,
                                0,0,0,0,0,0,0,0,
                                0,0,0,0,0,0,0,0,
                                0,0,0,0,0,0,0,0};
        
        // Create the terrain object based on the heightmap                       
        EnvTerrain terrain = new EnvTerrain(heightMap);
        // Terrain needs a texture
        terrain.setTexture("textures/mud.gif");
        
        // Add the terrain object to the environemnt.
        env.addObject(terrain);
        
        // Exit when the escape key is pressed
        while (env.getKey() != 1) {
            env.advanceOneFrame();
        }
        env.exit();
    }
    
    public static void main(String [] args) {
        (new Game()).play();
    }
}

The above code shows how a simple terrian of 8x8 can be created.  We also introduced the new EnvAdvanced class, which for now, is simply an environment without a default room with a background color of black.  There is one restriction on the array size: it must be a sqaure and each size must be a power of 2.

Image based height maps

While it seems easy to create a one-dimensional array for heightmap data, it gets tedious very quickly if your terrian is large.  A more convenient way is to use an image to represent height data.  You can find some sample height images in the textures/terrain subdirectory of your env3d_template project.  These images are always greyscale, with black representing a height of 0 and white representing a height of 1.  Instead of providing an array in the EnvTerrain constructor, you can provide the image, as follows:

import env3d.advanced.EnvAdvanced;
import env3d.advanced.EnvTerrain;
import env3d.Env;

public class Game
{
    public Game() {
        
    }
    
    public void play() {        
        Env env = new EnvAdvanced();
        
        // Use an image to provide height data, this image is 256x256
        EnvTerrain terrain = new EnvTerrain("textures/terrain/termap1.png");
        // Terrain needs a texture
        terrain.setTexture("textures/mud.gif");
        
        // Add the terrain object to the environemnt.
        env.addObject(terrain);
        
        // Inspect the entire terrain from far away.
        env.setCameraXYZ(128, 250, 400);
        env.setCameraPitch(-20);
        
        // Exit when the escape key is pressed
        while (env.getKey() != 1) {
            env.advanceOneFrame();
        }
        env.exit();
    }
    
    public static void main(String [] args) {
        (new Game()).play();
    }
}

You can use the terrain.setScale(double xScale, double yScale, double zScale) method to scale the size of the terrain.  A common thing to do is to scale the y axis to flatten out the mountains and valleys.

To find out the height of a particular location on the terrain, use the terrain.getHeight(double x, double z) method. 

A simple first person camera can walks around a terrain can be implemented as follows:

import env3d.advanced.EnvAdvanced;
import env3d.advanced.EnvTerrain;
import env3d.Env;

public class Game
{
    public Game() {        
    }
    
    public void play() {        
        Env env = new EnvAdvanced();
        
        // Use an image to provide height data, this image is 256x256
        EnvTerrain terrain = new EnvTerrain("textures/terrain/termap1.png");
        // Terrain needs a texture
        terrain.setTexture("textures/mud.gif");
        
        // Flatten the hills and valleys
        terrain.setScale(1, 0.2, 1);
        
        // Add the terrain object to the environemnt.
        env.addObject(terrain);        
        
        // Start in the middle of the terrain
        env.setCameraXYZ(128, 0, 128);
        
        // Exit when the escape key is pressed
        while (env.getKey() != 1) {
            // Get the height of the camera
            double height = terrain.getHeight(env.getCameraX(), env.getCameraZ());
            // Make sure that the height is valid (within the terrain boundary)
            if (!Double.isNaN(height)) {
                // Make the y value a little higher than the terrain, to create the
                // illusion that we are "walking" on the surface.
                env.setCameraXYZ(env.getCameraX(), height+1, env.getCameraZ());
            }

            env.advanceOneFrame();
        }
        env.exit();
    }
    
    public static void main(String [] args) {
        (new Game()).play();
    }
}

Rendering the sky

Our terrain looks pretty good, but the black background is not ideal.  In an outdoor enviornment, we should show static textures of the sky and mountains.  This can be accomplished using the skybox technique.  For backgound on skybox, refer to this wikipedia article.

For our purposes, a skybox is simply a really large room with textures of the sky.  The EnvSkyRoom class is designed for it.  The following code integrates a skybox into our terrain environment to create a complete outdoor experience.

import env3d.advanced.EnvAdvanced;
import env3d.advanced.EnvTerrain;
import env3d.advanced.EnvSkyRoom;
import env3d.Env;
  
public class Game
{
    public Game() {        
    }
      
    public void play() {        
        Env env = new EnvAdvanced();        
 
        // Create a the sky box.  The directory specified in the
        // constructor contains 6 images: north.jpg, east.jpg,
        // south.jpg, west.jpg, top.jpg, and bottom.jpg
        //
        // The camera will always be in the center of the skybox        
        EnvSkyRoom skyroom = new EnvSkyRoom("textures/skybox/default/");
         
        // EnvSkyRoom is a room, so we use env.setRoom() to set make it visible in our
        // environment.
        env.setRoom(skyroom);
                 
        // Use an image to provide height data, this image is 256x256
        EnvTerrain terrain = new EnvTerrain("textures/terrain/termap1.png");
        // Terrain needs a texture
        terrain.setTexture("textures/mud.gif");
          
        // Flatten the hills and valleys
        terrain.setScale(1, 0.2, 1);
          
        // Add the terrain object to the environemnt.
        env.addObject(terrain);        
          
        // Start in the middle of the terrain
        env.setCameraXYZ(60, 0, 60);
          
        // Exit when the escape key is pressed
        while (env.getKey() != 1) {
            // Get the height of the camera
            double height = terrain.getHeight(env.getCameraX(), env.getCameraZ());

            // Make the y value a little higher than the terrain, to create the
            // illusion that we are "walking" on the surface.
            env.setCameraXYZ(env.getCameraX(), height+1, env.getCameraZ());
  
            env.advanceOneFrame();
        }
        env.exit();
    }
      
    public static void main(String [] args) {
        (new Game()).play();
    }
}

Exercise

  1. Restrict the player's movement to the flat area.  Do not allow the player to fall down to the valley or climb any mountains.