Game Development Community

dev|Pro Game Development Curriculum

Creating a Torque X Terrain

by John Kanalakis · 05/19/2009 (10:18 pm) · 11 comments

In the Torque X Forums, I've seen a few questions come up about creating 3D terrain. Since it's not an easy process, I thought an easily searhable posting here would help.

The XTerrain Class

www.envygames.com/share/terrain450.jpgIn Torque X, the XTerrain class supports the creation of three different types of terrains: a TGE legacy terrain, a chunked geometry terrain, and a procedurally generated terrain. All terrains use the XTerrain class. Each different terrain type is implemented by creating an instance of the TerrainData class and then attaching it to the Data property of the XTerrain class.

Regardless of which type of terrain you plan to create, you always need to start with the XTerrain class. The XTerrain class inherits from TorqueObject, so it must be registered with the TorqueObjectDatabase and can have components. In fact, the most important component attached to the XTerrain is the T3DRigidComponent, without which, everything would fall through the terrain.

Begin creating a terrain by getting a local reference to the scene’s rigid physics manager. Next, create the XTerrain object and set its public properties. Attach a T3DSceneComponent to the XTerrain to give it a presence within the 3D scene. The only other component needed is a T3DRigidComponent to process all other rigid physics reactions in the scene. This component needs a RigidMaterial to define the collision surface and a collision shape, provided by the CollisionXTerrainShape class. Lastly, specify a TerrainData object that contains the details about the terrain’s shape. All that remains is to register with the TorqueObjectDatabase.

public void CreateTerrain()
{
    //find the physics resolver
    RigidCollisionManager rigidManager =
        TorqueObjectDatabase.Instance.FindObject<RigidCollisionManager>("RigidManager");

    //create the terrain
    XTerrain objTerrain = new XTerrain();
    objTerrain.Name = "Terrain";
    objTerrain.HorizontalScale = 8;
    objTerrain.VerticalScale = 1;
    objTerrain.LevelZeroError = 0;
    objTerrain.ViewError = 2;
    objTerrain.LODLevels = 4;
    objTerrain.LODError = 0.5f;
    objTerrain.Repeat = true;
    objTerrain.Visible = true;

    //give the terrain presence in the scene
    T3DSceneComponent componentScene = new T3DSceneComponent();
    componentScene.Position = new Vector3(0, 0, 0);
    componentScene.SceneGroup = "Terrain";
    objTerrain.Components.AddComponent(componentScene);

    RigidMaterial materialRigid = new RigidMaterial();
    materialRigid.Restitution = 0f;
    materialRigid.KineticFriction = 1f;
    materialRigid.StaticFriction = 1f;

    //give the terrain some physics attributes
    T3DRigidComponent componentPhysics = new T3DRigidComponent();
    componentPhysics.SceneGroupName = "Terrain";
    componentPhysics.GravityScale = 0f;
    componentPhysics.RigidManager = rigidManager;
    componentPhysics.RigidMaterial = materialRigid;
    componentPhysics.Immovable = true;
    //componentPhysics.CollisionBody.AddCollisionShape(new CollisionXTerrainShape());
    objTerrain.Components.AddComponent(componentPhysics);

    // ------  SPECIFY A TERRAIN DATA TYPE  ------
    TGETerrainData terrainData = new TGETerrainData();
    terrainData.TerrainFilename = "data/terrains/terrain.ter";
    terrainData.LightMapFilename = "data/terrains/lightmap.png";
    terrainData.TexturePathSubstitution = "data/terrains";
    terrainData.DetailMaterial = "TerrainDetailMaterial";
    objTerrain.Data = terrainData;

    //register the terrain
    TorqueObjectDatabase.Instance.Register(objTerrain);
}

At this point, we still have an incomplete terrain object. We need to attach a TerrainData with one of the preferred data formats in order to have it appear within the scene. Typically, the terrain format you choose depends upon the toolset and function of the terrain.

Using Legacy Terrain Data

The legacy terrain data format dates back to the original Torque Game Engine and uses a heightmap to determine the terrain’s shape. The terrain data is stored in a .ter file and can really only be edited with the Torque Game Engine’s Terrain Editor. The purpose for including support for format is to facilitate the migration of games into the Torque X Framework.

To use the legacy terrain format, copy the following code fragment to the end of your CreateTerrain() method. This fragment creates an instance of the TGETerrainData class and specifies paths to the .ter terrain file and the lightmap texture file.

//specify terrain data
TGETerrainData terrainData = new TGETerrainData();
terrainData.TerrainFilename = "data/terrains/terrain.ter";
terrainData.LightMapFilename = "data/terrains/lightmap.png";
terrainData.TexturePathSubstitution = "data/terrains";
terrainData.DetailMaterial = "TerrainDetailMaterial";
objTerrain.Data = terrainData;

