First-person Camera
The Matrix4.look method is handy when we want to fix the eye at an arbitrary location in the world. However, if we want the viewer to be able to navigate a scene, we need a more sophisticated abstraction that has behaviors for moving and turning. Let's build a FirstPersonCamera class that manages the viewer's state.
State
The FirstPersonCamera class must track its position and orientation. Some of the state will be the parameters we pass to Matrix4.look. Other state will be derived, like the eyeFromWorld matrix, which the renderer will access frequently.
Insert this code in lib/first-person-camera.ts in your project.
Behaviors
The FirstPersonCamera class provides several behaviors for initializing the camera, moving and turning it, and building the eyeFromWorld matrix.
Constructor
The constructor receives the camera's position and focal point and the world's up vector. It directly initializes all but its eyeFromMatrix and right variables. Note that we receive the focal point—which is a position—because the client of this class often knows what object the camera should look at. However, the focal direction—the forward vector—is more immediately useful and more flexible. We store it instead of the position.
The eyeFromWorld matrix must be rebuilt whenever the camera's position or rotation changes. Instead of duplicating that logic, we factor out a helper reorient method and call it at the end of the constructor.
Reorient
The reorient method is called whenever the camera moves or turns. It derives the eyeFromWorld matrix and right vector from the camera state using the look method we defined earlier.
Strafe
We want the camera to move as the viewer interacts with the renderer. One common camera motion in games is the strafe, in which the player sidesteps to the left or right without turning. We strafe the camera by pushing its position some distance along its right vector and then rebuilding the camera's transformation matrix.
If the distance is negative, the camera moves left.
Advance
Another common motion is for the camera to advance forward or backward. We advance the camera by pushing its position along the forward vector.
If the distance is negative, the camera moves backward.
Rotation
Imagine we are piloting an airplane and a flight controller tells us to turn 30 degrees. This isn't a helpful command because we have many possible turns to choose from. We could yaw, which is turning left or right. We could pitch, which is turning the nose up or down. We could roll, which is turning about the nose. Or we could rotate around some non-standard axis.
Cameras are like airplanes in that they may be turned in many directions. Users typically make them yaw and pitch in order to look at nearby objects in the scene. Outside of flight simulators, rolling isn't common.
We yaw the camera by rotating the forward direction around the world's up vector, which is a matrix-vector multiplication. What rotation matrix do we use? Currently our Matrix4 class only has methods for rotating around the x-, y-, and z-axes. The world's up axis could be anything. Let's assume for the moment we have a more general Matrix4.rotateAround(axis, degrees) method.
We similarly pitch the camera by rotating the forward direction around the right vector.
Since rolling is uncommon, we won't add a method for it.
Put all this code in your FirstPersonCamera class. We're almost ready to test it out in a renderer. We just need a rotateAround method.