Implementation
To implement a heightmap in M3G:
- Load a heightmap.
- Create a new array that is scaled proportionally to the grid size.
- Read pixels from the heightmap and store in the new array.
- 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();
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
createQuads();
// Create the water
createWater(waterLevel);
}
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;
System.gc();
// 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;
System.gc();
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);
}
}
}


