Channels ▼

JVM Languages

Heightmap Terrain Rendering

Source Code Accompanies This Article. Download It Now.


To implement a heightmap in M3G:

  1. Load a heightmap.
  2. Create a new array that is scaled proportionally to the grid size.
  3. Read pixels from the heightmap and store in the new array.
  4. Use the array to generate quads with varying heights.

It's a straightforward procedure that begins by inspecting the private members of the HeightMap class; see Listing Four. The heightMap array is the scaled array that holds the heights. It is not holding the pixels from the heightmap image. The Mesh table is holding all the generated Quads you render. Finally, the water Mesh is a blue plane that represents the water (rivers, lakes, and so on) in the terrain. Listing Five creates a HeightMap by first checking for invalid resolution values. Invalid values are values beyond 1.0f (a quad has four corners, so the smallest grid sector is a 2×2) and below 0.0001f (a very low resolution that more or less creates the entire terrain with one quad).

// Actual heightmap containing the Y-coords of our triangles
    private short[] heightMap;
    private int[] data;
    private int imgw, imgh;
    // Map dimensions
    private int mapWidth;
    private int mapHeight;
    // Actual quads
    private Mesh[][] map;
    // Water
    private Mesh water;
    // Local transform used for internal calculations
    private Transform localTransform = new Transform();
Listing Four

public HeightMap(String imageName, float resolution, int waterLevel)
throws IOException
        // Check for invalid resolution values
        if(resolution <= 0.0001f || resolution > 1.0f)
            throw new IllegalArgumentException("Resolution too small or too large");
        // Load image and allocate the internal array
        loadImage(imageName, resolution);
        // Create quads
        // Create the water
Listing Five

Next, Listing Six loads the image supplied as a constructor parameter and extracts its pixel values. Using the resolution parameter supplied from the constructor, you then create a size grid and fill it with pixel values. Last, you perform manual garbage collection to get rid of unnecessary data because the loadImage method is a memory-intensive method and you want to ensure garbage data isn't taking up vital memory for the next few tasks.

// Load actual image
        Image img = Image.createImage(path);
        // Allocate temporary memory to store pixels
        data = new int[img.getWidth() * img.getHeight()];
        // Get its rgb values
        img.getRGB(data, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());
        imgw = img.getWidth();
        imgh = img.getHeight();
        // Clear image
        img = null;

        // Calculate new width and height
        mapWidth = (int)(res * imgw);
        mapHeight = (int)(res * imgh);
        // Allocate heightmap
        heightMap = new short[mapWidth * mapHeight];
        // Calculate height and width offset into image
        int xoff = imgw / mapWidth;
        int yoff = imgh / mapHeight;
        // Set height values
        for(int y = 0; y < mapHeight; y++)
            for(int x = 0; x < mapWidth; x++)
                heightMap[x + y * mapWidth] = (short)((data[x * xoff + y* yoff * imgw] & 0x000000ff) * 10);
        // Clear data
        data = null;
   img = null;
Listing Six

The createQuads method in the constructor body is a straightforward method that takes the generated heightMap array and creates quads from it; see Listing Seven. Here, you iterate over the heightMap table and extract four values, which are used as the height values in the MeshFactory.createQuad method. The MeshFactory.createPlane method then creates a large plane textured with a watery mesh.

private void createQuads()
        map = new Mesh[mapWidth][mapHeight];
        short[] heights = new short[4];
        for(int x = 0; x < (mapWidth - 1); x++)
            for(int y = 0; y < (mapHeight - 1); y++)
                // Set heights
                setQuadHeights(heights, x, y, mapWidth);
                // Create mesh
                map[x][y] = MeshFactory.createQuad(heights,PolygonMode.CULL_NONE);
Listing Seven

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.