# TerraMonkey - jME3's Terrain System

The goal of TerraMonkey is to provide a base implementation that will be usable for 80% of people's goals, while providing tools and a good foundation for the other 20% to build off of.

## Overview

TerraMonkey is a GeoMipMapping quad tree of terrain tiles that supports real time editing and texture splatting. That's a mouth full! Lets look at each part:

- GeoMipMapping: a method of changing the level of detail (LOD) of geometry tiles based on how far away they are from the camera. Between the edges of two tiles, it will seam those edges together so you don't get gaps or holes. For an in-depth read on how it works, here is a pdf http://www.flipcode.com/archives/article_geomipmaps.pdf.
- Quad Tree: The entire terrain structure is made up of TerrainPatches (these hold the actual meshes) as leaves in a quad tree (TerrainQuad). TerrainQuads are subdivided by 4 until they reach minimum size, then a TerrainPatch is created, and that is where the actual geometry mesh lives. This allows for fast culling of the terrain that you can't see.
- Splatting: The ability to paint multiple textures onto your terrain. What differs here from JME2 is that this is all done in a shader, no more render passes. So it performs much faster.
- Real-time editing: TerraMonkey will be editable in JMonkeyPlatform, and you will be able to do it in real time: raising and lowering terrain.

## Current Features:

- Support for up to 3 splat textures.
- GeoMipMapping
- can be supplied a heightmap

## Planned Features:

- jMonkeyPlatform terrain editor
- Support for up to 16 splat textures.
- Hydraulic erosion and procedural texture generation
- Streaming terrain (ie. "infinite" terrain)
- Holes: caves, cliffs

## Geo Mip Mapping

You have seen GeoMipMapping implemented in games before. This is where the farther away terrain has fewer polygons, and as you move closer, more polygons fill in. The whole terrain is divided into a grid of patches, and each one has its own LOD. The GeoMipMapping algorithm will look at each patch, and its neighbours, to determine how to render the geometry. It will seam the edges between two patches with different LOD. This often leads to "popping" where you see the terrain switch from one LOD to another. TerraMonkey has been designed so you can swap out different LOD calculation algorithms based on what will look best for your game. You can do this with the LodCalculator interface. GeoMipMapping in TerraMonkey has been split into several parts: the terrain quad tree, and the LODGeomap. The geomap deals with the actual LOD and seaming algorithm. So if you want a different data structure for your terrain system, you can re-use this piece of code. The quad tree (TerrainQuad and TerrainPatch) provide a means to organize the LODGeomaps, notify them of their neighbour's LOD change, and to update the geometry when the LOD does change. To change the LOD it does this by changing the index buffer of the triangle strip, so the whole geometry doesn't have to be re-loaded onto the video card.

If you are eager, you can read up more detail how GeoMipMapping works here: www.flipcode.com/archives/article_geomipmaps.pdf

## Terrain Quad Tree

TerraMonkey is a quad tree. Each node is a TerrainQuad, and each leaf is a TerrainPatch. A TerrainQuad has either 4 child TerrainQuads, or 4 child TerrainPatches. The TerrainPatch holds the actual mesh geometry. This structure is almost exactly the same as JME2's TerrainPage system. Except now each leaf has a reference to its neighbours, so it doesn't ever have to traverse the tree to get them.

## Texture Splatting

The default material for TerraMonkey is Terrain.j3md. This material combines an alphamap with several textures to produce the final texture. Right now there is support for only 3 textures and an alpha map. This is in place until we finish the terrain editor in JMP, and then the texture support will be 16 textures. It is only 3 right now so you can hand-paint them in a drawing program, like Photoshop, setting each splat texture in either Red, Green, or Blue. The test case has an example texture to show you how this works.

Along with getting more splat texture support, we will be adding in lighting and normal mapping support. The normal mapping isn't fully planned out yet. We need to decide how we are going to handle a normal map for each texture that is passed in. That could generate some very odd effects. Thoughts, ideas, and recommendations are appreciated!

## Code Sample

First, we load our textures and the heightmap texture for the terrain

    // Create material from Terrain Material Definition
    matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
    // Load alpha map (for splat textures)
    matRock.setTexture("m_Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
    // load heightmap image (for the terrain heightmap)
    Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");

    // load grass texture
    Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
    grass.setWrap(WrapMode.Repeat);
    matRock.setTexture("m_Tex1", grass);
    matRock.setFloat("m_Tex1Scale", 64f);

    // load dirt texture
    Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
    dirt.setWrap(WrapMode.Repeat);
    matRock.setTexture("m_Tex2", dirt);
    matRock.setFloat("m_Tex2Scale", 32f);

    // load rock texture
    Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
    rock.setWrap(WrapMode.Repeat);
    matRock.setTexture("m_Tex3", rock);
    matRock.setFloat("m_Tex3Scale", 128f);

We create the heightmap from the `heightMapImage`.

    AbstractHeightMap heightmap = null;
    heightmap = new ImageBasedHeightMap(
        ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 1f);
    heightmap.load();

Next we create the actual terrain.

- The terrain tiles are 65x65.
- The total size of the terrain is 513x513, but it can easily be up to 1025x1025.
- It uses the heightmap to generate the height values.

    terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
    terrain.setMaterial(matRock);
    terrain.setModelBound(new BoundingBox());
    terrain.updateModelBound();
    terrain.setLocalScale(2f, 1f, 2f); // scale to make it less steep

    List<Camera> cameras = new ArrayList<Camera>();
    cameras.add(getCamera());
    TerrainLodControl control = new TerrainLodControl(terrain, cameras);
    terrain.addControl(control);

    rootNode.attachChild(terrain);

PS: As an alternative to an image-based height map, you can also generate a Hill hightmap:

    heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);

view online version