Opengl - Camera rotation on terrain problem

Page 1 of 1

10 Replies - 3645 Views - Last Post: 21 July 2012 - 09:35 AMRate Topic: //<![CDATA[ rating = new ipb.rating( 'topic_rate_', { url: 'http://www.dreamincode.net/forums/index.php?app=forums&module=ajax&section=topics&do=rateTopic&t=286186&amp;s=f92b1870bc08b6ec1bae714843d5d566&md5check=' + ipb.vars['secure_hash'], cur_rating: 0, rated: 0, allow_rate: 0, multi_rate: 1, show_rate_text: true } ); //]]>

#1 Java Student

• D.I.C Regular

Reputation: 21
• Posts: 496
• Joined: 05-February 10

Opengl - Camera rotation on terrain problem

Posted 18 July 2012 - 07:07 AM

I need to find how to adjust the camera's X rotation to immitate a person across slopes on a terrain, if that makes any sense. The cameras X rotation is set to default <0,0,0> (looking straight ahead) until i figure how to solve the problem.

I have a few a few images to help you understand my problem.

My problem: I need my camera to look upwards/downwards as it walks across hills and valleys on the terrain.

Given information:
- Terrains normal
- Cameras x,z position relative to the terrain
- Cameras height

Unknown information:
- Cameras X rotation

Is This A Good Question/Topic? 1

Replies To: Opengl - Camera rotation on terrain problem

#2 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 18 July 2012 - 08:27 AM

I'm sure there are people on this forum more experienced than me who can probably give you a better answer, but I'll take a crack at it until they post.

I see it as a classic "vector cross product" problem.

The X axis is easy. Just rotate the ground normal by 90 degrees.

But that's solving the problem in 2D, whereas this is really a 3D problem. So that's going to work great... until you turn and rotate around Y.

I "think" what you want is the vector cross product between your camera's right side and the ground's normal. By the camera's right side, I mean that I'm thinking of the camera's facing on a 2D X,Z plane where the camera's facing is normalized forward vector and the camera's "right side" facing would be that forward vector rotated 90 degrees on the X,Z plane (a Y axis rotation). That would give you a normal for the calculation. That normal cross producted with the ground normal should "I think" give you a normal that is oriented in 3D parallel to the ground in the direction that the camera is facing.

I would actually have to test it to make sure that it works. But I "think" that's the solution.

http://en.wikipedia....i/Cross_product

I might add that your cross producting a 2D vector (the right side vector) with a 3D vector. The right side vector is 2D because it exists on the X,Z plane. But for the calculation you put it into 3D space (make it a 3D vector) but just force it's Y value to always be zero (forcing it to be on the X,Z plane).

This post has been edited by BBeck: 18 July 2012 - 08:47 AM

#3 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 18 July 2012 - 09:34 AM

