You’ve just finished building a cool, static mesh airport for your flight trainer. You add it to your Delta3D application and then you realize something is amiss. Your airplanes can fly over your hand-crafted airport in a matter of seconds, soaring off into the endless void of empty space that surrounds your airport on all sides. You have crafted an island of training in a sea of blackness and it sure looks silly.
Well, one way to fix the problem is to create the terrain around the airport as a huge height map. You might choose to have an artist hand-craft it as a huge static mesh in your favorite modeling program (like 3D Studio Max). Of course, to support airplanes, you’re going to need a huge area. Plus, they’ll have to place all the detailed vegetation, buildings, and other decorations that make it realistic. Then, they’ll have to solve some of their own paging algorithms and find a way to get it all to work in Delta3D. That’s a lot of work and is hardly a ‘rapid’ process.
Fortunately, there is a solution; one that can scale and is designed for flexibility. If this sounds like the solution for your problem then we welcome you to the dtTerrain tutorial. This tutorial is your guide to a basic understanding of the Delta3D dtTerrain API and related technologies.
DtTerrain is a brand new architecture that was created to support a wide variety of terrain behaviors. It originated from the need to merge SOARX and GENETICS, two highly specialized terrain technologies, into Delta3D. The result is dtTerrain - a flexible and scalable architecture that is perfect for adding Terrains to Delta3D. It supports these two advanced terrain capabilities and paves the way for the future of Delta3D terrain.
This set of 3 tutorials introduces you to the dtTerrain API and the concepts of a Terrain, a terrain Reader, a terrain Renderer, Decorators, and terrain Tiles. In the next three chapters, you will learn what these concepts mean and how to create and use your very own Terrain in your Delta3D project.
* Note - in order to get this technology into Delta3D as quickly as possible we chose not to add Actor Proxy behavior for the objects in dtTerrain. For now, you won’t be able to access anything in dtTerrain in the Simulation, Training, and Game Editor (STAGE).
2. Basic Technology Overview
Before you can start using dtTerrain, you’ll need to understand a few of the architectural concepts.
Terrain –This refers to the overall concept of a large landscape. Typically, Terrains are much larger than a static mesh. They are huge areas of the world with lots of detail. They may be as small as a city, but often they are as large as an entire state, or even an entire country. Basically, a Terrain is defined by a set of points in 3D space that create a huge mesh. The points are really just a series of height markers (height posts) that together create an overall mapping of the surface of the earth (called a height map). Terrain is also the name of the central class in dtTerrain. For these tutorials, the word Terrain may refer to either, depending on context.
Readers – A reader is a specific class that knows how to read a terrain database from a hard drive or other data source. Terrains are very large and are usually generated by an organization that specializes in creating terrains. Or, maybe you have a tool like Terrex and can generate your own terrains. In either case, you will need a way to read in this data. That is the Reader’s job. An individual Reader is responsible for reading in a particular terrain format whether it’s DTED, CTDB, DEM, or even a specific Terrex format. Don’t worry about the acronyms, they’re just examples of terrain formats. The point is that the Reader interface creates a flexible architecture that will help enable future support for any type of terrain.
Renderer – So, we have something that can read our terrain data, but what do we do with it? That’s where the Renderer comes in. It is responsible for drawing the terrain in Delta3D; you see what it draws. But, Terrains come in all shapes and sizes and lots of projects will have their own particular needs. So, one Renderer won’t be enough. That’s why dtTerrain defines a Renderer interface. The Renderer interface allows you to plug in your own custom drawing behavior for your terrain. It is a generic architecture that lets you add optimizations, level of detail, texture blending, shading, lighting, or any other behavior that you can’t find in an existing Renderer.
Terrain Tiles – Terrain tiles are individual chunks of the terrain. Think of the earth as if it was broken up into squares. A Tile (or Page) would represent one of these squares. Terrain tiles are cached after they are loaded in order to optimize performance. For this tutorial, Tile can mean the generic concept of Tile or the specific PagedTerrainTile class (see below) in dtTerrain. The following picture is an example heightmap that was generated for a terrain tile. If you look closely, you can see what appears to be a river snaking through a mountainous region.
Figure 1 – Example Terrain Tile Heightmap
Decorators – A decorator is a layer that sits on top of the terrain. It could be used to add whatever you want to each tile; anything from objects such as trees to blended textures that are applied to the terrain. Some examples include a decorator that handles the placement of trees, a decorator for the placement of bodies of water, or a decorator for the placement of roads. The decorators get called after a terrain tile is loaded. This creates a flexible way to plug-in visualization layers that could be turned on or off in your application.
Figure 2 – Texture Decorator Over Terrain
Figure 3 – Vegetation Decorator Over Terrain
SOARX – SOARX is an example Renderer that is provided with dtTerrain. It is based on Andras Balogh's SOARX terrain algorithm. This algorithm combines dynamic level-of-detail rendering with automatic high-frequency detail generation. Say what? Let’s try that again. This algorithm adjusts how much detail it uses to draw terrain polygons depending on how far away your camera is. When you are far away, it smartly eliminates vertices from the heightmap so that it can draw very quickly. Then, as you get closer to the terrain, it adds vertices back in until you are at the full, native resolution of your terrain data. But, it doesn’t stop there. As you pass that threshold, SOARX will actually create additional terrain details on the fly. As you get closer, it continues to add vertices until you are standing right at eye level. To do this, it uses a Perlin random noise generator. The resulting terrain is incredibly smooth and very realistic. It’s surprisingly fast.
GENETICS – GENETICS (Generating Enhanced Natural Environments and Terrain for Interactive Combat Simulations) is an example of how powerful Decorators can be. It was created by the Naval Postgraduate School’s MOVES Institute as part of an effort for the National Geospatial-Intelligence Agency (NGA). GENETICS uses land cover classification (LCC) images to procedurally create vast, detailed vegetation at runtime. Basically, LCC images are like satellite pictures of the earth that have color representations for different types of vegetation; green for one type of tree, red for housing, brown for roads, etc… The GENETICS decorator parses data out of these images, determines where it should place vegetation, and then creates trees and houses that are placed in the scene. Most of this data is pre-created and cached, but it can be an intense CPU-bound process.
DTED – Digital Terrain Elevation Data. This is a specific terrain data format that is often used in government and military applications. It is typically provided by an organization like the National Geospatial-Intelligence Agency (NGA) or National Imagery and Mapping Agency (NIMA). It comes in various levels (0, 1, 2, …) that go from crude to more detailed. This is the format used by our example reader.
3. Putting It Together
At this point you may be wondering how all these pieces fit together. In essence, it works like this. The Terrain class uses a Reader to load in tile data. It then sends that data to the decorators for processing. When the decorators are done processing, it sends the data to the Renderer, which handles the actual drawing each and every frame.
The following picture gives an overview of how the Terrain, Reader, Renderer and Decorators interact to create the final image.
Figure 4 – Overview Diagram
To make this happen in your code, you will do the following:
- Create an instance of Terrain
- Add your Reader to the Terrain
- Add your Renderer to the Terrain
- Add your Decorators to the Terrain
As an example, if you have DTED source data you will need to create a DTED data reader. The reader will be used to properly convert the data from the hard drive into a format that dtTerrain understands. Next, you will need to pick or create a Renderer such as the SOARXTerrainRenderer. Then, you’ll need to add any custom Decorators, such as the GENETICS vegetation layer. Once you’ve done all that, you will see a beautifully drawn terrain.
At this point you should have a basic understanding of the different parts of the dtTerrain module and how they fit together. Now you are ready to move onto the next Chapters to see how it works in code.
Seriously, stop here! If want to know how to use dtTerrain by looking at examples then you might consider moving on to the second part of our tutorial Part 2. The Terrain module is fairly advanced and takes advantage of a number of different classes and interfaces that you may not need to understand just to use it. However, if you’re still interested in learning more details about what makes the dtTerrain API tick, then please continue reading.
4. dtTerrain General Diagram
The following is the complete dtTerrain API class diagram. The next sections describe each of the 5 main parts of this diagram.
Figure 5 – Complete dtTerrain Class Diagram
Figure 6 - Terrain Class Diagram
At the center of the main diagram is the Terrain class. The Terrain class is the heart of the entire dtTerrain module. It controls the interactions between the reader, the renderer, and all the decorators. It maintains the resource and cache paths and is responsible for managing the loading and unloading of tiles. The Terrain class is completely oriented toward ‘seamless’ non-terminal terrain. Meaning, it uses Tiles, Readers, and Renderers for the sole purpose of allowing Delta3D to play with an incredibly large universe. This architecture should be scalable enough to support the highly-detailed, seamless terrains used by Massively Multiplayer (MMO) games such as World of Warcraft ™. Basically, you have complete control over the level of detail in each Tile as well as the way in which the source data is read in, cached, and rendered. You shouldn’t usually need to subclass or modify the Terrain class.
Figure 7 – Reader Class Diagram
This is the base class for each individual Terrain reader. As described above, the Reader is responsible for loading tile data from the hard drive (or other external source) into memory. It converts the data into the internal height field format used by the Terrain module. This class is abstract (i.e. cannot be instantiated).
This is an example implementation of a TerrainDataReader. This specific reader knows how to read Digital Terrain Elevation Data (DTED) data at levels 0, 1, and 2. Level 0 is a very crude approximation of the Earth with height postings about every 1 kilometer. Level 1 has 10x the resolution of Level 0 with height postings every 100 meters. Level 2 has height postings roughly every 30 meters.
There are lots of other formats that can be supported by this architecture such as Digital Elevation Model (DEM) and Compact Terrain Database (CTDB). The most common of these will certainly be added over time as individual projects come along. For less common formats, you will need to write your own Reader. Use the basic structure of the example TerrainDataReader to add support for your specific format to Delta3D.
Figure 8 – Renderer Class Diagram
This is the base class for each individual Terrain Renderer. As described above, the Renderer is responsible for drawing the terrain data each frame. It uses the Paged Terrain Tiles from its owner Terrain class. This class is abstract (i.e. cannot be instantiated).
This is an example implementation of a TerrainDataRenderer. This Renderer dynamically adds vertices to or removes vertices from the actual tile data. It supports several customizations such as error threshold and detail multiplier that provide for more variation in terrain and higher polygon counts respectively. The SOARX renderer can preprocess tiles and store the results in cache files. This renderer can either provide basic shading of the landscape or it can drape a texture (such as satellite imagery) across the terrain mesh. It uses the Base Texture Image from the Paged Terrain Tile for draping. The SOARX Renderer also produces normalized shading based on the height deltas between posts – this shading can be blended with a draped texture. The resulting terrain is incredibly realistic and smooth. Despite the amount of work involved, it is also extremely fast. Note that this renderer caches the perlin noise in an external image file; it is possible to use this image to correlate SOARX terrain across multiple systems.
There are lots of ways to draw terrain. Fortunately, the dtTerrain module can easily support new Renderers. Simply subclass TerrainDataRenderer and build your new Renderer based on the example SOARX Renderer.
There is a world of possibilities out there (pun intended ;) for renderers. The most obvious example would be a basic ‘normal’ Renderer that simply draws the terrain vertices as they exist in the Tile. Another example would be a Renderer that can alpha-blend layered textures over a mesh terrain – this is the eventual vision for terrain editing and texture painting in STAGE. A final example is a Renderer that is capable of supporting deformable terrain – terrain that is dynamically modified by game events (i.e. explosions).
6. Paged Terrain Tiles
Figure 9 – Terrain Tile Class Diagram
This class holds the actual heightmap data for each Tile. It provides the basic data that the Readers, Renderers, and Decorators use. Currently the tiling is based on a one degree by one degree latitude and longitude. The terrain tiles play a role in the Terrain, the reader and the renderer. The renderer requires information related to normals and height. The reader will load or unload tiles that exist or, if a tile has not been created yet, will initiate the creation of a new tile for a particular latitude and longitude.
Although the base class provides most of what you need, it may not be enough for you. In that case, you can create your own subclass of PagedTerrainTile and stick in whatever custom behavior or custom data you need. Then, have your custom Reader create your Tile subclass instead of the base version. In this way, you can continue to use the standard Decorators and Renderers.
Based on the fact that different terrain formats are in different coordinate spaces, the GeoCoordinate class will help convert terrain data into the correct latitude and longitude. The GeoCoordinate class is intended to represent both cartesian (x,y,z) and geographic (lat/lon) coordinates.
Heightfield data is the meat and potatoes of what makes a terrain tile. A heightmap uses a gradient value from black to white. Black represents the lowest point in a heightmap whereas white represents the highest. The HeightField class allocates and creates an image based on the data at a location specified by the paged terrain tile.
Figure 10 – Decorator Class Diagram
This is the base class for each individual Decorator layer. A decorator is a way of adding details to a tile outside of the base terrain information. The decorator behavior is called for each new tile that is loaded by Terrain. Technically, it would be possible to write a custom Reader class that did everything you could do in a decorator. However, that constrains the flexibility of the terrain. By using a decorator, you can generically add or remove individual behaviors to your terrains.
The VegetationDecorator and DrapedTextureDecorator are perfect examples of generic decorators. The decorator encapsulates each specific behavior away from the Reader. The result is a dynamic architecture that can grow or shrink on the fly – you could even allow users to turn on/off specific behaviors. Decorators could be used for traditional terrain decoration (small shrubs and plant life), for drape textures, and even actor creation. Decorators are called in the order that they were added to Terrain.
This is an example implementation of a TerrainDecorationLayer based on GENETICS. It uses a highly advanced algorithm for procedurally generating vegetation objects based on Land Cover Classification (LCC) data. As described in the introduction, the GENETICS algorithm starts with image data in the LCC image. It then transforms that data into a series of filters and maps that represent where vegetation should be placed. Finally, it instantiates OSG nodes for each tree, house, etc… Note that the placement of vegetation attempts to account for variations in slope and relative elevation.
The GENETICS decorator can be programmed to respond differently to the same LCC data. Meaning, you can individually define how you want to handle every color in the LCC map. In the following chapters, you will see examples of how models are assigned to a variety of land classification types.
This is a much simpler example of a TerrainDecorationLayer. This decorator simply loads texture data for each tile; specifically, it works with a geo-specific TIFF image. The texture information is then stored in the Base Texture field of the Tile. The texture can be used by the Renderer as a drape, or by subsequent decorators for further processing.
The terrain was designed to plug in different readers, renderers and decorators. This has definitely made the terrain flexible for a wide array of applications. At this point you should have a strong understanding on the way things work. Please continue over to the next part of the tutorial where we will present the pieces that comprise dtTerrain.
Click here for Part 2