Plains Terrain: Part 1 - Starting Out


So I've been looking for a new procedural project (after my OpenGl maze and basic terrain generator.) I want to, eventually, make something like Project Frontier eventually, but I haven't had enough practice with OpenGl and I'm not good enough at programming yet.

So, for now, I'm going to make a Plains terrain generator. That is, I'm going to make a program that will generate one biome of terrain, namely the savanna, or something close to it.

My goal isn't to make something that's realistic, just something that looks very pretty and is procedurally generated. I'm not going to generate my own textures, at last most of them. I also want to have lighting, a sky, some kind of sunset/rise as well. Maybe trees, if I'm feeling adventurous.

Let's get started.

I'm gonna start by pulling in code from my old terrain project, which, at this point, doesn't actually work. It also loads the vertex and texture data into a straight float array, which I don't like, it looks ugly and it's hard to work with. So I'm going to switch it over to a more object oriented method.

For example, this is what I'm using.

for (int ix = 0; ix < TileData.terrainWidth; ix++)
            {
                for (int iy = 0; iy < TileData.terrainHeight; iy++)
                {

                    int curL = curLoc * 72;
                    int curT = curLoc * 8;

                    TileData.verts[curL] = ix * scaleFactor; // first 0, 0
                    TileData.verts[curL + 1] = heightByte[ix, iy] * heightFactor;
                    TileData.verts[curL + 2] = iy * scaleFactor;

                    TileData.verts[curL + 3] = ix * scaleFactor;
                    TileData.verts[curL + 4] = heightByte[ix, iy + 1] * heightFactor;
                    TileData.verts[curL + 5] = (1 + iy) * scaleFactor; // second 0, 1

                    TileData.verts[curL + 6] = (1 + ix) * scaleFactor;
                    TileData.verts[curL + 7] = heightByte[1 + ix, 1 + iy] * heightFactor;
                    TileData.verts[curL + 8] = (1 + iy) * scaleFactor; // 1, 1

                    TileData.verts[curL + 9] = (1 + ix) * scaleFactor; // 1, 0
                    TileData.verts[curL + 10] = heightByte[1 + ix, iy] * heightFactor;
                    TileData.verts[curL + 11] = iy * scaleFactor;

                    TileData.NumVertex += 4;

                    TileData.texVerts[curT] = 0;
                    TileData.texVerts[curT + 1] = 0.25f;

                    TileData.texVerts[curT + 2] = 0;
                    TileData.texVerts[curT + 3] = 0;

                    TileData.texVerts[curT + 4] = 0.25f;
                    TileData.texVerts[curT + 5] = 0;

                    TileData.texVerts[curT + 6] = 0.25f;
                    TileData.texVerts[curT + 7] = 0.25f;
                    curLoc++;
               
                }
            }
And this is what I'd rather use.




