Sign in to follow this  
Wavesonics

LookAt() method for camera

Recommended Posts

Wavesonics    330
I'm trying to implement a LookAt() method in my camera. I accumulate Euler angles, but use a Quaternion to generate the finale rotation matrix as to avoid gimbal lock. So for my LookAt() method I figured the following would be sufficient:
  1. Subtract the point at which to look (P) from the camera's position (K) resulting in vector (V)
  2. Take the cross product of V and the vector (Vz(0,0,-1) chosen b/c this is the default for opengl) resulting in vector (V'). V' is now the axis of rotation, to get from the default opengl orientation, to the rotation necessary to look at our point.
  3. Now we need the angle, acos( Vz DOT V ). Now we have the axis of rotation and the angle with which to rotate around it.
  4. Lastly, because my camera accumulates Euler Anglers, I need to turn this Axis Angle rotation back into Euler Angles.
    void lookAt( float x1, float y1, float z1 )
    {
        // Step 1) Setup our vectors
        VectorR3 v1( VectorR3( 0.0f, 0.0f, -1.0f ) );
        VectorR3 v2( m_position );
        v2.x -= x1;
        v2.y -= y1;
        v2.z -= z1;
        v2 = v2.normalized();

        // Step 2) Calculate Rotation Axis
        VectorR3 rotationAxis( v1.cross( v2 ).normalized() );

        // Step 3) Calculate Angle
        float rotationAngle = acos( v1.dot( v2 ) );

        // Step 4) Calculate Euler Angles
        float x = rotationAxis.x;
        float y = rotationAxis.y;
        float z = rotationAxis.z;

        float s = sin( rotationAngle );
        float c = cos( rotationAngle );
        float t = 1.0f-c;

        if ((x*y*t + z*s) > 0.998f) { // north pole singularity detected
            m_yaw = 2.0f*atan2(x*sin(rotationAngle/2.0f),cos(rotationAngle/2.0f));
            m_pitch = PI/2.0f;
            m_roll = 0.0f;
            return;
        }
        if ((x*y*t + z*s) < -0.998f) { // south pole singularity detected
            m_yaw = -2.0f*atan2(x*sin(rotationAngle/2.0f),cos(rotationAngle/2.0f));
            m_pitch = -PI/2.0f;
            m_roll = 0.0f;
            return;
        }

        m_yaw = atan2(y * s- x * z * t , 1.0f - (y*y+ z*z ) * t);
        m_pitch = asin(x * y * t + z * s) ;
        m_roll = atan2(x * s - y * z * t , 1.0f - (x*x + z*z) * t);
    }









But the resulting Euler Angles aren't quite right, especially when there should be more pitch involved. Any idea what I'm doing wrong? [edit] Ah so I clamped the resulting Euler Angles to be within 2PI Radians (rolls back over to 0) and I get better results, but still off in some cases. Another problem is I'm not preserving my up vector, so it rotates how ever it needs to look at the point. How can I incorporate what should be the up vector so my camera doesn't roll while it's looking? Also, when ever it detects a north or south pole singularity, it craps out and produces a completely incorrect rotation :/ Boy... [/edit] [Edited by - Wavesonics on February 17, 2010 7:58:04 PM]

Share this post


Link to post
Share on other sites
Buckeye    10747
Just for clarification, are you trying to orient the angle with respect to the negative z-axis?

If so, I would think you want v2 = lookAtPoint - cameraPos, to get a direction from the camera to the point, not the other way around. camPos - lookAtPoint will point behind the camera, not in front of it.

Share this post


Link to post
Share on other sites
Wavesonics    330
Hhhmmm, yes I do want to orient FROM the -Z axis to the vector I generate, so I see your point, and even agree with you.

But reversing the two seems to give me worse (possibly inverse) results.

Currently, as long as I don't elevate the camera, and i just move around the look at point on it's plane, it works. It's when I go off plane, that it fails.

Share this post


Link to post
Share on other sites
swiftcoder    18437
Quote:
Original post by Wavesonics
I accumulate Euler angles, but use a Quaternion to generate the finale rotation matrix as to avoid gimbal lock.
While it is likely entirely tangential to your current issue, this is doing nothing to help you. it would be worth reading up on gymbal lock, or forgetting the term exists at all.

Gymbal lock is (roughly speaking) a case when you lose one axis of rotation, due to the other two axes being in alignment with each other. But more importantly here, it only affects your ability to rotate further.

If your camera becomes gymbal locked (and it isn't that common), then the fact that you convert it into a matrix via quaternion will not in any way affect the fact that the camera will *still* be gymbal locked.

Share this post


Link to post
Share on other sites
Wavesonics    330
hhmmm... I was thinking this. Though I thought the order in which you applied Euler Angle rotation would cause gimble lock, while creating a Quaternion in one shot with all three Euler Angles would avoid it.

I've tried accumulating Quaternions: I took Euler Angles in, immediately converted them to a Quaternion, then multiplied the Camera's internal Quaternion by the newly created one, thus adding the new rotation to the current.

But extremely quickly, I get bleed over into another axis, and my camera begins to roll, when it should only be pitching & yawing.

Share this post


Link to post
Share on other sites
jyk    2094
Quote:
hhmmm... I was thinking this. Though I thought the order in which you applied Euler Angle rotation would cause gimble lock, while creating a Quaternion in one shot with all three Euler Angles would avoid it.
Like swiftcoder said, just throwing a quaternion or two into the mix won't prevent gimbal lock. In fact, you can think of quaternions as being functionally equivalent to matrices as far as rotations are concerned. (Obviously there are differences, or else no one would bother with quaternions; however, the differences have more to do with efficiency and elegance than they do with fundamental behavior.)
Quote:
I've tried accumulating Quaternions: I took Euler Angles in, immediately converted them to a Quaternion, then multiplied the Camera's internal Quaternion by the newly created one, thus adding the new rotation to the current.

But extremely quickly, I get bleed over into another axis, and my camera begins to roll, when it should only be pitching & yawing.
It sounds like you were implementing 6DOF motion here, which behaves exactly as you describe; that is, accumulated pitch and yaw will (usually) result in perceived roll.

I think the real question here is what kind of motion you want. If you only want pitch and yaw, then 'from scratch' Euler angles should work fine, and gimbal lock should not be a concern.

As for the 'look at' function, a typical implementation will compute the orientation using vector cross products rather than axis-angle rotations. However, this method will still fail when looking more or less straight along the reference axis. This has to be handled as a special case with any method though, simply because if the target is directly above or below the viewer, there's no unique orientation that will satisfy the constraints.

Share this post


Link to post
Share on other sites
Wavesonics    330
Right I am trying to implement a 6DOF camera. But I want to be able to rotate it accurately. If I only want to change the pitch I only to change the pitch. But if I do want to be able to use all 6 dof, then I want them there.

Share this post


Link to post
Share on other sites
jyk    2094
Quote:
Right I am trying to implement a 6DOF camera. But I want to be able to rotate it accurately.
Can you clarify what you mean by 'accurately'? You can rotate an object using incremental, local rotations with complete accuracy, but I get the sense that you mean something else here.

Your mention of 'only changing the pitch' suggests that you want to in some way combine 6DOF motion with absolute, 'from scratch' Euler angles. If so, it's not immediately clear to me how that would be done, as 6DOF motion and 'from scratch' Euler angles are sort of fundamentally at odds with one another.

If you still find yourself stuck on this, maybe you could go into a little more detail about the nature of the simulation and what sort of control scheme(s) you're using. That might make it a little easier to suggest ways you could get the behavior you're looking for.

Share this post


Link to post
Share on other sites
Wavesonics    330
Ah ok. Well the only reason I was using "from scratch Euler Angles" was because they are easiest to think in when saying "When mouse moves left, add yaw of 10 degrees".

Would starting with predefined Quaternions (10 degrees of yaw expressed as a Quaternion) and just using that to apply incremental rotation be the answer?

My current project only calls for pitch and yaw, but I want a general purpose camera I can use for anything down the road.

Share this post


Link to post
Share on other sites
Wavesonics    330
Nope. Clearly I have a bigger miss-understanding then I understand. Which is sort of a cyclical problem...

What did you mean btw about using cross products to do look at functions?

Share this post


Link to post
Share on other sites
BLiTZWiNG    361
I don't know if our little IRC talk helped, but I figured I'll paste it here incase you decide you want to reference it again, or maybe someone else can explain better why I don't suffer gimbal lock, but my code is:


SceneGroup.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix.CreateRotationX(MathHelper.ToRadians(pitch))
* Matrix.CreateRotationZ(MathHelper.ToRadians(yaw)) * Matrix.CreateRotationY(MathHelper.ToRadians(roll)));


The values for yaw, pitch and roll are only the distance to be moved this frame (ie the are derived from how far the mouse moved in my case).

HTH.

Share this post


Link to post
Share on other sites
Wavesonics    330
It certainly helped, and thanks for ur time.

Few questions though: So screen rotation is a qauternion its self? And do u finially extract a rotation matrix from it to pass to the gfx system?

In my latest itteration I was creating axis quaternions for certain amounts of pitch and yaw, and multiplying them to my cameras current quat. Then extracting a matrix for opengl to load. But I got the same results as always. Unindended roll.

Share this post


Link to post
Share on other sites
karwosts    840
Quote:

But I got the same results as always. Unindended roll.


As I believe was said earlier, if you use a 6 DOF camera, you will get this appearance of rolling. This is supposed to happen.

You can do a little experiment to prove it to yourself:

1) Hold your right arm out in front of you with palm facing the floor. The tips of your fingers are camera "out", and the top of your hand is camera "up".

