I spent the afternoon rewriting my camera class to store its orientation as a quaternion instead of lookAt, right, and up vectors. I use DirectX so I can take advantage of the built in math library which is optimized. For OpenGL you’ll have to write your own math code, which isn’t too hard. Here is a site that has some basic code. Anyway I’m providing my new camera class under the Boost License:
The trickiest part is getting the quaternion from a focal point and position, but its really not that complicated. First you get a “lookAt” vector by subtracting the position from the focal point and normalizing. Then you take a passed “up” vector, usually (0,1,0), and cross product the lookAt vector it with, storing the result as the “right” vector and normalize. Then all you need to do is cross product the lookAt vector with the right vector, normalize the result and you’ve got your up, right, and lookAt vectors. The following is the code:
D3DXVECTOR3 v3Up, v3Right, v3LookAt = v3FocalPoint - v3Position;
D3DXVec3Normalize( &v3LookAt, &v3LookAt );
D3DXVec3Cross( &v3Right, &v3PassedUpVec, &v3LookAt );
D3DXVec3Normalize( &v3Right, &v3Right );
D3DXVec3Cross( &v3Up, &v3LookAt, &v3Right );
D3DXVec3Normalize( &v3Up, &v3Up );
Then you need to create a rotation matrix from those axes:
D3DXMATRIX mRotation;
D3DXMatrixIdentity( &mRotation );
mRotation(0,0) = v3Right.x;
mRotation(0,1) = v3Up.x;
mRotation(0,2) = v3LookAt.x;
mRotation(1,0) = v3Right.y;
mRotation(1,1) = v3Up.y;
mRotation(1,2) = v3LookAt.y;
mRotation(2,0) = v3Right.z;
mRotation(2,1) = v3Up.z;
mRotation(2,2) = v3LookAt.z;
Finally, you just use the DirectX function D3DXQuaternionRotationMatrix to get the final quaternion.
One of the nice things about using quaternions is they can be interpolated without looking bad. If you were to linearly interpolate a lookAt vector for small differences you would be ok, but imagine interpolation from a vector and one rotated almost 180 degrees. The interpolated values would become non-unit length and just look bad if you are watching the camera movement. The answer is slerp. Slerp can interpolate two quaternions using the shortest path between the two resulting in smooth transitions.
Using slerp I added a filtering component to my camera. Instead of taking values directly as they are set I use an exponential moving average. Here is the code for updating the averages each frame:
D3DXVec3Lerp( &v3FilteredPosition, &v3FilteredPosition, &v3Position, fFilterAlpha );
D3DXQuaternionSlerp( &qFilteredRotation, &qFilteredRotation, &qRotation, fFilterAlpha );
The alpha factor determines how much we weight older observations and changes the speed at which the filtered values catch up to the current ones. Now when I change the camera rotation or position suddenly, the averages smooth out the movement. Its a cool effect but it does add a lag to camera movement which is why I don’t use an alpha value that is too low.