for (int ix = 0; ix < TileData.terrainWidth; ix++)
            {
                for (int iy = 0; iy < TileData.terrainHeight; iy++)
                {

                    int curL = curLoc * 4;
                    
                    TileData.Verts[curL].x = ix * scaleFactor;
                    TileData.Verts[curL].y = heightByte[ix, iy] * heightFactor;
                    TileData.Verts[curL].z = iy * scaleFactor;

                    
                    TileData.Verts[curL + 1].x = ix * scaleFactor;
                    TileData.Verts[curL + 1].y = heightByte[ix, iy + 1] * heightFactor;
                    TileData.Verts[curL + 1].z = (1 + iy) * scaleFactor;

Okay, I've ported the terrain class over to more OOP style. Try and run it? Black screen. Well, I didn't really expect it to work the first time anyway. It's better than crashing.

Herp derp. It's useful to tell OpenGl that you want to draw..well, anything. I was missing some initializations, add those and the terrain works perfectly. Excellent.

Of course, the textures don't work, let me fix that.
I derp'd again. It's nice to have actual texture coordinates, not the same one for everything. Everything works exactly as it was suppose to before, and it looks better. Awesome.



Now I need to do some optimizations to the drawing code, which, because I'm using VBOs (vertex buffer objects) means that I need to use glBufferSubData to edit the data that's already in the buffer.

Okay, screw that for now. I'll do it later. Right now I want to make a grid of my terrain squares, which is what everything will be made up of. Let's see if drawing multiple squares of terrain works.



Yup. That works. Cool. Now, I need to make it into a grid format. Generally, if you're working in 2D (which I sort of am) you do something like this.

for(int ix = 0; ix < gridMaxX; ix++)
{
     for(int iy = 0; iy < gridMaxY; iy++)
          {
               placeSquare(ix * squareWidth, iy * squareHeight);
          }
}

Obviously, that's pesudocode, but you get the point. Trying that in OpenGl gives me this.



Well..that's not what I wanted. What's going on? Looking at a few debug messages, I don't get it. It gives all the right data.

0, 0
0, 512
0, 1024
512, 512
512, 1024
1024, 512
1024, 1024

Nothing is rendering correctly, except the first two squares. I am confused. Let's mess around with some values.

I figured out why it's not working. Let me explain. I'm using this to make the translations.

terrain[ix, iy].LoadTerrain(ix * 512, iy * 512);

// In Terrain class
Gl.glTranslatef(origin.x, 0, origin.y);

The way OpenGl does it's translations, and actually the way all 3D programs do their translations, is you have a world matrix, and then a transformation matrix. You then multiply the world matrix by the transformation matrix, it's basic matrix math. When you do that, you have something like this: 

World Matrix     Transformation Matrix
[0, 0, 0]             [0, 0, 512]

 Result:
World Matrix
[0, 0, 512]

Then, if you try and do the transformation matrix again, you end up with this.
[0, 0, 1024]

Okay, that makes sense, yeah. Of course, that's not how I was thinking it would work. I'm used to placing 'squares' with absolute values, so when I tell something to go at 512 units, I'm saying place yourself at Z 512, then I say place something at Z 1024, I want it to go there.
OpenGl thinks I'm saying place the first one at Z 512 ([0,0,0] x [0,0,512] = [0,0,512]) and the second one at 1536 ([0,0,512] x [0,0,1024] = [0,0,1536]). You can see the problem, of course. You get that multiplicative growth shown above.

Awesome, I figured it out. Uhm..here's what I did to fix it, easier than explaining it. If you don't understand it, don't worry. Just say that I needed some special conditions to tell it to not have values over 512, and sometimes have negative values to move back up to the top of the grid.


for (int ix = 0; ix < terrain.GetUpperBound(0) + 1; ix++)
{
 for (int iy = 0; iy < terrain.GetUpperBound(1) + 1; iy++)
 {
  terrain[ix, iy] = new Terrain();

  int tX = ix * 512;
  int tY = iy * 512;
  
  if (tX > 512)
   tX = 512;

  if (iy == 0 && ix != 0)
  {
   tX = 512;
   tY = -((terrain.GetUpperBound(1)) * 512);
  }
  
  if (iy > 0 && tX == 512)
   tX = 0;
  if (tY > 512 && iy != 0)
   tY = 512;
  terrain[ix, iy].LoadTerrain(tX, tY);
  terrain[ix, iy].setiTextures(iTextures);
 }
}

So, here's what that does.



Perfect grid, exactly what I want. Obviously there's no stitching, or anything to make each grid of terrain like the others, but that'll come later.

Also, there are two rows of vertices that don't have height value, so they're down at Y 0, which won't work. I'll fix that later.

I can also make huge grids now, this is a 20x20 (400 terrain squares at 64 verts each, 25,600 vets total.) It takes about 5 seconds to load, which is unacceptable, as I'm going to have to generate more terrain on the fly, but I'll fix that later.

0 comments:

Post a Comment