PDA

View Full Version : Quaternion based camera



vgo
19-02-2007, 06:51 PM
While working on my PGD competition entry I ran into a silly problem...

My nice quaternion based camera class works great in the first person shooter mode of my game, but if I remove the clamping and try to use it as a 6DOF camera it doesn't work properly. It rotates alright, but it always rotates about the world axis, not itself.

Ie. if I pitch (x-axis) the camera +90 degrees and then yaw (y-axis) +90 degrees it rotates about the world axis and it looks like the camera is actually rolling... How can I do proper cumulative rotation?

This is how I do it now:


q := Q_FromEuler(Roll, Pitch, Yaw);
Orientation := Q_Mult(Orientation, q);


I tried to look for answer and found some nice formulas etc. but I can't understand them. Oh yes, I suck at math. :P

grudzio
19-02-2007, 10:12 PM
You cant rotate vector by simple quaternion multiplication. To get proper results you have to use the following formula:

v' = qvq<sup>-1</sup>

where q is quaternion containig rotation, v is a vector to be rotated represented as a quaternion with zero real (scalar) part, and q<sup>-1</sup> is the inverse of q. If q is normalised then its inverse is equal to conjugate of q.

This is my function for rotating vectors using quaternions.


function quatRotateVector&#40;v &#58; TVector; q &#58; TQuatermion&#41; &#58; TVector;
var
q_1,qv,qt &#58; TQuatermion;
begin
q &#58;= quatNormalize&#40;q&#41;;
q_1 &#58;= quatConjugate&#40;q&#41;;
qv &#58;= Quatermion&#40;v,0.0&#41;;
qt &#58;= quatMul&#40;q,qv&#41;;
qt &#58;= quatMul&#40;qt,q_1&#41;;
Result.x &#58;= qt.x;
Result.y &#58;= qt.y;
Result.z &#58;= qt.z;
end;


I hope it helps you.

P. S. My code is not tested since I don't use quatrnion based camera.

vgo
20-02-2007, 08:30 AM
Hmm... Maybe I have misunderstood something about how the cameras work?

My camera class has only the position as a vector and orientation as a quaternion. I multiply the orientation with given pitch/yaw/roll degrees, then convert the resulting quaternion to a matrix which I then feed to OpenGL to update the view. I also translate the position vector with the direction orientation information of the quaternion when I move the camera.

All this works fine if I don't try to turn the camera upside down at that point everything goes to hell. :)

grudzio
20-02-2007, 02:35 PM
I've made a false assumption that you store camera orientation as a vector.

But if you store orientation as a quaternion, isn't it enough to create orientation from Euler angles, convert to matrix and send to OpenGL, without the multiplication?

vgo
20-02-2007, 02:50 PM
If I don't reset the pitch/yaw/roll I can do that, but I still got the same problem: the camera rotates about the world axis, not about it's own axis.
If I pitch the camera by 90 degrees and then yaw it, the camera seems to roll, because it's still rotating about the world y-axis, not the local y-axis.

Maybe I should make a little test program to demonstrate the problem I have. :)

EDIT: Here's some screenshots from a test program.

