Advertisement

Getting terrain height properly

Started by March 04, 2002 01:43 PM
11 comments, last by Lupson 22 years, 11 months ago
Hi everyone! I''ve searched this and other forums for a solution for this problem, but I haven''t found a solution yet so I''ll be grateful if someone could help out. Im pretty new to OGL so I started out doing an learning a lot of NeHe''s excellent tutorials. I then started modifying Lesson35, Height maps. Ive added textures, a working mouselooking, controllable camera and so on - but for the last week I''ve tried to get a working solution for keeping the camera or objects above the ground level. I''ve tried using the built-in "Height"-function - I''ve tried a method using bilinear interpolation for getting the height. (by DJ_GL http://www.gamedev.net/community/forums/topic.asp?topic_id=62447 ) - nothing works. The bilinear interpolation method for example, seems to be getting decent data from the heightmap, but suddenly when I move across the landscape it suddenly change from - to +, or jumps 120 units up or downward, or other strange stuff. It seems as if the Camera position x and z doesn''t correspond with how the terrain is rendered. I''ve tried fiddling with the scaleValue since my code for following an object flying across the world (using GLuLookAt) went crazy if I didn''t multiply the objects world- x,y,z positions with scaleValue when telling GLuLookAt where to look. Sorry for the long post - but can anyone point me in a good direction for checking camera/object altitude? I can post my code if anyone wants to, but it''s Lesson 35 concerning the Heightmap. Thx in advance!
Ok, I (almost) got it working - now it follows the terrain smoothly, except for terrible jumping and blinking and stuff. I modified the code I referenced in my last post to what''s below:

This code delivers either accurate Height data or really weird numbers:

I call the GetHeight function like this:

CamPos.y = GetHeight(g_HeightMap, CamPos.x/scaleValue, CamPos.z/scaleValue) * scaleValue;

float GetHeight(BYTE *pHeightMap, float X, float Z)
{
int x = (int)X;
int z = (int)Z;
// Get height data from all four corners
float h00 = (float)pHeightMap[(x+z*MAP_SIZE)];
float h01 = (float)pHeightMap[((x+1)+z*MAP_SIZE)];
float h11 = (float)pHeightMap[((x+1)+(z+1)*MAP_SIZE)];
float h10 = (float)pHeightMap[(x+(z+1)*MAP_SIZE)];

float tx = X/STEP_SIZE - float(x);
float ty = Z/STEP_SIZE - float(z);

// the next step is to perform a bilinear interpolation
// to compute the height
// of the terrain directly below the object.
float txty = tx * ty;

return h00 * (1.0f - ty - tx + txty)
+ h01 * (tx - txty)
+ h11 * txty
+ h10 * (ty - txty);
} // end GetHeight()

Example return data from above function when moving forward(where the around 32 range is correct data:

31.900002, 31.900002, 32.050003, 32.050003,-14061.422852, 32.200001, 32.350002, -6.007955, 32.500000, 32.650002,-14679.510742, -6.658030, 14855.677734, 33.100002, -15091.593750
-7.308109, 33.400002, -15359.470703, 15432.100586, 33.700001, 33.700001, 33.700001

Anyone got a slightest clue what causes this strange behaviour?
Regards Lupson
Advertisement
not really, but where you have:

float h00 = (float)pHeightMap[(x+z*MAP_SIZE)];
float h01 = (float)pHeightMap[((x+1)+z*MAP_SIZE)];
float h11 = (float)pHeightMap[((x+1)+(z+1)*MAP_SIZE)];
float h10 = (float)pHeightMap[(x+(z+1)*MAP_SIZE)];

perhaps you should try putting some brackets in like:

float h00 = (float)pHeightMap[((x+z)*MAP_SIZE)];
float h01 = (float)pHeightMap[(((x+1)+z)*MAP_SIZE)];
float h11 = (float)pHeightMap[(((x+1)+(z+1))*MAP_SIZE)];
float h10 = (float)pHeightMap[((x+(z+1))*MAP_SIZE)];

because c++ could be doing, say 4 + (4*MAP_SIZE) instead of (4+4)*MAP_SIZE
I don''t think the problem is in the way the C++ compiler follows order of operations. More likely this code snippet''s odd xyz orientation has gotten some variables mixed up somewhere.

I can''t really determine the problem without seeing the modified source code, but I can give some general pointers.

First, when rendering terrain it is conventional to consider the xy-plane horizontal, and the positive Z axis as vertical. There''s really no reason to flip this all around since OpenGL is capable of rendering any given scene from an arbitrary camera position and angle.

If you start your camera at a given height over a given x,y position, you should be able to recalibrate its proper height no matter what x,y position it moves to based on the nearest 3 (not four) points.

However having said that, allowing full 6-degrees of free camera movement AND terrain-following in the same program, even a simple heightmap, is no small feat, even if you''re not trying for smooth camera movement. My advice would be to start simple:

1. rewrite the drawing code so that the heightmap points are on the xy-plane, with the mountains going up the positive z axis.
2. put the camera at a fixed height above the landscape, angled so that you get a good enough view to tell where you are on the heightmap (apart for printing coords).
3. make the arrow keys move in the +x direction (right arrow), -x (left arrow), +y (up arrow), -y (down arrow). Point the camera slightly toward the terrain, in the +y direction (+z is the camera''s up-vector). This will make the camera move as though you''re strafing in Quake.
4. now that you have a very simple camera movement setup, you can go back and fix the GetHeight() code.
which was probably what was confusing you to begin with, but I can''t explain it at 4:00am. I''ll post the algorithm tomorrow if someone else doesn''t beat me to it.
Thanks for your replies. Concerning the brackets it was fine the way it was. Since I (or NeHe, to be honest..) store the heightdata in an one-dimensional array [1024*1024] one has to get the height values with the used formula. I tried wAVaRiaN''s bracket version though, but then it didn''t retrieve any correct data. As for Chromebenders suggestions; While I see your point in changing the planar coords - I''ve always thought of the x-plane as the horizontal, y as vertical and z as depth. Well...Im so close now so I''d rather stick with my current xyz setup. I''ve posted all the source code here:

http://w3.adb.gu.se/~s99luppe/openGL/Lesson35/Lesson35.cpp

It''s kinda messy right now, but I''ve tried to comment the important stuff.

If I set a static CamPos.y I can mouselook, move, strafe just fine. I can also by modifying the code a little move up and down in the camera direction. I used a variant of the code posted above for setting player height, instead of the bilinear interpolation I simply added the four height values together and split by 4. That method caused no strange jumping but instead gave a very unsmooth camera because it would jump in 0.25 "unit" leaps when repositioning the camera in the y-plane.

I''d be very grateful for further help, perhaps another GetHeight algoritm will do the trick.
Thx /Lupson
Here it is:
float GetHeight(float pointX, float pointY)
{
float pointZ;
float vector1[3], vector2[3], vector3[3];
float nearPointA[3], nearPointB[3], nearPointC[3];
int nearAX, nearAY;
float planeK;
/* set x,y,z coords for nearest point ''A'' */
nearPointA[0] = (float) floor(pointX);
nearAX = (int) nearPointA[0];
nearPointA[1] = (float) floor(pointY);
nearAY = (int) nearPointA[1];
nearPointA[2] = g_HeightMap[nearAX + nearAY*MAP_SIZE];
/* set x,y,z coords for nearest point ''B'' */
nearPointB[0] = (float) nearAX + 1;
nearPointB[1] = (float) nearAY + 1;
nearPointB[2] = g_HeightMap[(nearAX+1) + (nearAY+1)*MAP_SIZE];
/* set x,y,z coords for nearest point ''C'' */
nearPointC[0] = (float) nearAX;
nearPointC[1] = (float) nearAY + 1;
nearPointC[2] = g_HeightMap[(nearAX) + (nearAY+1)*MAP_SIZE];
/* calculate in-plane vectors (switching order of vector1, vector2 since left-handed coord system */
vector1[0] = nearPointB[0] - nearPointA[0];
vector1[1] = nearPointB[1] - nearPointA[1];
vector1[2] = nearPointB[2] - nearPointA[2];
vector2[0] = nearPointC[0] - nearPointA[0];
vector2[1] = nearPointC[1] - nearPointA[1];
vector2[2] = nearPointC[2] - nearPointA[2];
/* perform cross-product to find normal vector */
vector3[0] = vector1[1]*vector2[2] - vector2[1]*vector1[2];
vector3[1] = vector1[2]*vector2[0] - vector2[2]*vector1[0];
vector3[2] = vector1[0]*vector2[1] - vector2[0]*vector1[1];
/* find constant in 3-d plane eqn */
planeK = nearPointA[0]*vector3[0];
planeK += nearPointA[1]*vector3[1];
planeK += nearPointA[2]*vector3[2];
/* now that the xyz equation for the plane of the quad is known,
find the Z coordinate on the plane using the known x, y coords of the camera */
pointZ = (planeK - pointX*vector3[0] - pointY*vector3[1])/vector3[2];
/* point Z is the value of the z coordinate ON the plane, so add the desired height */
return pointZ;
}
Advertisement
You know that feeling, when your body gets pleasantly warm, everything is wonderful and you feel like the world lies beneath your keyboard....thanks man! It works almost, almost perfectly. It follows the groud nicely, no strange jumping whatsoever - the only thing is that the height transitions when moving across ground is a little bit bumpy, but I''ll try writing a function to smooth out the transitions over a couple of rendering passes or something. Now I just have to sit down and really understand your function. Thanks again! Drop me an email if you ever need help with....hmmm....you''ll figure something out
Sorry to bother you again, but I simply can''t get rid of the unsmooth Height transitions. I don''t know if thats the right way to put it though, when moving the camera across the landscape it sure follows the ground but it jerks up and down in small steps all the time. Same with my ugly little square shooter which shoots a square in the direction of the camera. The square uses the same (Chromebenders code posted in this thread) GetHeight code for terrain following.

If I shoot it down a hill, it goes down the hill evenly and then jumps up and sticks to an even number for a few frames, then continuing downwards. Data from function where H: is the value returned by GetHeight when shooting down an evenly sloped hill:

H:170.681076 H:170.482727 H:170.284378 H:170.086029 H:169.887680 H:169.689331 H:169.490982 H:169.292633 H:169.094284
To this point, nice and even vertical movement - then a sudden leap to 170.0000->
H:170.000000 H:170.000000 H:170.000000 H:170.000000 H:170.000000
And here it continues as it should.->
H:168.904190 H:168.705841 H:168.507492 H:168.309143 H:168.110794 H:167.912445 H:167.714096 H:167.515747 H:167.317398 H:167.119049 H:166.920700 H:166.722351 H:166.524002 H:166.325653 H:166.127304
Same thing here again:
H:167.000000 H:167.000000 H:167.000000 H:167.000000 H:167.000000 H:165.937210 and so on

If one studies the X and Z coords (Y is up Vector) one sees that whenever the Z coord changes from ex) 231.05532 to 230.99943 the H: value jumps up in that peculiar fashion.
For more example data with X and Z coords:
http://w3.adb.gu.se/~s99luppe/openGL/Lesson35/out.txt
Im really sorry to bother the community with this, but I''m simply lost here.
Cheers!
/Lupson

What is the STEP_SIZE you''re using? I''m wondering if maybe the code is working correctly, but following tiny flat ridges intthe map that occur frequently but are too small see easily... Also, the GetHeight() code should work correctly as long as each argument is in the range [0..255]. Oh, here''s something important: the GetHeight() code as I wrote operates as if the STEP_SIZE is 1, regardless of what STEP_SIZE is for the purposes of drawing the quads. So if you want the camera to follow exactly what is on the screen you''ll need to tweak the X,Z coordinates some. I''d use something like

nearPointA[0] = (float) floor(pointX);
nearAX = (int) nearPointA[0];
nearAX = nearAX / STEP_SIZE; /* now the X coordinate matches a vertex on the screen */
and so on...

However, even when you get the camera height working correctly movement will still be somwhat jerky whenever you cross over the boundary between two squares that aren''t coplanar. I would suggest reading up on quaternions and trying to implement the camera coordinate system that way. In a nutshell, quaternions are a different way of expressing and transforming 3D coordinates so that you input frames that are evenly spaced in 3D space (like camera positions on 3 different squares) and you end up with a bunch of coordinates that are evenly spaced in TIME (so that the camera moves smoothly, at a uniform rate per frame). Obviously using quaternions is a somewhat more advanced 3D programming topic, but well worth looking into.
Thanks man, I was thinking of the stepping issue too . The step size simply tells the renderer how large each QUAD should be. IE, since Im using an 1024*1024 Heightmap, I'll get 1024/STEP_SIZE quads on the x or z axis. (EDIT: Misunderstood your post, Im currently using a STEP_SIZE of 12 END EDIT)

I also found another function for getting player height which I modified to my needs:

float GetTerrainHeight(float x, float y){

short mX, mZ;
float fmX, fmZ, ret;

fmX = x; //Put between 0.0->63.0
fmZ = y; //CamPos.z/SCALE_VALUE; //Put between 0.0->63.0
mX = fmX; //Put between 0->63
mZ = fmZ; //Put between 0->63
//Get distance from Integer point (always will be >= 0.0 and < 1.0
fmX = fmX-(float)mX;
fmZ = fmZ-(float)mZ;

ret = (1.0-fmX)*(1-fmZ)*g_HeightMap[(mX+mZ*MAP_SIZE)]; //Top Left Corner
ret+= fmX*(1.0-fmZ)*g_HeightMap[((mX+1)+mZ*MAP_SIZE)]; //Top Right Corner
ret+= (1.0-fmX)*fmZ*g_HeightMap[(mX+(mZ+1)*MAP_SIZE)]; //Bottom Left Corner
ret+= fmX*fmZ*g_HeightMap[((mX+1)+(mZ+1)*MAP_SIZE)]; //Bottom Right Corner

//This basically finds out where you are in the quad, and
//calculates what your height would be at that position
//without needing collision detection... this is the simplest way
return ret;
}

This one uses 4 points instead and produces smoother, although not perfect object movement. I'll try your new STEP_SIZE code now. Thanks again!

Edited by - Lupson on March 6, 2002 1:33:21 PM

This topic is closed to new replies.

Advertisement