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 has also been removed.
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.
The user is now given direct control over a small sphere, which can roll over the terrain. The camera, which used to move in a fixed circular pattern around the terrain, is now tied to follow the sphere. To make it more obvious which code is new, 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 diagram, 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 diagram, we know the (x,y) coordinates of two points on the red line: (2,1) and (8,4). We want to find out the y-coordinate at x = 4. (The diagram isn't to scale at all, so don't get out your rulers; you'll 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 1/3 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 1/3 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 diagram, 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 don't know is the height, y.
So 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. Then, 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, and we've got our height.
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 will suffice for many games.