September 96 - Game Controls for QuickDraw 3D
Game Controls for QuickDraw 3D
Philip McBride
Whether the user is navigating a starship or examining a model of
the DNA helix, your first-person 3D application must allow user
control of the camera movements in a scene. You must keep
changing the camera's position and orientation in response to what
the user wants to see. Here you'll learn how to create those camera
movements and handle the user's directions. As part of the bargain,
you'll even get a refresher course in the associated geometry.
Letting the user control the movement of the camera (and thus the view) is critical to
first-person interactive 3D games and extremely useful in 3D modeling systems.
Through QuickDraw 3D's camera functions and supporting mathematical functions, you
can create game controls that direct the position and orientation of a camera. In
general, game controls take user input from any input device and control the camera in
ways that emulate movements of players, such as people or aircraft. Game controls are
useful for any type of 3D viewer application, including 3D Internet browsers.
You'll start your career as a camera operator by learning about the basic moves you
can make with the camera. Then you'll create the various camera movements, keep the
camera movements smooth, and translate user inputs to move the camera. The sample
code (which is provided on this issue's CD) is a 3D viewer application with camera
movements activated by the keyboard or the mouse. In all of the code, the geometry has
been kept as simple as possible, but if you need to brush up, you'll find a refresher
course on calculating points and vectors in 3D space.
For an overview of QuickDraw 3D, turn to "QuickDraw 3D: A New Dimension for
Macintosh Graphics" in develop Issue 22. That article discusses topics like reading
models, using a viewer, creating a camera, and managing documents that have 3D
information. To learn more about those and related topics, see the list of recommended
reading at the end of this article.
MOVING THE CAMERA
We'll be controlling camera movements based on first-person viewing, so the camera
will be our eyes. But before we move through a scene, let's take a look at the kind of
camera moves we plan to use. The camera movements you would create in a 3D game
for a person who is driving a vehicle or walking on level ground are examples of
ground movements. These camera moves include moving forward, backward, sideways
to the left, and sideways to the right, plus turning to the left (pan or yaw left) and
turning to the right (pan or yaw right). Figure 1 illustrates these basic ground
movements.
Figure 1. Ground movements
You can also go airborne with a variety of camera movements. These fancier camera
moves are changes that might be typical of an aircraft. They include ascending and
descending (moving upward and downward), pitching (tilting) up and down, and
rolling (tilting) left and right. Figure 2 illustrates these moves.
Figure 2. Air movements
Now to the fun part -- let's get that camera moving! What you must do to achieve the
previously described camera movements, both ground and air, involves some
geometry. If you're like most of us and have forgotten your 3D geometry, see "3D
Geometry 101" for a refresher course. The 3D geometry for our camera moves is
quite simple; it will stick to the kinds of calculations illustrated in "3D Geometry
101.
First, let's take a look at our world. In Figure 3, we have an object in the world
coordinate system and a camera looking at the object. The camera has its own
coordinate system defined by its location (in world coordinates), up vector, and point
of interest.
Figure 3. Our world
______________________________
3D GEOMETRY 101
If you're new to 3D programming (and perhaps a little rusty on your math),
here's a brief introduction to some of the 3D concepts you'll find in this
article's code.
A point is represented in 3D space by x, y, and z values in a coordinate system.
A vector is a magnitude (length) and direction; it's represented by an initial
point (usually the origin of the coordinate system) and a final point {x, y, z}.
Figure 4 illustrates a point and a vector in 3D space.
Figure 4. A point and a vector in 3D space
To add a vector and a point, you place the vector's initial point on that point
(keeping the vector's direction and magnitude). The new final point of the
moved vector is the point resulting from the addition. (See Figure 5.)
Figure 5. Adding a vector and a point
To subtract a vector from a point, you place the vector's final point on that
point (keeping the vector's direction and magnitude). The new initial point of
the moved vector is the result (Figure 6).
Figure 6. Subtracting a vector from a point
To create a vector between two points, you subtract the vectors defined by the
points (called position vectors). To do this, you first reverse (turn around)
the second vector and place its initial point on the final point of the first
vector. Then you make a new vector from the first vector's initial point to the
second vector's new final point. This new vector has the direction and
magnitude of the vector between the two points (Figure 7).
Figure 7. Creating a vector between two points
A translation of a point or a vector by Tx, Ty, and Tz values moves the point or
the vector by adding the T values to its own values (Figure 8).
Figure 8. Translating a point or a vector by T
In Figure 8, the translation value T is really from the translation part of a
transformation matrix. A transformation matrix is used to transform a point
or a vector by translation, rotation, and scaling. The transformation matrix
you use is 4 x 4 -- with the upper-left 3 x 3 portion acting as the rotation
matrix, the bottom-left 1 x 3 portion acting as the translation matrix, and the
top-left to bottom-right diagonal of the rotation matrix acting as the scaling
matrix. The following transformation matrix has elements labeled for
translation (T), rotation (R), and scaling (S). The fourth column is ignored
for simplicity.
When you apply a transformation to a point or a vector, you multiply by the
matrix, as in the following formula for our point {x, y, z} and a
transformation matrix:
[{Sx*R0,0*x + R1,0*y + R2,0*z + Tx},
{R0,1*x + Sy*R1,1*y + R2,1*z + Ty},
{R0,2*x + R1,2*y + Sz*R2,2*z + Tz}]
As you can see from this formula, if you only want the matrix to apply a
translation (the T's), the 3 x 3 rotation matrix will be all 0's except for the
scaling diagonal, which will be all 1's.
A rotation of a vector through an arbitrary angle about different axes will use various R elements (the 3 x 3 rotation matrix of the transformation
matrix), depending on which axis you're rotating about. For rotations
of[[theta]] about the x axis, you get the matrix
For rotations about the z axis, you get
And for rotations about the y axis, you get the following matrix:
So to apply a rotation about an axis, you simply multiply the appropriate
rotation matrix by the vector. In Figure 9, the vector on the right is rotated
90deg. about the z axis in the {x, y} plane.
Figure 9. Rotating a vector about an axis
______________________________
We'll be dealing with the vectors making up the camera's coordinate system for many
of our movement functions, so let's keep these in our application's document structure.
We'll keep the camera placement data there as well.
The document structure looks like this:
typedef struct _DocumentRecord {
...
TQ3Point3D cameraLocation;
TQ3Point3D pointOfInterest;
TQ3Vector3D xVector;
TQ3Vector3D yVector; // up vector
TQ3Vector3D zVector;
...
} DocumentRecord, *DocumentPtr;
The first time we set up our camera, we'll set the values in our document to
correspond to the initial camera position. Then with each subsequent movement of the
camera, we'll update these fields. The initial camera data is constructed by the code in
Listing 1. In the function MyGetCameraData, we do some of our geometric calculations
to get the x and z vectors. We subtract the two endpoints (the initial and final points)
of the z vector to get that vector. And we get the x vector by cross-multiplying the y
and z vectors.
______________________________
Listing 1. Initializing the camera data
void MyGetCameraData(DocumentPtr theDocument,
TQ3CameraObject theCamera)
TQ3CameraPlacement cameraPlacement;
// Get the camera data.
Q3Camera_GetPlacement(theCamera, &cameraPlacement);
// Set the document's camera data.
theDocument->cameraLocation = cameraPlacement.cameraLocation;
theDocument->pointOfInterest = cameraPlacement.pointOfInterest;
theDocument->yVector = cameraPlacement.upVector;
// Calculate the x and z vectors and assign them to the document.
Q3Point3D_Subtract(&theDocument->pointOfInterest,
&theDocument->cameraLocation, &theDocument->zVector);
Q3Vector3D_Cross(&theDocument->zVector,
&theDocument->yVector, &theDocument->xVector);
}
______________________________
After the fields in our document have been updated by some camera movement function,
we'll want to reset the camera to that new data with the function MySetCameraData
(Listing 2).
______________________________
Listing 2. Setting the camera data after a move
void MySetCameraData(DocumentPtr theDocument,
TQ3CameraObject theCamera)
TQ3CameraPlacement cameraPlacement;
// Set the camera placement data.
cameraPlacement.cameraLocation = theDocument->cameraLocation;
cameraPlacement.pointOfInterest = theDocument->pointOfInterest;
cameraPlacement.upVector = theDocument->yVector;
// Set the camera data to the camera.
Q3Camera_SetPlacement(theCamera, &cameraPlacement);
}
______________________________
With that camera infrastructure, we're ready to move the camera around a bit. You can
find the code for all the moves on this issue's CD. Here you'll find only the code for
those movements that are unique. Code for those moves not shown (but previously
mentioned) is almost identical to one of the functions shown in the listings.
To move the camera along the z axis either forward or backward, we call the function
MyMoveCameraZ (Listing 3). This function translates the camera location and point of
interest by the given delta. Note that the associated z vector isn't changed.
______________________________
Listing 3. Moving the camera along the z axis
void MyMoveCameraZ(DocumentPtr theDocument, float dZ)
TQ3ViewObject theView;
TQ3CameraObject theCamera;
TQ3Vector3D scaledVector;
TQ3Point3D newPoint;
// Get the view and the camera objects.
theView = theDocument->theView;
Q3View_GetCamera(theView, &theCamera);
// Scale the y vector to make it dY longer.
Q3Vector3D_Scale(&theDocument->yVector,
dY/Q3Vector3D_Length(&theDocument->yVector),
&scaledVector);
// Move the camera position and direction by the new vector.
Q3Point3D_Vector3D_Add(&theDocument->cameraLocation,
&scaledVector, &newPoint);
theDocument->cameraLocation = newPoint;