Terrain based on Heightmaps





Step 1:
File > Add Project > XNA > Content Pipeline Extension Library

Step 2:
[ContentProcessor(DisplayName = "VikPro")]
public class ContentProcessor1 : ContentProcessor<TInput, TOutput>
TInput will be TextureContent and TOutput will be ModelContent

Step 3:
The Algorithm -
Create an instance of MeshBuilder by calling StartMesh.
MeshBuilder baseBuilder = MeshBuilder.StartMesh("Terrain");

Convert input type for ease of use. We can use it just like it is if we wanted to.
input.ConvertBitmapType(typeof(PixelBitmapContent<float>));
PixelBitmapContent<float> heightFields = (PixelBitmapContent<float>)input.Mipmaps[0];
Now GetPixel will return float values.

Add each pixel as point in the Mesh builder
for (int y = 0; y < heightFields.Height; y++)
{
    for (int x = 0; x < heightFields.Width; x++)
    {                   
         Vector3 tmp;
         tmp.X = xScale * (x - heightFields.Width / 2f);
         tmp.Z = yScale * (y- heightFields.Height / 2f);
         tmp.Y = heightScale * (heightFields.GetPixel(x, y) - 1);
         baseBuilder.CreatePosition(tmp);
    }
}

Create Material content and inform XNA that the vertices will have texture coordinates.
BasicMaterialContent material = new BasicMaterialContent();
material.SpecularColor = new Vector3(.4f, .4f, .4f);
baseBuilder.SetMaterial(material);

int texCoordId = baseBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));
Create Triangle based on the points we created.
for (int y = 0; y < heightFields.Height-1; y++)
{
    for (int x = 0; x < heightFields.Width-1; x++)
    {
         AddVertex(baseBuilder, texCoordId, heightFields.Width, x, y);
         AddVertex(baseBuilder, texCoordId, heightFields.Width, x+1, y);
         AddVertex(baseBuilder, texCoordId, heightFields.Width, x+1, y+1);

         AddVertex(baseBuilder, texCoordId, heightFields.Width, x, y);
         AddVertex(baseBuilder, texCoordId, heightFields.Width, x+1, y+1);
         AddVertex(baseBuilder, texCoordId, heightFields.Width, x, y+1);
    }
}








static void AddVertex(MeshBuilder builder, int texCoordId, int w, int x, int y)
{
    builder.SetVertexChannelData(texCoordId, new Vector2(x, y));
    builder.AddTriangleVertex(x + y * w);
}


Call FinishMesh to obtain MeshContent. Use context to convert to ModelContent(output)
MeshContent terrain = baseBuilder.FinishMesh();

Whats the fuss with ContentWriter and Reader?
Model.Tag can have any generic data (object). In case we want some custom information in to be stored for runtime use, add this to Model.Tag. If this data is custom class, then create Reader and Writer to store this information (like a custom Serializer/Deserializer)





Example: Really stupid useless one – Saving information like author, version, name, etc
Suppose I want to save this ModelTag class. I need a writer that will save this info in the XNB File.
public class ModelTag
{
    public string name;
    public string author;
    public string version;

    public ModelTag(string n, string a, string v)
    {
       name = n;
       author = a;
       version = v;
    }
}
   
The writer will be something like this-
[ContentTypeWriter]
public class TagWriter : ContentTypeWriter< ModelTag >
{
   protected override void Write(ContentWriter output, TWrite value)
   {
       output.Write(value.name);
       output.Write(value.author);
       output.Write(value.version);
   }
}

The reader will be like-
protected override TRead Read(ContentReader input, TRead existingInstance)
{
    string n= input.ReadString();
    string a= input.ReadString();
    string v=input.ReadString();

    ModelTag tmp = new ModelTag(n, a, v);
    return tmp;
}

In the Process function I’ll have -
model.Tag = new ModelTag("Terrain", "Vikram", "1.0.0.0");

A very nice and comprehensive sample can be found here at Creators Club.
In this example, find the Terrain Processor. They generate the normals of each tri and save this as Tag info. This can be pretty useful.


NFS: When assigning height values from the image, the Y co-or will get the height value and not the Z component. Otherwise, the terrain will be vertical and you'll end up messing it up using CreateRotation till you finally realize it. Stupid you.





XNA Draw Calls

I've been tampering with how to construct the world.

Base Idea:

Step I:
Create a cube.

StepII:
Save the details in an XML File like -

<Blocks>
  <Block X="0" Y="0" Z="0" Height="1" Color="R" />
  <Block X="0" Y="0" Z="120" Height="1" Color="R" />
  <Block X="80" Y="0" Z="240" Height="1" Color="R" />
  <Block X="0" Y="0" Z="240" Height="1" Color="R" />
  <Block X="160" Y="0" Z="360" Height="1" Color="R" />
  <Block X="80" Y="0" Z="360" Height="1" Color="R" />
  <Block X="240" Y="0" Z="480" Height="1" Color="R" />
  <Block X="160" Y="0" Z="480" Height="1" Color="R" />
  <Block X="240" Y="0" Z="600" Height="1" Color="R" />
  <Block X="240" Y="0" Z="720" Height="1" Color="R" />
  <Block X="240" Y="0" Z="840" Height="1" Color="R" />


Step III:
Create and add blocks based on this. Set scales and Draw

Problem:
The problem with this approach is
I: We'll need around 100 - 300 blocks
II: Call mesh.Draw( ) for each of these
III: mesh.Draw( ) is an expensive operation

Solution:
Use heightmaps to generate terrain.
Although it may have more [lot more] Polys - its still a single mesh. Hence, just one Draw call.

NFS: Always create a single or may be just a few meshes for the level. As per NProf, mesh.Draw will consume 90% more CPU as compared to setting params like World, View, Projection, etc

Final Ship Model

 Well.. The ship model is complete [more like I'm bored].
Final render follows. Looks pretty decent.. So its done.. whew! And a big no-no to texturing.
















Next up.. Terrain Stuff.
Note to Future Self: Im having fun

Few Renders

Have been a bit busy these days... Well here are some of the renders of the design.





Have to say.. pretty ugly from the back. Come to think about it, wings are useless when not flying.. ill try removing them..