back to table of contents

Camera Translation and Rotation
download mac os x application and source code (76k)
read "controls" for keys to move and rotate camera
First released thursday february 7, 2002. Developed with apple's free developer tools on mac os 10.1
This document, images, source and application are the intellectual property of forrest briggs. You may freely use the source code i provide. Email me with questions or suggestions.

Theory
In triangle rasterizing 3d engines, triangles are transformed into camera space, then projected onto the screen. We can take a much more direct approach to camera movement in ray tracing. Changing the location of the camera is as simple as changing the origin of the camera rays. The camera can be rotated by rotating the direction of the camera rays as though they were points. Until now, i have been using the following simple equations to rotate primitives:

X-axis:
Y' = y * cosθx - z * sinθx
Z' = z * cosθx + y * sinθx

Y-axis:
Z' = z * cosθy - x * sinθy
X' = x * cosθy + z * sinθy

Z-axis:
X' = x * cosθz - y * sinθz
Y' = y * cosθz + x * sinθz

Where (x,y,z) is the original point, (x',y',z') is the new point and θ is the angle. An important thing to notice here is that for x-axis rotation, y is used in the calculation of z', so make sure to use The original value (y) not the new value (y'.) The same is true of the other axes. We cannot use these formulas effectively for camera movement because they can only rotate around the x, y and z axes of world Space. To illustrate this point, first turn your head slightly to the right, then tilt up. You first rotated your head around the y-axis, then rotated around the x-axis, but not the x-axis of world space. You Rotated around the x-axis in head-space. In order to rotate around any axis, we must use matrices, which are also more efficient.

Matrices
Matrices are covered in depth many other places on the web (try searching on google,) so i will only describe the parts of them that are relevant to rotation. This is in no way a complete description of matrices and should either be supplemented with previous knowledge or other reading. It may be easy for programmers to think of matrices as 2 dimension arrays of floating point numbers. Matrices are written like this:

   [m00 m10 m20]
M =[m01 m11 m21]
   [m02 m12 m22]
Where mxy denotes the element of m at position (x,y) and m is a 3x3 matrix. Math books will describe the elements of a matrix with (row, column) which is the same as (y,x) and start counting at 1. Since matrices are represented in C++ as 2 dimensional arrays, it is convenient to start at 0 and use (x,y) addressing. All of the math will come out the same though.

In order to multiply matrices, the x dimension of the first matrix must be the same as the y dimension of the second. The resulting matrix has the x dimension of the second matrix and the y Dimension of the first matrix. Each element of the resulting matrix is the dot product of the row of the first matrix with the same y coordinate as the element and the column of the second matrix with the same x. In such terms, matrices will warp your mind. An example will help:

          [1]
[4 7 8] * [5] = [((4*1) + (7*5) + (8*2))]=[(4 + 35 + 16)]=[55]
          [2]
The resulting matrix is 1x1 because the second matrix has an x dimension of 1 and the first matrix has a y dimension of 1. Matrix multiplication is not commutative, meaning that a * b is not always the same as B * a. It is associative, so a * b * c = a * (b * c), which will be important later.

Here is the code to multiply 2 3x3 matrices:
Matrix3x3 MatrixMult3x3(Matrix3x3 &a, Matrix3x3 &b) //returns ab
{
 Matrix3x3 result;

 For(int i=0; i<3; i++)
  For(int j=0; j<3; j++)
   Result.Elements[i][j] = a.Elements[0][j] * b.Elements[i][0] + 
                           A.Elements[1][j] * b.Elements[i][1] + 
                           A.Elements[2][j] * b.Elements[i][2];

 Return result;
}
And to multiply a vector, which can be considered a 3x1 matrix by a 3x3 matrix:
Inline Vector VectorMultMatrix(Vector &a, Matrix3x3 &b)
{
 Vector result;
 Result.X = a.X * b.Elements[0][0] + a.Y * b.Elements[0][1] + a.Z * b.Elements[0][2];
 Result.Y = a.X * b.Elements[1][0] + a.Y * b.Elements[1][1] + a.Z * b.Elements[1][2];
 Result.Z = a.X * b.Elements[2][0] + a.Y * b.Elements[2][1] + a.Z * b.Elements[2][2];
 Return result;
}

Rotation Matrices
Matrices can be used for rotation. For example, the matrix for rotation about the x-axis is

    [1 0     0    ]
R = [0 cosθ -sinθ]
    [0 sinθ cosθ ]
If a point or vector p = [x y z] then
                       [1 0     0    ]
P' = p * r = [x y z] * [0 cosθ -sinθ] = [x (y * cosθ - z * sinθ) (z * cosθ + y * sinθ)]
                       [0 sinθ cosθ ]
Which is the same as the equation for x-axis rotation previously described. Here is the matrix for rotation around any axis:
    [(t * x2 + c)        (t * x * y - s * z) (t * x * z + s * y)]
R = [(t * x * y + s * z) (t * y2 + c)        (t * y * z - s * x)]
    [(t * x * z - s * z) (t * y * z + s * x) (t * z2 + c)       ]
When c = cosθ, s = sinθ, t = 1 - cosθ and (x,y,z) is a normalized vector in the axis of rotation. Here is a discussion of this matrix that goes into quaternions, which are 4 dimensional complex numbers.

Implementation
Now we can rotate the rays by constructing a rotation matrix and using VectorMultMatrix() to multiply the direction of the rays by that matrix, resulting in the correctly rotated direction. This matrix will need to be updated every time the camera rotates, and it should be initialized so that if a ray's direction is multiplied by it, the direction will not change at all. For this, we need The identity matrix (i.)

    [1 0 0]
I = [0 1 0]
    [0 0 1]
The identity matrix has the property that if a matrix m of the correct dimensions is multiplied by it, it will not change. In other words, m * i = m. The camera data structure contains 3 vectors: right, up and forward. These vectors define the direction that the camera is pointing and are initialized to right = (1,0,0), up = (0,-1,0) and forward = (0,0,1). These values may differ depending on your coordinate system.

Once the camera is set up, we can rotate it with CameraRotate()
Void CameraRotate(Camera &camera, float thetaRight, float thetaUp, float thetaForward)
{
 Matrix3x3 rotationRight, rotationUp, rotationForward;

 // calculate rotation matrices about each axis that defines the camera's direction
 RotationRight = CalculateArbitrayRotationMatrix(camera.Right, thetaRight);
 RotationUp = CalculateArbitrayRotationMatrix(camera.Up, thetaUp);
 RotationForward = CalculateArbitrayRotationMatrix(camera.Forward, thetaForward);

 Matrix3x3 combinedRotationMatrix;
 // combinedeRotationMatrix = rotationRight * rotationUp * RotationForward
 //combinedRotationMatrix = MatrixMult3x3(MatrixMult3x3(rotationRight, rotationUp), rotationForward); gives a compiler error
 CombinedRotationMatrix = MatrixMult3x3(rotationRight, rotationUp);
 CombinedRotationMatrix = MatrixMult3x3(combinedRotationMatrix, rotationForward);

 // rotate the defining axes by the combined rotation matrix 
 Camera.Right = VectorMultMatrix(camera.Right, combinedRotationMatrix);
 Camera.Up = VectorMultMatrix(camera.Up, combinedRotationMatrix);
 Camera.Forward = VectorMultMatrix(camera.Forward, combinedRotationMatrix);
 
 // combine the current ray rotation matrix with the new matrix
 Camera.RayRotationMatrix = MatrixMult3x3(camera.RayRotationMatrix, combinedRotationMatrix);
}
This function takes the parameters thetaRight, thetaUp and thetaForward, which are the angles around the camera space axes up, right and forward that the camera should rotate. This means that they are the Angles the camera should turn relative to where it is already looking, not the x, y and z axis in world space. Next, rotation matrices are calculated around each axis and these matrices are combined into a Single matrix that when multiplied by a vector has the same effect as multiplying the vector by each of the matrices in sequence. If p is a vector and Rx, ry and rz are rotation matrices, then to rotate p by rx, ry and rz, we could first multiply p by rx, then multiply the result of That by ry and then multiply the result of that by rz. This is equivalent to p' = p * rx * ry * rz. Because matrix multiplication is associative, P * rx * ry * rz = p * (rx * ry * rz) or if rxyz = rx * ry * rz then P * rx * ry * rz = p * rxyz. The compiler error mentioned in the comment is that MatrixMult3x3() cannot take the result of itself as a parameter because that is a value, Not a reference. Next we rotate each of the camera axes with the combined rotation matrix. Finally, we combine the ray rotation matrix with the new rotation matrix, which again because of the associative property Is like setting it to all of the previous rotations and then the new rotation. This is very important: multiplying a ray's direction by the ray rotation matrix is now equivelent to multiplying it by the Original matrix, then multiplying the result of that by the new rotation matrix. This looks like a lot of calculation but it happens at most once per frame, so its ok.

The function CalculateCameraRay() takes the screen coordinate through which we want a ray and like the previous examples, uses a lookup table to get the direction of that ray. Then it multiplies that direction By the ray rotation matrix to make it point in the rotated direction.
Inline Ray CalculateCameraRay(Camera &camera, int x, int y, Vector *directionTable)
{
    Ray ray;
    Ray.Origin = camera.Location; // maybe optimize this statement
    Ray.Direction = directionTable[x + (y<<9) + (y<<7)];
    
    Ray.Direction = VectorMultMatrix(ray.Direction, camera.RayRotationMatrix); // rotate the ray
    
    Return ray;
}