Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
100 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

The quaternion camera

To make a camera you typically use three vectors: Position, View, and Up (or you may call them what you like). For a first person camera - which we will be using - we're only going to consider rotating the View vector. With quaternions we can rotate a vector around an arbitrary axis (same as with axis-angles) very easily.

To achieve this, first we need to turn our View vector into a quaternion, then define a rotation quaternion and lastly, apply the rotation quaternion to the View quaternion to make the rotation.

To make the View quaternion, V, the x, y, and z values are taken from the View vector and we simply add a 0 for the scalar component w. Thus,

V = [0, View]

Then you need to make a quaternion to represent the rotation. To do this, you need the vector you want to rotate about, and the angle you wish to rotate by. We'll just simply term the vector to rotate about A, and the angle theta. Here is the formula to build your rotation quaternion, which we'll call R.

vector A = [x, y, z] 
R.x = A.x * sin(theta/2)
R.y = A.y * sin(theta/2)
R.z = A.z * sin(theta/2)
R.w = cos(theta/2)

So now we have the vector (View) and its quaternion V that we want to rotate by an angle theta about the vector A. The rotation quaternion R defines this rotation. After the rotation, we'll have the new quaternion representing our view, given by W. The rotation operation is simply

W = R * V * R'

where R' is the conjugate of R. To get our new view vector, we just take the vector components out of W.

NewView = [W.x W.y W.z]

The following function (using SDL, use glut or whatever you like) sets the view based on the distance from the current mouse coordinates to the centre of the screen. I learned how to do this from gametutorials.com, and modified the code for my purposes. In this code, positive x is to the right and positive y is down the screen.

void Camera::SetViewByMouse(void)
{
  // the coordinates of our mouse coordinates
  int MouseX, MouseY;

  // the middle of the screen in the x direction
  int MiddleX = SCREENWIDTH/2;

  // the middle of the screen in the y direction
  int MiddleY = SCREENHEIGHT/2;

  // vector that describes mouseposition - center
  Vector MouseDirection(0, 0, 0);

  // static variable to store the rotation about the x-axis, since
  // we want to limit how far up or down we can look.
  // We don't need to cap the rotation about the y-axis as we
  // want to be able to turn around 360 degrees
  static double CurrentRotationAboutX = 0.0;

  // The maximum angle we can look up or down, in radians
  double maxAngle = 1;

  // This function gets the position of the mouse
  SDL_GetMouseState(&MouseX, &MouseY);

  // if the mouse hasn't moved, return without doing
  // anything to our view
  if((MouseX == MiddleX) && (MouseY == MiddleY))
    return;

  // otherwise move the mouse back to the middle of the screen
  SDL_WarpMouse(MiddleX, MiddleY);

  // get the distance and direction the mouse moved in x (in
  // pixels). We can't use the actual number of pixels in radians,
  // as only six pixels  would cause a full 360 degree rotation.
  // So we use a mousesensitivity variable that can be changed to
  // vary how many radians we want to turn in the x-direction for
  // a given mouse movement distance

  // We have to remember that positive rotation is counter-clockwise. 
  // Moving the mouse down is a negative rotation about the x axis
  // Moving the mouse right is a negative rotation about the y axis
  MouseDirection.x = (MiddleX - MouseX)/MouseSensitivity; 
  MouseDirection.y = (MiddleY - MouseY)/MouseSensitivity;

  CurrentRotationX += MouseDirection.y;
  
  // We don't want to rotate up more than one radian, so we cap it.
  if(CurrentRotationX > 1)
  {
    CurrentRotationX = 1;
    return;
  }
  // We don't want to rotate down more than one radian, so we cap it.
  if(CurrentRotationX < -1)
  {
    CurrentRotationX = -1;
    return;
  }
  else
  {
    // get the axis to rotate around the x-axis. 
    Vector Axis = CrossProduct(View - Position, Up);
    // To be able to use the quaternion conjugate, the axis to
    // rotate around must be normalized.
    Axis = Normalize(Axis);

    // Rotate around the y axis
    RotateCamera(MouseDirection.y, Axis.x, Axis.y, Axis.z);
    // Rotate around the x axis
    RotateCamera(MouseDirection.x, 0, 1, 0);
  }
}

This function actually rotates our view. After we are done, just plug your camera vectors (Position, View, and Up) into gluLookAt(Position.x, Position.y, Position.z, View.x, View.y, View.z, Up.x, Up.y, Up.z). Here is the code for the rotation.

void RotateCamera(double Angle, double x, double y, double z)
{
  quaternion temp, quat_view, result;

  temp.x = x * sin(Angle/2);
  temp.y = y * sin(Angle/2);
  temp.z = z * sin(Angle/2);
  temp.w = cos(Angle/2);

  quat_view.x = View.x;
  quat_view.y = View.y;
  quat_view.z = View.z;
  quat_view.w = 0;

  result = mult(mult(temp, quat_view), conjugate(temp));

  View.x = result.x;
  View.y = result.y;
  View.z = result.z;
}

Again, at the end of the above functions, you should call

gluLookAt(Position.x, Position.y, Position.z,
          View.x, View.y, View.z, Up.x, Up.y, Up.z).

and your camera should work just perfectly. Maybe some other time, I'll do a third person camera tutorial, and explain how to use SLERP.


Contents
  Introduction
  Quaternion Operations
  The Quaternion Camera

  Printable version
  Discuss this article