Keep in mind that there is no XNA content processor for the .ter file. After you add it to your game's content project, set the Build Action property to “Content” and Copy to output directory property to “Copy if newer” - do not set it to "Compile". The TGE terrains is great and flexible, but it is also small. It's best suited when you need terrain to decorate a 3D scene but not be fully explored since it will tile/repeat pretty quickly.

Using Raw Terrain Data

To use the Raw terrain format, copy the following code fragment to the end of your CreateTerrain method. This fragment creates an instance of the RAWTerrainData class and specifies a path to the .raw terrain file and the lightmap texture file.

RAWTerrainData terrainData = new RAWTerrainData();
terrainData.TerrainFilename = "data/terrains/l3dt_generated.raw";
terrainData.LightMapFilename = "data/terrains/l3dt_generated_LM.jpg";
terrainData.UniqueTextureFilename = "data/terrains/l3dt_generated_TX.jpg";
terrainData.DetailMaterial = "TerrainDetailMaterial";
objTerrain.Data = terrainData;

At present, the most popular tool for creating Atlas terrains is called Large 3D Terrain Creator, or L3DT for short, by Bundysoft. This reasonably priced software guides you through the creation of large RAW terrain files. When working with L3DT, keep try the following guidelines. Start by selecting File + New Project from the main menu and then selecting the Design/Inflate algorithm.

• Set a map size of 16 for the X and Y values
• Set the horizontal scale to 10
• Do not enable disk paging for mosaic maps
• Enable edge wrapping to produce a terrain that can be tiled
• Experiment with the remaining settings, such as elevations and climates
• You’ll need to create a Design map, Heightfield, Terrain normals, and a Light map

You can save the project and all maps directly to your game project’s data/terrains folder as “l3dt_generated”. Next, you can export the files that the RAWTerrainData class needs.

• Select File + Export + Export map...
• Select the Heightfield map
• Set the file format to Raw
• Browse to the data/terrains folder and set the file name to l3dt_generated.raw
• Do not resize the file by 1 or flip the heightmap as formerly done with TGEA

www.envygames.com/share/l3dt450.jpgAfter the terrain maps have been exported, return to XNA Game Studio and add the “l3dt_generated.raw”, “l3dt_generated_LM.jpg”, and the “l3dt_generated_TX.jpg” files into your game project. Remember to set the properties for the.raw file. The Build Action property should be set to “Content” and Copy to output directory property should be set to “Copy if newer”.

Using Procedural Terrain Data

L3DT presents a colorful show as it produces all of the related terrain files. When it completes, you can browse through the collection of detailed texture maps.

The procedural terrain dynamically creates a terrain landscape at runtime based upon parameters you set in code. It’s a great solution for games that do not require a specific terrain shape because it renders quickly and does not need to include any extra terrain data files. This works well for a flight simulator, where you can see a terrain, but not really interact with it. However, it may not work well for land-based games, where the specific placement of ground structures is required.
To use the procedural terrain format, copy the following code fragment to the end of the CreateTerrain method. This fragment creates an instance of the GeneratedTerrainData class and specifies several computational properties as well as a lightmap texture file.

//specify terrain data
GeneratedTerrainData terrainData = new GeneratedTerrainData();
terrainData.Size = 1025;
terrainData.SmoothingPasses = 2;
terrainData.Jitter = 3;
terrainData.TerrainFilename = "data/terrains/terrain.ter";
terrainData.LightMapFilename = "data/terrains/lightmap.png";
terrainData.TexturePathSubstitution = "data/terrains";
objTerrain.Data = terrainData;

As you can see, there's a lot of flexibility when it comes to Torque X terrains. Although the toolset is still catching up, there's a lot you can do with code an external tools, such as L3DT and the TGE Terrain Editor.

One last item... a new update to Torque X 3.0 that applies fixes to the 2D builder is available, so be sure to download that if you're running into trouble with Tilemaps or Particle Effects.

John K.
www.envygames.com

About the author

John Kanalakis is the owner of EnvyGames, an independent game development studio in Silicon Valley that produces games and tools for Xbox 360, Windows, and the Web.


#1
05/20/2009 (11:33 am)
Wow. Great blog, John!
#2
05/24/2009 (10:53 pm)
This line is giving me an issue:

terrainData.DetailMaterial = "TerrainDetailMaterial";

I don't have a file named TerrainDetailMaterial and the compiler tells me it cannot convert a string to type RenderMaterial.

