Heightmap Collision Sample

This sample demonstrates how to move objects along a heightmap. It is based on the Generated Geometry Sample, which creates a landscape from a bitmap. We build upon that sample, showing how to quickly calculate the height of any point on that heightmap.

Sample Overview

The Generated Geometry Sample introduced the concept of a heightmap. In that sample, a content processor reads a bitmap and uses the intensity of its pixels as height values on a terrain. A logical next step to this technique is to place objects on the terrain. This sample demonstrates that technique by placing a ball on the generated terrain.

Sample Controls

This sample uses the following keyboard and gamepad controls.

Action Keyboard control Gamepad control
Move the ball

UP ARROW, DOWN ARROW, LEFT ARROW, and RIGHT ARROW or W, A, S, and D

Left thumb stick or D-pad
Exit the sample ESC or ALT+F4 BACK

How the Sample Works

This sample is based on the Generated Geometry Sample. To support the added functionality, the original sample has been modified in several ways. For clarity, the sky also has been removed.

Changes to the Terrain Processor

The TerrainProcessor has been modified slightly for this new sample. First, the algorithm that gives the vertices their XZ position has been modified slightly from the Generated Geometry Sample. In the new version, the positions are calculated so that the heightmap is always centered around the origin. This change simplifies the math at run time.

In addition, the processor is responsible for creating a class called HeightMapInfoContent, which contains data about the height of the points of the heightmap, as well as the distance between them. This information is attached to the finished terrain model's .Tag property, and will be read when the game loads.

Finally, the SkyProcessor and SkyContent classes have been removed from the pipeline assembly.

Rolling Around on the Terrain

The user is now given direct control over a small sphere, which can roll over the terrain. The camera, which is used to move in a fixed circular pattern around the terrain, is now tied to follow the sphere. To make which code is new more obvious, we removed the Sky class from the sample.

To keep both the sphere and the camera on top of the terrain, we use the GetHeight function on HeightMapInfo. GetHeight is the most complicated part of this sample. This function accepts a position as an argument and returns the height of the heightmap at that position.

In this illustration, we are trying to calculate the height of the red circle. The grid represents our heightmap. In this example, our heightmap is 4×4. Note that this means that the heightmap's width and height are only 3 * TerrainScale. We'll call the white space between gridlines "cells."

To calculate the red circle's height at any point on a cell, we use bilinear interpolation. This is a lot simpler than it sounds. To understand how it works, let's first examine the simpler case, linear interpolation.

In this illustration, we know the (x,y) coordinates of two points on the red line: (2,1) and (8,4). We want to find the y-coordinate at x = 4. (The illustration is not to scale, so do not get out your rulers; you will be disappointed.) To find the missing value, we can use linear interpolation. There are several different ways to do this. We'll use a method that, when ported to C# code, lets us make use of MathHelper.Lerp (linear interpolate) and will be fairly efficient.

The process is relatively straightforward. We know the x-coordinates of all three points, and we can see that the x-coordinate of the center point, which we are trying to find, is 1/3 of the way between the x-coordinate of the other two points. (In other words, the distance from x = 2 to x = 4 is one-third of the distance from x = 2 to x = 8.) Since the points are all on a straight line, the y-coordinate must also be 1/3 of the way.

The distance between the two y-coordinates, y = 1 and y = 4, is 3. So, if the y-coordinate at (4,?) is one-third of that distance away from the point on the left, our missing value is:

1 + (1/3) × 3 = 2

Simple enough. Bilinear interpolation will extend that principle further.

In this illustration, we are again on a heightmap, trying to calculate the height of our red circle. We know all of the heights at the corners of the cell, since we read those in from the bitmap, remember? We also know the x and z coordinates of the red circle. What we do not know is the height of y.

First we do a linear interpolation on the top edge of the cell. We go from the (left,top) corner to the (left+1,top) corner, and find the height at point A. Next, we do the same thing on the bottom edge of the cell, calculating the height at point B. Now we know the heights at A and B, both of which are on a straight line with the circle! To find the height of the circle, we do one more linear interpolation between A and B.

Note that this technique is not perfect: on steep hills, it is still possible that parts of the sphere may clip through the terrain. To avoid this, we would have to perform a much more expensive collision check. Our technique, although imperfect, is inexpensive and yields results that suffice for many games.

Extending the Sample

  • The camera in this sample is fairly simple. Try taking the camera from the Chase Camera Sample and putting it into this one.
  • When moving objects over a heightmap, you may also want to align them to the slope of the terrain. To do this, you will need to calculate the heightmap's normal vectors in the TerrainProcessor. A GetNormal function would work similarly to GetHeight, except all three components of the vector would have to be interpolated separately, and then renormalized. Using this normal vector, you can calculate an orientation matrix for your object.