Oh. Also. Your camera facing should be a normalized vector, either 2D or 3D. It should be (0,0,1) in 3D or (0,1) in 2D when facing down the Z axis (Y axis in 2D). Rotating that 3D vector by 90 degrees around Y should give you a normalized right facing vector. (In 2D you rotate the 2D vector by 90 degrees (In 2D Y is Z, not Y, but that's just confusing.)

You wrote camera rotation as (0,0,0) when it should probably be (0,1) or (0,0,1) in your drawing.

In this case, your camera is going to have Y forced to zero until you determine the correct camera orientation. So, it's 2D even if it's a 3D vector because it's confined to a 2D plane.

Anyway, I just wanted to make sure you understood that already because otherwise it might make the whole concept even more difficult to understand.

Of course, it could be a normalized 2D vector facing in any direction, but facing down the Z axis it would be (0,0,1) until it's rotated to another 2D or 3D facing.

This post has been edited by BBeck: 18 July 2012 - 09:36 AM

#4 anonymous26

• D.I.C Lover

Reputation: 1
• Posts: 3,638
• Joined: 26-November 10

Re: Opengl - Camera rotation on terrain problem

Posted 18 July 2012 - 06:35 PM

There is a problem with just using the cross product in that you are always inclined according to the orientation of the polygon that you are currently referencing, as well as your camera being locked in one position at any given moment in time. The vector product is not a good solution here.

I would be inclined to look into Euler Angle computations to solve your problem. Although more complicated you will have three degrees of freedom as you move around your terrain. You will also be able to interpolate otherwise jerky camera motions as you travel around.

A little tougher but worth the effort.

#5 Java Student

• D.I.C Regular

Reputation: 21
• Posts: 496
• Joined: 05-February 10

Re: Opengl - Camera rotation on terrain problem

Posted 20 July 2012 - 01:07 AM

First of all, i appreicate both of your responses. I have spent a couple nights trying to figure the problem.

I know that the tilt angle(cameras x-rotation) can be given by ->
tilt = acos( clamp(-1.0, lookDir.dotProduct( (1,0,0) ) , 1.0) )

* Where the 'lookDir' is resulted from "terrainNormal.cross(<1,0,0>)"

Since clamping is really a safety procaution, i will implement it later.

Let:

a = Terrain normal;

b = Standard unit vector <1,0,0>;

c = a.cross( b ); //look direction vector

d = c.dot( b );

e = acos(d)*(180/3.14); //radians to angle for Opengl purposes

My problem is, my x-component in variable 'c'(look direction vector) ALWAYS remains 0. So when i attempt to calculate variable 'd' by dot'ing it with my <1,0,0> axis vector, i ALWAYS result in variable 'd' being zero. Why? because doing the dot product formula of <0,?,?> with <1,0,0> will always result in zero. Subsequently finding variable 'e' by acos(d), where 'd' is always zero, results in 90 degrees forever as a tilt rotation.

#6 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 20 July 2012 - 06:51 AM

SA Games, on 20 July 2012 - 01:07 AM, said:

My problem is, my x-component in variable 'c'(look direction vector) ALWAYS remains 0. So when i attempt to calculate variable 'd' by dot'ing it with my <1,0,0> axis vector, i ALWAYS result in variable 'd' being zero. Why? because doing the dot product formula of <0,?,?> with <1,0,0> will always result in zero. Subsequently finding variable 'e' by acos(d), where 'd' is always zero, results in 90 degrees forever as a tilt rotation.

First, let me make sure we're on the same page here with orientation. The three components are X,Y,Z for your coordinates. I'm defining X as left-right, Y as up-down, and Z as forward backwards before anything is rotated, etc. If that's wrong, let me know. And Z is positive as you move forward.

It makes sense that your X from your C vector would always be zero. That's because you're cross producting it with a right facing vector <1,0,0>. The cross product takes the plane that two vectors are on and produces a result vector that points directly out from that plane in a perpendicular manner. (This is how you calculate a terrain normal by taking two of the sides of the triangle as vectors and taking their cross product as the normal.) So, I'm thinking no matter what direction the other vector is facing, the fact that the right facing vector is fixed to face down the X axis insures that the resulting answer can never face in that direction.

But I'm getting a little confused as to what you're trying to solve for. Correct me if this is wrong but I think that you are starting with a 2D normalized vector that represents the direction that the camera is facing (or a 3D vector where the Y component is always zero which is essentially a 2D vector - I assume user input controls the facing of this vector). You are trying to turn that into a 3D vector that is parallel to the surface of the terrain. Then you want to take that 3D vector and convert it to angles of pitch, yaw, and roll, correct?

So, assuming that that's correct let me try and write some pseudo-code.

```Vector2 CameraDirection;  //2D normalized vector on the X,Z plane
Vector3 CameraFacing; //3D direction camera faces
Vector3 CameraRight; //90 Degree rotation from CameraDirection
Vector3 TerrainNormal; //Points directly out of the surface
Vector3 Up = (0,1,0);

CameraFacing.Y = 0;

//The order you do a cross product will determine
//the direction of the result vector. So reverse the
//order if it points in the opposite direction.
CameraRight = CameraFacing.Cross(Up);
//Or is it:
//CameraRight = Up.Cross(CameraFacing); ???

//Again, order is important here and I'm not sure
//off the top of my head which one is crossed
//with the other in what order.
CameraFacing = TerrainNormal.Cross(CameraRight);
//Or is it:
//CameraFacing = CameraRight.Cross(TerrainNormal);

```

Anyway, I "think" that will work but can't say for certain with out actually writing some code and testing it. That's basically just two cross products to give you a forward facing vector from a 2D vector that's parallel to the terrain at that point. And you already have the formulas to turn the 3D facing vector into Pitch, Yaw, and Roll (Tilt, etc.). So maybe that will do the trick for you.

The next consideration is going to be that the camera is going to violently turn from surface to surface. Everytime you move from one terrain polygon to the next it will act like the edges of the polygons are hard sharp edges... because they are.

So, the way around that is to interpolate between the terrain normals so that the camera angle gradually and gently goes from one to the next.

You may already have vertex normals stored at every vertex. And those vertex normals may be averages of all the face normals of the terrain triangles that touch that vertex. Maybe you can use those normals rather than the face normals. That way, when you're in a grid quad (square) you can average the normals of the corners of the quad (grid square) according to the percentage of distance you are between each corner. (It's more accurate to do it per triangle than per quad, but doing it per quad is a little easier and so far I've found it works "well enough".)

There are probably some interpolation methods that you can call to do the interpolation based on a percentage between two numbers (LERP).

That should smooth things out for you quite a bit as you go over the terrain. Likewise, camera height should probably be interpolated between the vertices of the quad (or triangle) you are currently in on the grid or your altitude will jump around irradically as you pass over those hard sharp triangle edges.

This post has been edited by BBeck: 20 July 2012 - 10:43 AM

#7 Java Student

• D.I.C Regular

Reputation: 21
• Posts: 496
• Joined: 05-February 10

Re: Opengl - Camera rotation on terrain problem

Posted 20 July 2012 - 10:56 AM

BBeck, on 20 July 2012 - 06:51 AM, said:

First, let me make sure we're on the same page here with orientation. The three components are X,Y,Z for your coordinates. I'm defining X as left-right, Y as up-down, and Z as forward backwards before anything is rotated, etc. If that's wrong, let me know. And Z is positive as you move forward.

Correct, except Z is negative going foward.

BBeck, on 20 July 2012 - 06:51 AM, said:

It makes sense that your X from your C vector would always be zero. That's because you're cross producting it with a right facing vector <1,0,0>. The cross product takes the plane that two vectors are on and produces a result vector that points directly out from that plane in a perpendicular manner. (This is how you calculate a terrain normal by taking two of the sides of the triangle as vectors and taking their cross product as the normal.) So, I'm thinking no matter what direction the other vector is facing, the fact that the right facing vector is fixed to face down the X axis insures that the resulting answer can never face in that direction.

It does make sense to me that i keep getting zero in my X-component for the lookDir, but then does mean the tilt formula is wrong? Or is my lookDir(terrainNormal.cross(<1,0,0>)) somehow not accurate?

But the only way for my lookDir to be inaccurate is for the terrainNormal to be incorrect and this extremely unlikely as the functions for creating my terrain are taken completely lighthouse3d.com

BBeck, on 20 July 2012 - 06:51 AM, said:

But I'm getting a little confused as to what you're trying to solve for. Correct me if this is wrong but I think that you are starting with a 2D normalized vector that represents the direction that the camera is facing (or a 3D vector where the Y component is always zero which is essentially a 2D vector - I assume user input controls the facing of this vector). You are trying to turn that into a 3D vector that is parallel to the surface of the terrain. Then you want to take that 3D vector and convert it to angles of pitch, yaw, and roll, correct?

I'm trying to solve for the tilt angle(cameras X-rotation as it stands on an inclined/declined plane). I always use 3D vectors and never 2D, theres no transforming 2D to 3D vectors. Yes, that is correct. Basically, i need the terrains direction(inclined/declined) to match the cameras direction in terms of tilt(x-axis). In theory, i should be able to come up with tilt angle for my camera from 2 peices of information, (1) The terrains normal, and (2) The X-axis standard unit vector <1,0,0>.

A couple side notes,
The camera starts at an initial direction of <0,0,-1> (forward).
The X-axis goes left(-) and right(+)
The Y-axis goes up(+) and down(-)
The Z-axis goes forward(-) and backward(+)

#8 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 20 July 2012 - 02:54 PM

Ok. So, I "think" I've managed to get this one figured out.

I believe Kyall is saying if and only if "you're camera is heading forward in Z [perfectly aligned with the axis and never looking to the side]" you could do a cross product with the terrain normal and (1,0,0) [the right-side normal if you're facing straight down Z, but only if you're facing straight down Z] and the result would be lookDir.

In that case, I believe he's saying you can calculate the tilt with
```tilt = acos( clamp(-1.0, lookDir.dotProduct( (1,0,0) ) , 1.0) )

```

But I think he's got one of the vectors wrong here because the lookDir in this case is always going to be pointed straight down the Z axis meaning that it will never point in either the negative or positive direction at all because it rotates around that axis. You would have to use a different rotational axis/vector to get x to have a value in the result.

Then, for the tilt value he's got the lookDir X (1,0,0). That should project the lookDir (axis aligned with Z) perfectly onto the X axis and result in a zero (non-existant) vector. I haven't worked with the dot product of two 3D vectors, only the 2D dot product. So, I'm not 100% certain what the 3D dot product does. But I assume it still projects the 3D vector on to the other 3D vector.

So, I'm not 100% certain at what Kyall is going for there.

But to back up and just find the answer we can solve it as a trig problem. First, you can't calculate roll from a direction vector because it is just a direction that you're looking towards and doesn't contain any roll information.

So, let's start with Yaw. I don't have a way to draw you a picture here, but let's imagine a 3D normalized LookAt vector that holds the direction the camera "lens" is facing. This vector can be obtained using the method that I specified in my cross product code above although you probably want to make sure the end result is normalized.

Imagine our 3D vector pointing somewhere around (X=2,Y=3,Z=1.5). Now that's obviously an unnormalized vector and you would need to normalize it before doing these calculations, but it gives you an idea of the vector I'm imagining here so that I can paint a picture in your mind. The only important information in this first vector is what direction it's pointing in (from it's tail at 0,0,0 to it's head at (2,3,1.5)).

Now imagine a light directly above in the positive Y region shining down and causing our vector to cast a shadow line on the X,Z grid. The shadow of the unnormalized vector should basically be the vector (2,0,1.5). The exact coordinates aren't what's important but the important part is you imagine this shadow line on the X,Z plane. This shadow line is going to be the hypotenuse of our first triangle. It forms a right triangle with the X axis where the X axis side (adjacent side) is 2 in length before normalizing. If you draw an imaginary line between the ends of the two lines you complete the sides of the triangle and that side is the side opposite with a length of 1.5 before it's normalized.

Anyway, (SOHCAHTOA) the Tangent of the angle is the ratio of the side opposite divided by the side adjacent. That means that the arctan of the side opposite (1.5) divided by the side adjacent (2) is the amount of Yaw off the Z axis.

There are a couple problems with ArcTan, as I recall. I think it's going to be undefined when you yaw straight down the X axis and it seems like there was a problem with arctan in different quadrants. So, you may have to deal with that. I remember that with what I was working with there was a seperate ArcTan2 function that took care of the problems.

So, there's your yaw from a 3D vector. Yaw = Arctan(z/x); //Where Z and x are components of the 3D LookAt vector.

To get the pitch you need the angle between the "shadow" on the X,Z plane and the 3D LookAt vector. The 3D LookAt vector is going to form the hypotenuse of the triangle and the shadow is going to be the side adjacent.

(SOHCAHTOA) says that the Cosine of the angle is equal to the ratio of the side adjacent divided by the hypotenuse. Finding the length of the Hypotenuse is easy. It's just LookAt.Length() aka the magnitude/length of the LookAt vector. The length of the side adjacent is a little more tricky because it's not axis aligned and there's no supper easy way to get it unless maybe we use a vector trick like a dot project (maybe?). Anyway, you can get it by realizing it's the hypotenuse of our first triangle. So it's length is taken from the Pythagorean theorm.

c^2 = a^2+b^2

or c = SqrRt(a^2+b^2)

or in our case:

SideAdjacent = SqrRt(x^2 + z^2)

where x and z are components of the LookAt vector

So to get the angle of pitch it would be:

Pitch = Arcsin(SqrRt(x^2 + z^2)/LookAt.Length())

But if we had of properly normalized our LookAt vector it would have a length of one and you can remove it from the equation because it's a divide by one.

Pitch = Arcsin(SqrRt(x^2 + z^2))

Yaw = Arctan(z/x); //Where Z and x are components of the 3D LookAt vector.

And there you have it. (The answers, of course, are in radians - not degrees).

You still need a LookAt vector though to make that work, and I don't see - off the top of my head - how you're going to get that without the cross product pseudo-code I did above.

But I think that solves your problem. And it even seems more straight forward to me than Kyall's code, but I'm not 100% certain I fully understood what he meant with that code.

This post has been edited by BBeck: 20 July 2012 - 04:51 PM

#9 Java Student

• D.I.C Regular

Reputation: 21
• Posts: 496
• Joined: 05-February 10

Re: Opengl - Camera rotation on terrain problem

Posted 21 July 2012 - 04:17 AM

It works!

I really appreciate the long answer, and now i can actually see why what i tried wasnt working.

The only minor thing which had to be changed was converting radians to degrees for OpenGL's camera function glRotatef.

Approaching downwards slopes rotates the cameras tilt downwards, thus creating a positive camera x-rotation angle (since, in openGL, postive angles mean tilt downwards, not upwards).

The only issue, to be expected, is with the terrain slopes, the program doesn't know if you entered the slope from the top or bottom so it doesnt know whether to send the camera a positive rotation(descend) or negative rotation(climb).

One possible solution is to get the cameras y-rotation angle coming into the slope, so it knows which side im on(top or bottom) and can give me the appropriate positive/negative angle.

Basically, you've helped me find the angle and now it doesnt know if i want a positive or negative angle. Do you have an idea on how to solve this lesser problem?

#10 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 21 July 2012 - 09:13 AM

I wouldn't think that it should matter which side of the polygon (slope) you approach from. The camera direction vector should be dependent on facing, not the direction you entered the polygon from. And the pitch and yaw formulas "should" be independent of that as well.

There's probably an error in the math, more than likely on my part. Either that or we have something "quirky" in the math going on such as the fact that we've got a square root that "really" produces two answers, positive and negative. Or it could be one of the quirks of the trig functions such as what quadrant it's in and whether the angle exceeds 90 degrees.

Anyway, I had some additional thoughts. I'm reading this book on Linear Algebra right now and it brought up a few points.

First, I "think" there may be an easier way to get the perpendicular Left Facing Vector. We used Right Facing, but this method gives a left facing vector. In the book, it's for 2D vectors but we're starting out with a 2D vector anyway. So it "should" work.

Here's the trick:

To get a Left Facing perpendicular vector from a starting 2D vector (camera facing on the X,Z plane) reverse the coordinates and flip the sign [+/-].

So:

```Vector3 CameraFacing;
Vector3 LeftFacing;

CameraFacing.Y = 0; //You don't have to set it but it's assumed.
LeftFacing.X = -CameraFacing.Z;
LeftFacing.Z = CameraFacing.X;
LeftFacing.Y = 0; //Also assumed.

```

Anyway, I haven't really tested that out other than some quick tests in my head, but the Linear Algebra book says it will work and I think it will. Seems more simple and faster to calculate it this way rather than doing a cross product.

The next thing is calculating Pitch and Yaw. There's a formula in the book for calculating the angle between two vectors. It's written for 2D vectors, but I suspect it will work with 3D vectors as long as you understand that you're getting the angle between them on their own private plane. That is, the two vectors define a plane and the angle will be on that plane and not axis aligned.

Again, this is for 2D vectors in the book and I'm totally "assuming" that it will work for 3D vectors without working it through on paper or coding it and testing it. I just thought I would share it because I suspect it will work for 3D vectors or you might find it useful in 2D.

So, here's the formula as a method/function (in pseudo-code):

```float AngleBetweenVectors(Vector3 VectorA, Vector3 VectorB)
{
float Angle; //In radians not degrees.

Angle =
ArcCos((VectorA.Dot(VectorB))/(VectorA.Length()*VectorB.Length());

return Angle;
}

```

And, of course, the "Dot" method there is the vector dot product between the vectors. Again, a very nice neat formula for getting an angle.

If this works, and let me know if it does because I suspect it does but haven't tried it, I think it can be used to get pitch and yaw.

So for yaw, it should almost certainly work if you make all of the vectors 2D. The yaw is not dependent on the pitch of the facing vector and can be represented as the 2D camera facing vector on the X,Z plane. But I'll treat it as a confined 3D vector here.

So, "I believe" you can get the yaw by comparing the angle of the facing vector to the "due north" vector on the 2D X,Z plane. "North" can be defined as a vector pointing straight down the Z axis.

So:

```float Yaw; //In radians.
Vector3 CameraFacing;
Vector3 North;  //Constant.

North = (0,0,1);
CameraFacing.Y = 0; //Must be confined to the X,Z plane.
Yaw = AngleBetweenVectors(CameraFacing, North);

```

For pitch we need this to work in 3D and I'm not 100% certain it will. But I'll assume it does until someone tests it.

```Vector3 CameraFacing; //Confined to X,Z plane.
Vector3 CameraLookTowards; //Not confined. Aligned with terrain.
float Pitch;  //In radians.

CameraFacing.X = CameraLookTowards.X;
CameraFacing.Y = 0;
CameraFacing.Z = CameraLookTowards.Z;

Pitch = AngleBetweenVectors(CameraLookTowards, CameraFacing);

```

So all of these formulas are pretty short and sweat. Which I like. I almost want to try them out myself but I've got another project I need to get to work on today. Maybe I'll try them later or you can try them and let me know if they work.

This "may" give you a work around that entirely lets you bypass the problems with the other Pitch and Yaw methods. And maybe it will solve your problems. Let me know how it works out.

This post has been edited by BBeck: 21 July 2012 - 09:24 AM

#11 BBeck

• Here to help.

Reputation: 742
• Posts: 1,740
• Joined: 24-April 12

Re: Opengl - Camera rotation on terrain problem

Posted 21 July 2012 - 09:35 AM

Here's a re-write of how to get the 3D terrain aligned facing vector using the new method to calculate the perpendicular. If you use it, let me know if it worked. I "believe" it will.

```Vector2 CameraDirection;  //2D normalized vector on the X,Z plane
Vector3 CameraFacing; //3D direction camera faces
Vector3 CameraLeft; //-90 Degree rotation from CameraDirection
Vector3 TerrainNormal; //Points directly out of the surface
Vector3 Up = (0,1,0);

CameraFacing.Y = 0;

CameraLeft.X = -CameraFacing.Z;
CameraLeft.Y = 0;
CameraLeft.Z = CameraFacing.X;

//Again, order is important here and I'm not sure
//off the top of my head which one is crossed
//with the other in what order. It will be opposite
//of whatever it was in the previous example due
//to swaping right for left.
CameraFacing = TerrainNormal.Cross(CameraLeft);
//Or is it:
//CameraFacing = CameraLeft.Cross(TerrainNormal);

```