Using Oops! Heightmap in my project

Jan 15, 2008 at 12:34 PM
Edited Jan 15, 2008 at 12:38 PM
Hi,

I run into a problem when using your HeightMap for collision...basically, the code I use to generate my heightmap is such:
for (int x = 0; x < WIDTH; x++)
                for (int y = 0; y < HEIGHT; y++)
                {
                    terrainVertices[x + y * WIDTH].Position = new Vector3(x * terrainScale, heightData[x, y] * terrainScale, y * terrainScale);
                    terrainVertices[x + y * WIDTH].Normal = new Vector3(0, 1, 0);
.....

Whereas when I check Oops! Heightmap.cs, when it comes to generating Normals, it is
 for(int z = 0; z < depth; z++)
            {
                for(int x = 0; x < width; x++)
                {
                    index = z * width + x;
...

How should I reconcile Oops Heightmap to mine? I thought it was just that you generate it height-first, then width, while mine is width-first, then height, but it seems that I am missing something else.

Thanks in advance!
Coordinator
Jan 15, 2008 at 1:21 PM
Edited Jan 15, 2008 at 1:24 PM
Hi extrakun,

To reconcile, I'm assuming that heightData is just a 2D array of height values. If you take a look at Example 2 of the Testing Application as a guide, you can just use this array to implement the ICollisionTerrainCallback interface:

        #region ICollisionTerrainCallback Members
 
        int ICollisionTerrainCallback.Width
        {
            get { return this.heightData.GetUpperBounds(0); }
        }
 
        int ICollisionTerrainCallback.Depth
        {
            get { return this.heightData..GetUpperBounds(1); }
        }
 
        float ICollisionTerrainCallback.GetHeight(int x, int y)
        {
            return this.heightData[x, y]; 
        }
 
        #endregion

Now, to create a CollisionTerrain instance, you have to pass your terrain scale values into the constructor:
        CollisionTerrain terrain = new CollisionTerrain(terrainScale, terrainScale, terrainScale, this);

In my code, I'm assuming that the heightmap is centered at {0, 0}, so it gets offset when I create the vertices in Heightmap.cs:
        this.vertices[x + z * width].Position = new Vector3(x - (width >> 1), height, z - (depth >> 1));

But that really doesn't matter since I'm only interested in the height of the Position during collision and where it's indexed (x + z * width). The x and y components of the Position only come into play when I draw the heightmap. In your case, you are applying the terrain scale as well to the x and z components of the Position vector. This is fine as long as it's indexed correctly.

Another assumption of mine is that the triangles that make up the terrain mesh look like so :
-----> x
 
v1----v2   |
|     /|   |
|   /  |   v
| /    |   z
v3----v4
 
(y is up or Vector3.Up)
In your case, I believe it doesn't matter that you are processing width first, then height. I think you just need to be aware that my z index is your y index. Hope that helps!
Jan 15, 2008 at 2:05 PM
Hey thanks for the quick reply.

I think there should be no problem for the positioning then, but I think there is an issue with the normals because I tried using your code for the normals for my heightmap (yep, just a 2d array) and it doesn't look right. I will check the vertices winding. Here's how I am generating the normals now:
for (int x = 1; x < WIDTH - 1; x++)
            {
                for (int y = 1; y < HEIGHT - 1; y++)
                {
                    Vector3 normX = new Vector3((terrainVertices[x - 1 + y * WIDTH].Position.Y - terrainVertices[x + 1 + y * WIDTH].Position.Y) / 2, 0, 1);
                    Vector3 normY = new Vector3(0, (terrainVertices[x + (y - 1) * WIDTH].Position.Y - terrainVertices[x + (y + 1) * WIDTH].Position.Y) / 2, 1);
  
                    terrainVertices[x + y * WIDTH].Normal = normX + normY;
                    terrainVertices[x + y * WIDTH].Normal.Normalize();
                    terrainNormal[x,y] = terrainVertices[x + y * WIDTH].Normal;
                }
            }

I am also using a index buffer for to render the final result, and I believe there may be a problem in the vertice winding:
int[] terrainIndices = new int[(WIDTH - 1) * (HEIGHT - 1) * 6];
            for (int x = 0; x < WIDTH - 1; x++)
            {
                for (int y = 0; y < HEIGHT - 1; y++)
                {
                    terrainIndices[(x + y * (WIDTH - 1)) * 6] = (x + 1) + (y + 1) * WIDTH;
                    terrainIndices[(x + y * (WIDTH - 1)) * 6 + 2] = (x + 1) + y * WIDTH;
                    terrainIndices[(x + y * (WIDTH - 1)) * 6 + 1] = x + y * WIDTH;
 
                    terrainIndices[(x + y * (WIDTH - 1)) * 6 + 3] = (x + 1) + (y + 1) * WIDTH;
                    terrainIndices[(x + y * (WIDTH - 1)) * 6 + 5] = x + y * WIDTH;
                    terrainIndices[(x + y * (WIDTH - 1)) * 6 + 4] = x + (y + 1) * WIDTH;
                    
                }
            }

Think I am doing it in the opposite direction or something, according to your diagram.

Coordinator
Jan 15, 2008 at 6:58 PM
Yep. I think your normal calculations are a bit off. You can calculate a normal for a vertice many ways but normally you just try to get the normaized sum of the face normals of the triangles that are made from the vertex you are trying to calculate the normal for (Read that again!):
+---+---+
|  /| x/|
| / | / |
|/x |/x |
+---v---+
| x/| x/|
| / | / |
|/x |/  |
+---+---+

So, in the sweet picture above, the vertex v's normal would be the normalized sum of all the faces marked with an x. I'm not sure what you are trying to do with your calculations.

Looking at my previous post, your indices for one cell should be {v1, v2, v3} and {v2, v3, v4}. In the code above, it looks like you have {v4, v1, v2} and {v4, v3, v1}. Your triangles will end up looking like this:
+---+
|\  |
| \ |
|  \|
+---+

The opposite diagonal but still winding in the right direction. Collision won't look right, but it should still be drawing correctly.
Jan 16, 2008 at 3:01 AM
So after spending hours on trying to modify your normal generation code, it could be due to the vertex winding instead. Ouch. I'll try to look into it - the rendering is all right as I am using the normal generations designed for that winding, but yes, collision is a bit off. I just wonder what else may break if I change the winding now...