2) Pitch your "camera" up 90 degrees by bending your elbow. Now your fingertips are pointing towards the ceiling, and your palm is facing in front of you.

3) Now yaw your arm 90 degrees to the left, so that your fingertips are pointing to the left, and your palm is still facing front.

4) Now pitch your camera back down 90 degrees. Your camera "out" (fingertips) are pointing the same way as when you started, but your "up" has rolled 90 degrees.

This should help you understand why your camera is rolling when you're doing "only pitch and yaw"

Share this post


Link to post
Share on other sites
BLiTZWiNG    361
Quote:
Original post by karwosts
Quote:

But I got the same results as always. Unindended roll.


As I believe was said earlier, if you use a 6 DOF camera, you will get this appearance of rolling. This is supposed to happen.

This should help you understand why your camera is rolling when you're doing "only pitch and yaw"


I never get the 6DOF roll problem with the system I'm using.

I'm only using a Quaternion because that's what TorqueX uses. Matrix or Quaternion matters not, but I'm sure it turns into a matrix when it goes into a shader somewhere.

My mouse inputs only affect pitch and yaw, and I have keys for roll. I don't use any axis quats or whatever they might be. Pitch, yaw and roll are just floats 0 <= X < 360. Multiply the change in angle since the last frame with your current rotation and it should work fine. Roll will be 0 most of the time.