The terrain shows up in the 3D editor, or at least something that looks like terrain. I used the L3DT to generate this terrain.
#3
07/01/2009 (9:07 pm)
I got this working following the book chapter 13. Needed to create a DetailMaterial and call GenerateCollisionData() on the CollisionXTerrainShape to get the T3DBlobShadowCasterComponent to stop crashing.


Here is my code

protected void CreateTerrain()
        {
            // Find the collision manger
            RigidCollisionManager rigidManager = TorqueObjectDatabase.Instance.FindObject<RigidCollisionManager>("RigidManager");

            XTerrain objTerrain = new XTerrain();
            objTerrain.Name = "Terrain";
            objTerrain.HorizontalScale = 8;
            objTerrain.VerticalScale = 1;
            objTerrain.LevelZeroError = 0;
            objTerrain.ViewError = 2;
            objTerrain.LODLevels = 4;
            objTerrain.LODError = 0.5f;
            objTerrain.Repeat = true;
            objTerrain.Visible = true;

            T3DSceneComponent componentScene = new T3DSceneComponent();
            componentScene.Position = new Vector3(0, 0, 0);
            componentScene.SceneGroup = "Terrain";
            objTerrain.Components.AddComponent(componentScene);

            RigidMaterial materialRigid = new RigidMaterial();
            materialRigid.Restitution = 0f;
            materialRigid.KineticFriction = 1f;
            materialRigid.StaticFriction = 1f;

            // Give terrain some physics attributes
            T3DRigidComponent componentPhysics = new T3DRigidComponent();
            componentPhysics.SceneGroupName = "Terrain";
            componentPhysics.GravityScale = 0f;
            componentPhysics.RigidManager = rigidManager;
            componentPhysics.RigidMaterial = materialRigid;
            componentPhysics.Immovable = true;
            CollisionXTerrainShape shape = new CollisionXTerrainShape();
            componentPhysics.CollisionBody.AddCollisionShape(shape);
            objTerrain.Components.AddComponent(componentPhysics);


            DetailMaterial detailMaterial = new DetailMaterial();
            detailMaterial.TextureFilename = "data/terrains/SandDetail01";
            detailMaterial.DetailTextureRepeat = 256;
	  
            /// SPECIFY A TERRAIN DATA TYPE
            TGETerrainData terrainData = new TGETerrainData();
            terrainData.TerrainFilename = "data/terrains/terrain.ter";
            terrainData.LightMapFilename = "data/terarins/lightmap.png";
            terrainData.TexturePathSubstitution = "data/terrains";
            // terrainData.DetailMaterial = "TerrainDetailMaterial";
            terrainData.DetailMaterial = detailMaterial;

            objTerrain.Data = terrainData;

            TorqueObjectDatabase.Instance.Register(objTerrain);
            // This needs to be called after registering, normaly its called when XML is onLoaded
            shape.GenerateCollisionData(objTerrain);

        }
#4
08/11/2009 (3:56 pm)
After much trial and debugging I also got collisions working on this terrain written with just code.

Replace the last line:
shape.GenerateCollisionData(objTerrain);

with these two instead.

shape.OnLoaded(objTerrain);
       componentPhysics.CollisionBody.UpdateShapes();

#5
10/28/2009 (12:42 am)
I am having trouble getting my terrain to work. First of all I am using the new 3.1 Torque X and the 3D Builder for this project.

I created a new project using the wizard that starts with the sandy terrain. I want to replace the terrain. I read the above tutorial and tried to match the changes into the levelData.txscene XML file. I think I got it right, but it is still not working just right.

Here are the relevant entries in the XML:

