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. Check out the videos in the following announcements:
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:
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 level of detail (LOD). The GeoMipMapping algorithm looks at each patch, and its neighbours, to determine how to render the geometry. It will seam the edges between two patches with different LOD.
GeoMipMapping 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
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.
When you "slap" a texture on a mesh, the whole mesh looks the same. For big meshes (such as terrains) that is undesirable because it looks very boring (your whole landscape would be all rock, or all grass, or all sand). Texture Splatting is a technique that lets you "paint" several textures into one combined texure. Each of the splat textures has an opacity value so you can define where it is visible in the final overall texture.
The default material for TerraMonkey is TerrainLighting.j3md. This material combines several texture maps to produce the final custom texture. Remember, Diffuse Maps are the main textures that define the look; optionally, each Diffuse Map can be enhanced with a Normal Map; Alpha Maps describe the opacity of each Diffuse Map used (one color –red, green, blue, or alpha– stands for one Diffuse Map's opacity); Glow and Specular Maps define optional effects.
Here are the names of TerrainLighting.j3md's material properties:
AlphaMapAlphaMap_1AlphaMap_2DiffuseMap, DiffuseMap_0_scale, NormalMap DiffuseMap_1, DiffuseMap_1_scale, NormalMap_1DiffuseMap_2, DiffuseMap_2_scale, NormalMap_2DiffuseMap_4, DiffuseMap_4_scale, NormalMap_4DiffuseMap_5, DiffuseMap_5_scale, NormalMap_5DiffuseMap_6, DiffuseMap_6_scale, …, DiffuseMap_11GlowMapSpecularMap
Note: DiffuseMap_0_scale is a float value (e.g. 1.0f); you must specify one scale per Diffuse Map.
Video cards support a maximum of 16 Splat textures total. This means you can only use a subset of material properties at the same time!
Adhere to the following constraints:
Here are some common examples what this means:
You can hand-paint Alpha, Diffuse, Glow, and Specular maps in a drawing program, like Photoshop. Define each splat texture in the Alpha Map in either Red, Green, Blue, or Alpha (=RGBA). The JmeTests project bundled in the SDK includes some image files that show you how this works. The example images show a terrain heightmap next to its Alpha Map (which has been prepare for 3 Diffuse Maps), and one examplary Diffuse/Normal Map pair.
This example shows the simpler material definition Terrain.j3md, which only supports 1 Alpha Map, 3 Diffuse Maps, 3 Normal Maps, and does not support Phong illumination. It makes the exmaple shorter – TerrainLighting.j3md works accordingly (The list of material properties see above. Links to extended sample code see above.)
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(heightMapImage.getImage(), 1f); heightmap.load();
Next we create the actual terrain.
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);