Maybe you're trying to do something extra that is warping the results?

Share this post


Link to post
Share on other sites
jyk    2094
Quote:
Original post by BLiTZWiNG
I never get the 6DOF roll problem with the system I'm using.

I'm only using a Quaternion because that's what TorqueX uses. Matrix or Quaternion matters not, but I'm sure it turns into a matrix when it goes into a shader somewhere.

My mouse inputs only affect pitch and yaw, and I have keys for roll. I don't use any axis quats or whatever they might be. Pitch, yaw and roll are just floats 0 <= X < 360. Multiply the change in angle since the last frame with your current rotation and it should work fine. Roll will be 0 most of the time.

Maybe you're trying to do something extra that is warping the results?
I think you and Wavesonics may be talking about different things here. What karwosts posted is correct - with 6DOF rotation, successive (local) changes to pitch and yaw can (and usually will) result in perceived roll. Its not really a 'problem' per se - it's just a natural consequence of applying local rotations in sequence.

I don't know how your system works, so I can only speculate as to why you're not seeing this behavior (or perhaps you are seeing it, but it's acceptable and/or expected in the given context).

Share this post


Link to post
Share on other sites
szecs    2990
Do you know CAD based design programs? (SolidEdge, ProEngineer SolidWorks etc.)
Do you want that kind of rotation, or do you want to rotate like in a first person shooter game, or in 3ds Max (where you will have a gymbal lock if you look straight up or down)?

Or do you want to build the matrix from a lookat + up vector?

I'm not sure that you use these terms correctly, so we are not sure what you want. If I didn't notice something, then sorry.

Share this post


Link to post
Share on other sites
Wavesonics    330
Aahhhh!!!! I see said the blind man to his deaf wife!

karwosts you finally cleared it up in my head, makes perfect sense now.

Lets see if I truly understand this. This should work:


class Camera
{
Qaut m_yaw;
Quat m_pitch;
Quat m_roll;

Vector m_pos;

void rotate( float yaw, float pitch, float roll )
{
Quat qYaw( yaw, 0, 0 ); // Construct Quat from Euler
Quat qPitch( 0, pitch, 0 ); // Construct Quat from Euler
Quat qRoll( 0, 0, roll ); // Construct Quat from Euler

m_yaw *= qYaw;
m_pitch *= qPitch;
m_roll *= qRoll;
}

// The return value from this method is passed into glLoadMatrixf()
Matrix getMatrix()
{
Qaut rot = m_yaw * m_pitch * m_roll;
Matrix mRot = rot.getMatrix();

Matrix mPos = m_pos.getMatrix();

return mRot * mPos;
}
}






Now... Assuming that is right (unless someone can point out where I am wrong) I do want more specific lookAt() functionality, where, except for the polar cases, the up vector will remain the same. Any suggestions for that?

[Edited by - Wavesonics on February 18, 2010 9:44:23 AM]

Share this post


Link to post
Share on other sites
Wavesonics    330
It works! Just tested it :D

Now though, that I don't have discrete Euler angles stored in my camera, i need to find a different way of calculating my translation vector:



void move( float distance ) {
m_position.x -= std::cos( -m_yaw + Rotation3D::deg2rad( -90.0f ) ) * distance;
m_position.z -= std::sin( m_yaw + Rotation3D::deg2rad( -90.0f ) ) * distance;
m_position.y -= std::sin( m_pitch ) * distance;
}




:(

Share this post


Link to post
Share on other sites
jyk    2094
Quote:
Original post by Wavesonics
It works! Just tested it :D

Now though, that I don't have discrete Euler angles stored in my camera, i need to find a different way of calculating my translation vector:

*** Source Snippet Removed ***

:(
The quaternions in your earlier example are an unnecessary intermediate step. In fact, they serve no purpose at all, so I'd recommend dropping them (at which point you'll again have 'plain' Euler angles available, if you want to use them for updating the position).

Can't guarantee I'll get all the details right, but here's what the revised code might look like:

class Camera
{
float m_yaw;
float m_pitch;
float m_roll;

Vector m_pos;

void rotate( float yaw, float pitch, float roll )
{
m_yaw = wrap(m_yaw + yaw, 0.f, two_pi());
m_pitch = wrap(m_pitch + pitch, 0.f, two_pi());
m_roll = wrap(m_roll + roll, 0.f, two_pi());
}

Matrix getMatrix()
{
Matrix mRot = MatrixFromEuler(m_yaw, m_pitch, m_roll);
Matrix mPos = m_pos.getMatrix();
return mRot * mPos;
}
}

Share this post


Link to post
Share on other sites
Wavesonics    330
The multiplication at the end:
Quote:

Quat rotation = qYaw * qPitch * qRoll;


If those component Quats were instead Matrices, couldn't that same multiplication result in gimbal lock? Or does keeping the components separate like this, and always remaking the final rotation from scratch prevent it?

(btw i do very much appreciate ur help, but I could use some more explanation of things, such as why they are unnecessary)

Just to humor me though, do you know of a way to do the translation equations using a Quaternion?

I would like to keep the internal rotation as a quaternion so that I can do some interpolation later on with it. Like for my lookAt() method, I can smoothly rotate the camera over time to face the point.

Share this post


Link to post
Share on other sites
swiftcoder    18437
Quote:
Original post by Wavesonics
The multiplication at the end:
Quote:

Quat rotation = qYaw * qPitch * qRoll;


If those component Quats were instead Matrices, couldn't that same multiplication result in gimbal lock? Or does keeping the components separate like this, and always remaking the final rotation from scratch prevent it?
Once again: the matrix you calculate can *never* be gimbal-locked *because you never rotate it*. Gimbal lock only applies to further rotations, and the matrix is not rotated further after construction.

Share this post


Link to post
Share on other sites
Wavesonics    330
Got ya, so what i was saying about keeping the components separate, and re-generating the full rotation every time is what prevents it from happening. Because you are not continuing to apply rotations to the same thing.

Now I still want to keep the internal rotation as a Quaternion to be able to do interpolation on it as I mentioned before. But that it's self is at odds with the fact that I keep all the components separate.

I would need one single Quat that stores the full rotation, and then I could interpolate and set that internal Quat to the intermediate interpolation steps.

But I can't separate out the individual Euler components of the intermediate rotation... so my current method of angle storage doesn't work well with interpolation...

Damn.

Share this post


Link to post
Share on other sites
Wavesonics    330
I wounder... if instead of having component Quats, if I go back to accumulating a single Quat, maybe there is a way to avoid, or even correct for the un-wanted rotation?

In the current use case, I DO want yaw/pitch, FPS style rotation, with no perceived roll. So maybe it's a matter of how I am specifying the incremental rotations?

As I begin pitching and yawing, and everything is fine, the problem is when I want to then pitch or yaw in the opposite direction. Instead of adding yet more rotation (which happens to be in the opposite direction) I want to sort-of... undo the previous rotation, not add more rotation on top of it.

Maybe it's not possible, but does it make any sense?

[BTW, thank you all for helping me, I really am starting to understand the issues at hand much better]

Share this post


Link to post
Share on other sites
szecs    2990
You should decide what you want to do.
FPS style and the 6dof style are two completely different transformations.
Make two totally different code for them. I don't think you can mix them (in a sane way).

You can't make FPS camera with incremental transformations (or maybe you can, but you'd doom yourself and the whole world), and you can't make that kind of 6dof what you have without incremental transformations (or maybe you can, but you'd doom yourself and the whole world)

EDIT: IMHO (maybe because I code in C, and I don't know much about OOP) it's meaningless to do one monster generalized function to calculate every possible rotation forms. Have neat, short, clear, separate functions for the types of rotations.

Share this post


Link to post
Share on other sites
Wavesonics    330
Right, it doesn't make sense, because it's the fact that you are applying more yaw after there has been pitch applied...

But how about this, maybe there could be a roll correcting Quat applied to zero out the perceived roll. In-fact, this could be use to keep the camera facing up during the look-at function as well.

It would be a special case for when the camera was being used as in an FPS style, but I could generalize it to be able to correct for, and zero out any of the axis, making it quite flexible.

Is this completely insane? (it smells a little insane to me...)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this