<TerrainDetailMaterial type="GarageGames.Torque.Materials.DetailMaterial" name="TerrainDetailMaterial">
      <TextureFilename>data/terrains/l3td_generated_TX</TextureFilename>
      <DetailTextureRepeat>256</DetailTextureRepeat>
      <DetailDistance>300</DetailDistance>
      <EffectFilename>DetailEffect</EffectFilename>
    </TerrainDetailMaterial>
    <Terrain type="GarageGames.Torque.T3D.XTerrain" name="Terrain">
      <Components>
        <T3DSceneComponent type="GarageGames.Torque.T3D.T3DSceneComponent" name="" />
        <T3DRigidComponent type="GarageGames.Torque.T3D.T3DRigidComponent" name="">
          <GravityScale>0</GravityScale>
          <Mass>0</Mass>
          <Kinetic>true</Kinetic>
          <Immovable>true</Immovable>
          <Velocity>
            <X>0</X>
            <Y>0</Y>
            <Z>0</Z>
          </Velocity>
          <RotationScale>1</RotationScale>
          <ResolveCollisions>true</ResolveCollisions>
          <CollisionShapes>
            <CollisionShape>
              <Shape type="GarageGames.Torque.T3D.RigidCollision.CollisionXTerrainShape" />
            </CollisionShape>
          </CollisionShapes>
          <RigidManager nameRef="RigidManager" />
          <AnimateCollisionOffsets>false</AnimateCollisionOffsets>
          <RenderCollisionBounds>false</RenderCollisionBounds>
          <RigidMaterial type="GarageGames.Torque.T3D.RigidCollision.RigidMaterial">
            <Restitution>0.0</Restitution>
          </RigidMaterial>
        </T3DRigidComponent>
      </Components>
      <Position>
        <X>1028</X>
        <Y>1028</Y>
        <Z>0</Z>
      </Position>
      <HorizontalScale>8</HorizontalScale>
      <VerticalScale>1</VerticalScale>
      <Scale>
        <X>8</X>
        <Y>8</Y>
        <Z>1</Z>
      </Scale>
      <Repeat>true</Repeat>
      <RepeatX>true</RepeatX>
      <RepeatY>true</RepeatY>
      <Data type="GarageGames.Torque.T3D.RAWTerrainData">
        <TerrainFilename>data/terrains/l3td_generated.raw</TerrainFilename>
        <LightMapFilename>data/terrains/l3td_generated_LM.jpg</LightMapFilename>
        <UniqueTextureFilename>data/terrains/l3td_generated_TX.jpg</UniqueTextureFilename>
        <DetailMaterial>TerrainDetailMaterial</DetailMaterial>
      </Data>
      <ViewError>2</ViewError>
      <LODError>0.5</LODError>
      <LevelZeroError>0</LevelZeroError>
    </Terrain>

When I run this I get a Null Reference Exception. If I take out the UniqueTextureFilename node it doesn't crash, and the new terrain shows up, but it doesn't have any texture to it. It is just a checkerboard color.

What do I do?
#6
10/28/2009 (12:49 am)
Look at this thread: we got it to work, but in code not via the XML.

http://www.garagegames.com/community/forums/viewthread/93769

#7
11/08/2009 (1:52 am)
I too got this to work...kinda. Has anyone tried this on Xbox? When I did I received a couple errors, mainly UnauthorizedAccessException. This was an easy fix (only if you have the source). I had to replace a couple lines in the source:

FileStream fs = new FileStream(_shapeName, FileMode.Open);

To:

FileStream fs = new FileStream(_shapeName, FileMode.Open, FileAccess.Read);

To make things easier just do a search on FileMode.Open.

So once I did this I thought I was in the clear....WRONG. There is a XBOX/PC conditional in the ClipMapUniqueImageCache.cs Initialize method. In the XBOX code there is a for loop that iterates mipmap levels. The first pass (i=0) is fine but subsequent passes causes line (~129):

sourceTex.GetData<uint>(i, rect, data, 0, dataSize);

to bomb out ("Value does not fall within the expected range"). Curious, I just threw a try catch in there an DoRectUpdate bombed. Trowing a try catch to swallow those exceptions rendered a black terrain on Xbox without any texture.

John, my question is...Is this a bug? Did I create my terrain incorrectly in l3dt? I followed your instuctions to the letter (I think). I have also tried multiple variations of terrains (~14) with different settings/resolutions and nothing seems to work.

PS. Just as a heads up I see this line at line number 112 in ClipMapUniqueImageCache.cs:

while (width > 1 && height >= 1);

Not sure what this is doing there.

PPS. I am using version 3.1.4. Great work on the fixes....the terrains look great on the PC.
#8
04/18/2010 (10:00 pm)
I am able to add a player and it works fine. When I introduce a new object, I instantly fall through the terrain.

I tried Richards code. The onLoad code causes the game executable to just chomp on the processor, but never loads the game window. Is it supposed to be in an if statement or something

I am also using a RAW terrain, if that has something to do with it.
#9
07/05/2010 (6:35 pm)
I'm experiencing the same issue, regardless whether I use the terrain in the pre-generated level file or programmatically create it following this example (http://www.torquepowered.com/community/blogs/view/17277).

I guess we are all stuck, so sounds like this is a bug. Will this be addressed in the upcoming beta of Torque X 3D 3.1.5?
#10
07/08/2010 (3:32 pm)
Actually, the issue only occurs when I spawn a character from a template, using the example in John's book. I first create the TorqueObject and set it as a template, then I use the Clone() call to instantiate an actual TorqueObject for the character. It is this TorqueObject that falls through the terrain. TorqueObject's that I create normally (not via Clone()) do not fall through the terrain.
#11
07/14/2010 (12:52 am)
I changed it to not spawn but still get the same result. Someone has found a possible solution in the FPS demo.