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.
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 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
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.
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!
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.
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);