Heightmap collision detection?

Started by
7 comments, last by jonathanc 16 years ago
Hi all! I have recently started to learn OpenGL and its been great so far due to the vast resources available online. However I am stuck when it came to rendering heightmaps. Basically, I have a camera class and also a working terrain loader/renderer in the code. I can render the heightmap from a raw file fine and also move my model as 3rd person around the heightmap. However, I noticed that sometimes my char goes through the hills and that is not that ideal. My terrain renderer (from nehe) have a function to return the height of the heightmap at a given x and y coordinates. I have so far tried a simple height difference test between subject and heightmap height output but I noticed that the movements are very "choppy". It just don't look natural at all. Also sometimes the character will still go into the hills and cause the whole program to crash out. I am wondering if anyone could give some advice/examples to remedy this problem. I would like to say that I am not an expert programmer so please be gentle with me :D I can include my code if needed ( /code works here?) Thanks all and I hope someone can help me out.
Advertisement
You'll probably want to do real triangle collision instead of just heightmap look-ups.
Google for "triangle sphere collision"
while (tired) DrinkCoffee();
Quote:Original post by polymorphed
You'll probably want to do real triangle collision instead of just heightmap look-ups.
Google for "triangle sphere collision"


Hi !

Thanks for the reply! However I noticed that the methods used are for sphere/epsiloid to an even plane. Technically I am not testing vs. a plane but rather a point (points could vary every pixel due to the heightmap's resolution etc). This causes a "stutter" in the detection and hence makes my character looks as though it's bouncing up and down at a fast speed.

I am wondering if there is any way to fix this? I have not tried the more complicated solution but I have tried sphere/plane and ray intersection. I used the height value (y) of the map as char.x and char.z as a reference plane.

here is my pseudo code if anyone is kind enough to have a look through :P
void RenderScene() {	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);		glLoadIdentity();					CheckAndComputeInputs();//left these few lines in in case I did something wrong	model.heading.x = cos(90-model.direction);	model.heading.z = sin(90-model.direction);	Camera.x += ((View.x + Heading.x * -6) - Camera.x) / 3;	Camera.y += ((View.y + Heading.y * -6 + 1) - Camera.y) / 3; 	Camera.z += ((View.z + Heading.z * -6) - Camera.z) / 3;		model.position.x+=model.speed.x;	model.position.z+=model.speed.z;		View.x = model.position.x;	View.y = model.position.y + 1;	View.z = model.position.z;	model.speed.x /= airdrag;	model.speed.y /= airdrag;	model.speed.z /= airdrag;			gluLookAt(Camera.x, Camera.y, Camera.z, // the position 	View.x, View.y, View.z, //the View	0.0f,1.0f,0.0f); //the Up-Vector	// I am trying to average the points around the character	float a = (float)Height(g_HeightMap, (int)model.position.x, (int)model.position.z);	float b = (float)Height(g_HeightMap, (int)model.position.x+0.05, (int)model.position.z+0.05);	float c = (float)Height(g_HeightMap, (int)model.position.x-0.05, (int)model.position.z-0.05);	float d = (float)Height(g_HeightMap, (int)model.position.x+0.05, (int)model.position.z);	float e = (float)Height(g_HeightMap, (int)model.position.x, (int)model.position.z+0.05);	float f = (float)Height(g_HeightMap, (int)model.position.x-0.05, (int)model.position.z);	float g = (float)Height(g_HeightMap, (int)model.position.x, (int)model.position.z-0.05);	float ave = (a+b+c+d+e+f+g)/7;	if (model.position.y < ave+2)	{		float newPos = ave+2 ;		model.position.y = newPos;	}	if (model.position.y > ave+5)	{		float newPos = ave + 5;		model.position.y = newPos;	}	glPushMatrix();	StencilShadow();	DrawGround();	MoreStencilShadow();	// these is the "shadow"	// left a good chunk in	glTranslatef(model.position.x, model.position.y, model.position.z);	glScalef(0.04f,0.04f,0.04f);	glRotatef(model.direction*180/PI -110,0.0,1.0,0.0);	g_Model.DrawModel();	glDisable(GL_CULL_FACE);	glEnable(GL_TEXTURE_2D);	glEnable(GL_DEPTH_TEST);	glDisable(GL_BLEND);	glEnable(GL_LIGHTING);	glPopMatrix();	// end of draw shadow	glDisable(GL_STENCIL_TEST);	// Here I draw the character	glPushMatrix();	glEnable(GL_TEXTURE_2D);	glEnable(GL_CULL_FACE); // Turn back face culling on	glCullFace(GL_FRONT);		glTranslatef(model.position.x, model.position.y, model.position.z);	glScalef(0.04f,0.04f,0.04f);	glRotatef(model.direction*180/PI -110,0.0,1.0,0.0);	glColor3ub(255, 255, 255);	g_Model.DrawModel();	glDisable(GL_CULL_FACE);	glDisable(GL_TEXTURE_2D);	glPopMatrix();	SwapBuffers(g_hDC);}



[Edited by - jonathanc on March 31, 2008 9:54:36 PM]
Quote:Original post by jonathanc
I have recently started to learn OpenGL and its been great so far due to the vast resources available online.
Take care, most of those resources are badly out of date, with only a few exceptions.
Quote:Original post by jonathancI have so far tried a simple height difference test between subject and heightmap height output but I noticed that the movements are very "choppy". It just don't look natural at all. Also sometimes the character will still go into the hills and cause the whole program to crash out.
This has nothing to do with GL, it's an indicator you're probably overrunning some array or stuff like this. Try using small heightmaps such as 4x4 and think at the various reads/writes, this will probably give you an hint on where the error could be.
Also, _ASSERTE(_CrtCheckMemory()) is your friend. Drop in a few of them.
Quote:Original post by polymorphed
You'll probably want to do real triangle collision instead of just heightmap look-ups.
Not really. Heightmaps are 2D entities and such, heightmap lookups are sufficient when properly implemented. Assuming he's not perturbing the heightmap with high-frequency signals, the triangles are just linear interpolations of a function of the heightmap value, as such, increasing the processing data won't give out much.

Previously "Krohm"

@Krohm

Thanks for the advice :D And yes there are a lot of contradicting opinions out there. I have read up a bit on the sphere/plane collision etc but I feel that I do not need to go that detail for this app. I just want to render a char moving on my heightmap without going into the terrain. I will see what I can do :D

by the way, did you look at my code snippet ? It's the Renderscene func which is the core of it all at the moment. I couldn't find anything wrong at the moment but maybe you could spot a mistake or two.
You should try something like this.
This is quite the same as you did, but with some poundering (? sorry I'm french and my english's not always what I'd like it to be) (weighting's the right word ?) each vertex. Looking at your code, if your player jumps I can understand that's because you have no weighting of your position versus how far you are from each point, so when you change coords then the average just change.

// I am trying to average the points around the characterint ModelPosX = (int)model.position.x;int ModelPosZ = (int)model.position.z;float h00 = (float)Height(g_HeightMap, ModelPosX, ModelPosZ);float h10 = (float)Height(g_HeightMap, ModelPosX+1, ModelPosZ);float h01 = (float)Height(g_HeightMap, ModelPosX, ModelPosZ+1);float h11 = (float)Height(g_HeightMap, ModelPosX+1, ModelPosZ+1);float FracModelPosX = model.position.x - ModelPosX;float FracModelPosZ = model.position.z - ModelPosZ;float ave = h00*(1.0f-FracModelPosX)*(1.0f-FracModelPosZ) + h10*FracModelPosX*(1.0f-FracModelPosZ) + h01*(1.0f-FracModelPosX)*FracModelPosZ + h11*FracModelPosX*FracModelPosZ;// so if i'm near X.Z, then result is h00, near X+1.Z > h10, X.Z+1 > h01, X+1.Z+1 > h11;// if i'm half way between h00 and h10; FracPosX = 0.5 FracPosZ = 0, result is 0.5*h00 + 0.5*h10, seem to be ok.model.position.y = ave;// optional : average Y over time, warning this is heavy frame-rate dependant// model.position.y = (model.position.y * 4.0f + ave)/5.0f;//float ave = (a+b+c+d+e+f+g)/7;/* // no need for this ?if (model.position.y < ave+2){float newPos = ave+2 ;model.position.y = newPos;}if (model.position.y > ave+5){float newPos = ave + 5;model.position.y = newPos;}*/


I personnally use some triangle detection using heights, but if you're just starting that may be complex because it depends on how the triangles are rendered (ask and i'll paste some of my old my code, but i'll leave it to you to interpret it :)).
Quote:Original post by Kaeltyk
You should try something like this.
This is quite the same as you did, but with some poundering (? sorry I'm french and my english's not always what I'd like it to be) (weighting's the right word ?) each vertex. Looking at your code, if your player jumps I can understand that's because you have no weighting of your position versus how far you are from each point, so when you change coords then the average just change.

*** Source Snippet Removed ***

I personnally use some triangle detection using heights, but if you're just starting that may be complex because it depends on how the triangles are rendered (ask and i'll paste some of my old my code, but i'll leave it to you to interpret it :)).


Thanks a lot Kaeltyk! Your suggestion worked brilliantly! :D I will try the second bit once I optimise the rest of my code a bit. It just feels so great when your code finally turns out to be something awesome on the screen.

To be honest , I am using a height map loader code from a source I have found online and it isn't mine. However, I understand how it works though because it's pretty straight forward and simple.

Thanks a lot again and yes, I would be interested at looking at your code if it is possible (at looking at, hmmm...my english isn't that good either :P) [cpp] a [/cpp]

[Edited by - jonathanc on March 31, 2008 9:44:49 PM]
You're welcome, I've gone throught these computations before :).

Now to explain a bit more, the above code is exact only when your quad is flat. But this is usually not the case as we render triangles and a heightmap with different heigths for each corner of the quads.

example of when it's not visually accurate :
h00 = 0; h01 = 0; h10 = 0; h11 = 10; player at center.
The average given by the formula in the center is 2.5, which is ok from the average point of view. But as we rendered the quad as triangles, it is not the height of the visual the player is on.
Either the triangles were rendered (h00-h01-h10 then h10-h01-h11) then the player should be at height = 0.0 (he is on the h10-h01 edge), or the triangles are rendered (h00-h01-h11 then h11-h10-h01) and the player should be at height 0.5 (he is on the h00-h11 edge).

The following code works when you render triangles in the h00-h01-h10 order (and looking at the NeHe code this is not your case if you used the height map tutorial code)

rough code extracted from my old engine:
void CMap::PutObjectOnGround(CObject* Obj){	// Lookup for Object Cell 	// Cell X-Y have coordinates that are X-TableXSize/2.f, Y-TableYSize/2.f to same+1	float fX = Obj->Location.X + TableXSize/2.f;	float fY = Obj->Location.Y + TableYSize/2.f;	int X = (int)fX;	int Y = (int)fY;	float ZHeight = 0.f;	if ( X>=0 && X<TableXSize-1 && Y>=0 && Y<TableYSize-1 )	{		float XFrac = fX - X;		float YFrac = fY - Y;		if ( XFrac + YFrac < 1 )		{	// We're on Left triangle (X,Y) (X+1,Y) (X,Y+1) of the tile			// Plane z = ax + by + c			// x=0 y=0 > c = Cellheight[X.Y] 			// x=1 y=0 > a+c = CellHeight[X+1.Y] > a = Cellheight[X+1/Y] - c;			// x=0 y=1 > b+c = CellHeight[X.Y+1] > b = CellHeight[X.Y+1] - c			float c = CellHeights[(int)X+(int)Y*TableXSize];			float a = CellHeights[(int)X+1+(int)Y*TableXSize] - c;			float b = CellHeights[(int)X+(int)(Y+1)*TableXSize] - c;			ZHeight = XFrac * a + YFrac * b + c;		}		else		{	// We're on Right triangle (X+1,Y) (X+1,Y+1) (X,Y+1) of the tile			// Plane z = ax + by + c			// x=1 y=0 > a + c = Cellheight[X+1.Y]			// x=1 y=1 > a+b+c = CellHeight[X+1.Y+1]			// x=0 y=1 > b+c = CellHeight[X.Y+1]			// > b = CellHeight[X+1.Y+1] - Cellheight[X+1.Y];			// > c = CellHeight[X.Y+1] - b;			// > a = Cellheight[X+1.Y] - c;			float b = CellHeights[(int)X+1+(int)(Y+1)*TableXSize] - CellHeights[(int)X+1+(int)Y*TableXSize];			float c = CellHeights[(int)X+(int)(Y+1)*TableXSize] - b;			float a = CellHeights[(int)X+1+(int)Y*TableXSize] - c;			ZHeight = XFrac * a + YFrac * b + c;		}		Obj->Location.Z = Obj->Height + ZHeight*CELLHEIGHT;//		CellTable[(int)X+(int)Y*TableXSize].CellColor[0] = 1.f;	}	else	{		Obj->Location.Z = Obj->Height;	}}


Overall you need to check which triangle you're on, and solve the Height = a*FracX + b*FracY + c equation, knowing that
h00 = c (because FracX=0 and FracY=0)
h10 = a+c (because FracX=1 and FracY=0)
h01 = b+c (because FracX=0 and FracY=1)
h11 = a+b+c (because FracX=1 and FracY=1)
and use only 3 of the above statements that match the triangle you're on.
Hmm, I heard its something to do with the shadowmatrix plane but I have tried changing the plane[4] = (0,1,0,0) to 0,2,0,0 etc but it seems that plane[4] takes 1s and 0s only.

[Edited by - jonathanc on April 2, 2008 11:41:58 AM]

This topic is closed to new replies.

Advertisement