Here's the camera's default orientation, facing along the Z-axis:
Camera1 (http://www.projectminiverse.com/images/screenshots/Camera1.jpg)

Now I rotate it 90 degrees about the X-axis (pitch):
Camera2 (http://www.projectminiverse.com/images/screenshots/Camera2.jpg)

Next I rotate it 90 degrees about the Y-axis (yaw), but this is wrong. I'd expect the camera to face along the X-axis:
Camera3 (http://www.projectminiverse.com/images/screenshots/Camera3.jpg)

This is how I'd want it to look like after the X - Y rotations:
Camera4 (http://www.projectminiverse.com/images/screenshots/Camera4.jpg)

How can I achieve this?

grudzio
20-02-2007, 04:31 PM
I think the problem is that your Euler angles are specified in world coordinate system but they have to be specified in camera coordinate system. You need to store in camera class ist coordinate system as a three vectors:
:arrow: look - camera z axis
:arrow: up - camera y axis
:arrow: right - camera x axis
So, rotating along y axis means rotating along camera up vector. Each time camera rotates along one of its vectors you must update the other two. Then you create proper viewing matrix from look, up and right vectors and send it to OpenGL.

Here is an example of rotating along camera Y axis


procedure TCamera.RotateY&#40;angle &#58; single&#41;;
var
rot &#58; TQuaternion;
begin
//create rotation quaternion from angle and camera up &#40;Y&#41; axis
rot &#58;= quatFromAxisAngle&#40;fUp,angle&#41;;
//rotate look and right vectors
look &#58;= rotateVector&#40;rot,look&#41;;
right &#58;= rotateVector&#40;rot,right&#41;; //or right &#58;= CrossProduct&#40;look,up&#41;;
normalize&#40;look&#41;;
normalize&#40;right&#41;;
end;

The right, up and look vectors are columns of a rotation matrix.
Here is my code for setting such matrix for OpenGL (including translation)
My code is based on this sample (http://www.codesampler.com/oglsrc/oglsrc_5.htm#ogl_fps_controls)


procedure TCamera.SetView;
var
viewMat &#58; TMatrix44;
_rp,_lp,_up &#58; single;
begin
_rp &#58;= vecDot&#40;fRight,fPos&#41;;
_up &#58;= vecDot&#40;fUp,fPos&#41;;
_lp &#58;= vecDot&#40;fLook,fPos&#41;;
viewMat&#91;0&#93; &#58;= fRight.x; viewMat&#91;1&#93; &#58;= fUp.x; viewMat&#91;2&#93; &#58;= -fLook.x;
viewMat&#91;4&#93; &#58;= fRight.y; viewMat&#91;5&#93; &#58;= fUp.y; viewMat&#91;6&#93; &#58;= -fLook.y;
viewMat&#91;8&#93; &#58;= fRight.z; viewMat&#91;9&#93; &#58;= fUp.z; viewMat&#91;10&#93; &#58;= -fLook.z;
viewMat&#91;12&#93; &#58;= -_rp; viewMat&#91;13&#93; &#58;= -_up; viewMat&#91;14&#93; &#58;= _lp; viewMat&#91;15&#93; &#58;= 1.0;

glMultMatrixf&#40;@viewMat&#41;;
end;

Two more things:
1. You may want to get rid of minus signs in the code above.
2. Such camera may not be good for your FPS mode. In this case instead of rotating along up vector you should always rotate along (0,1,0) vector.
(This is how I did my FPS camera)

vgo
20-02-2007, 06:57 PM
I tried something similar, but I couldn't get it to work. Now I tried your code and it didn't work either, after a few rotations the view matrix contained garbage, turned out that my vector normalisation function was broken! :shock:

I wrote a new one and now it works perfectly, now I can pitch, yaw and roll like there's no tomorrow! Thanks a lot for your help! :D

vgo
02-06-2007, 07:46 AM
This is one of the things that's been bothering me, it just felt silly to have those Right/Up/Look vectors to help with rotating and I think I've finally figured out a way to rotate without them. :)

This is code from my camera test class:

if (Pitch <> 0) or (Yaw <> 0) or (Roll <> 0) then
begin
q := QuaternionRotateObjectToInertial(DegToRad(Pitch), DegToRad(Yaw), DegToRad(Roll));
Orientation := QuaternionCrossProduct(Orientation, q);
Matrix := MatrixCreateOpenGLTranslation(Orientation, Position);
Pitch := 0;
Yaw := 0;
Roll := 0;
end;


Here are the quaternion functions:

function MatrixCreateOpenGLTranslation(const Orientation: TQuaternion; const Position: TVector3f): TMatrix4x4f;
begin
Result[0,0] := 1 - 2*Orientation.y*Orientation.y - 2*Orientation.z*Orientation.z;
Result[1,0] := 2*Orientation.x*Orientation.y - 2*Orientation.w*Orientation.z;
Result[2,0] := 2*Orientation.x*Orientation.z + 2*Orientation.w*Orientation.y;
Result[3,0] := 0;

Result[0,1] := 2*Orientation.x*Orientation.y + 2*Orientation.w*Orientation.z;
Result[1,1] := 1 - 2*Orientation.x*Orientation.x - 2*Orientation.z*Orientation.z;
Result[2,1] := 2*Orientation.y*Orientation.z - 2*Orientation.w*Orientation.x;
Result[3,1] := 0;

Result[0,2] := 2*Orientation.x*Orientation.z - 2*Orientation.w*Orientation.y;
Result[1,2] := 2*Orientation.y*Orientation.z + 2*Orientation.w*Orientation.x;
Result[2,2] := 1 - 2*Orientation.x*Orientation.x - 2*Orientation.y*Orientation.y;
Result[3,2] := 0;

Result[0,3] := Position.x;
Result[1,3] := Position.y;
Result[2,3] := Position.z;
Result[3,3] := 1;
end;

function QuaternionRotateObjectToInertial(const Pitch, Yaw, Roll: TFloat): TQuaternion;
var
sp, sb, sh: TFloat;
cp, cb, ch: TFloat;
begin
// Compute sine and cosine of the half angles
sp := Sin(Pitch * 0.5);
cp := Cos(Pitch * 0.5);
sb := Sin(Roll * 0.5);
cb := Cos(Roll * 0.5);
sh := Sin(Yaw * 0.5);
ch := Cos(Yaw * 0.5);
// Compute values
Result.w := ch*cp*cb + sh*sp*sb;
Result.x := ch*sp*cb + sh*cp*sb;
Result.y := -ch*sp*sb + sh*cp*cb;
Result.z := -sh*sp*cb + ch*cp*sb;
end;

function QuaternionCrossProduct(const q1, q2: TQuaternion): TQuaternion;
begin
Result.w := q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z;
Result.x := q1.w*q2.x + q1.x*q2.w + q1.z*q2.y - q1.y*q2.z;
Result.y := q1.w*q2.y + q1.y*q2.w + q1.x*q2.z - q1.z*q2.x;
Result.z := q1.w*q2.z + q1.z*q2.w + q1.y*q2.x - q1.x*q2.y;
end;


This seems to work, but I still need to do some more testing. The quaternion used for orientation should probably be normalized every now and then.

Does anyone else use this scheme for object/camera rotation, any comments on this?