How to handle the 'camera' and terrain collision

Hi there,

I have an application that renders some terrain from a height map and I have a ‘camera’ class which allows rotation (using spherical coordinates) around a point and movement in the XZ plane. There are, of course, times when the camera can be positioned below my defined terrain. The way I’ve come up with to handle this situation is:


		Vec3glf increment = position / 100;

		for(int i=100; i>0; i--){
			Vec3glf putativePosition = increment * i;
			if(putativePosition.y()-0.1 > landscape->height(round(putativePosition.x(), round(putativePosition.z()))){
				gluLookAt(putativePosition.x(), putativePosition.y(), putativePosition.z(), 
						focus.x(), focus.y(), focus.z(),
						up.x(), up.y(), up.z());
				break;
			}
		}

where:
Vec3glf is a class that handles my vector math.
position is a Vec3glf representing the camera’s position.
100 is an arbitrary division of my position vector.
-0.1 is my near clipping plane (is there a way of getting this information out of the GL - I don’t really want to have to pass this to my camera class if I’m not going to need it anywhere else).
landscape->height gives the terrain’s height at an x,z coordinate.
focus is the point at which the camera is looking
up is the up-vector.

So, the code just starts at the actual camera position and checks if it’s under the terrain and keeps moving in by 100th of itself towards the focus if it isn’t until it finds a position above the terrain that’s suitable to place the camera upon.

My question is whether there is a better way of doing this? Where better is more efficient and more precise. I’ll be wanting to do the same thing when objects occlude my focus and I can imagine that this is going to become quite costly when it’s not just terrain I’m concerned with. Also, the division is very arbitrary and guarantee the smoothest animation when moving over a surface of terrain.

Thanks, Dan.

I read the height map values in a diamond around the current camera position. I then linearly intoperate them to average out the height. Any adjustments the camera needs to make I spread out over several frames to keep it smooth.
The near clip is the parameter u supplied to gluproject so you should already have that value somewhere.

Hi BionicBytes, thanks a lot for your reply.

Good point, I plan to implement this too.

I do, but it’s in a different class and I’d rather not have to hand it to the Camera class if I don’t have to, for example, if there was a glGet that I haven’t discovered to retrieve it.

And you do this iteratively for set intervals away from the ‘focus’?

I thought about this a bit further and have ended up with a solution that functions almost perfectly and is reasonably efficient.

Instead of using the height at the closest known point the height is estimated using bi-linear interpolation (as suggested by BionicBytes).

Instead of testing points outward from the ‘focus’ and using the point before the first point that’s below the terrain I recursively move back and forward along the possible path with increasingly smaller increments. So, I break down the possible path into 1/10th increments and progress from the focus outwards until I find a point lower than the terrain. I then move back inwards at increments of 1/10th of the previous increment until I hit a point that’s above the terrain. I keep going back and forwards until I reach a point where I’ve gone outwards 10 times - I know that this point is as close to the terrain as I can be so I return.

Here’s some code:


Vec3glf OGLCamera::positionToViewFocus(Vec3glf increment, Vec3glf &putativePosition, bool lt){
	for(int i=1; i<=10; i++){
		putativePosition += increment;
		float estimatedHeight = landscape->estimateHeight(
				putativePosition.x()+((landscape->width()-1)/2), 
				putativePosition.z()+((landscape->depth()-1)/2));
		float putativeY = putativePosition.y()-0.1;

		if(lt && putativeY < estimatedHeight) break;
		else if(!lt && putativeY > estimatedHeight) break;

		if(lt && i == 10) return putativePosition;
	}

	increment /= -10;
	if(increment.length() == 0.0) return putativePosition;
	else return positionToViewFocus(increment, putativePosition, !lt);
}

This function is initially called via:


Vec3glf putativePosition = focus;
positionToViewFocus(view / -10, putativePosition);

The function to estimate the height looks like this:


		float estimateHeight(float x, float z){ 
			float ceilx = floor(x)+1;
			float ceilz = floor(z)+1;
			float floorx = floor(x);
			float floorz = floor(z);

			Vec3f bl(floorx, 0.0, floorz);
			Vec3f tl(floorx, 0.0, ceilz);
			Vec3f br(ceilx,  0.0, floorz);
			Vec3f tr(ceilx,  0.0, ceilz);

			Matrix<float, 2, 1> xes(ceilx-x, x-floorx);
			Matrix<float, 1, 2> zes(ceilz-z, z-floorz);
			Matrix<float, 2, 2> heights(height(bl), height(tl), height(br), height(tr));

			return (xes * heights * zes)(0,0);
		}

If anyone spots a mistake or sees further improvement (including doing things radically differently) please let me know.

Thanks,
Dan.