OpenGL Programming/Glescraft 4



= Introduction =

There are several ways to manipulate the view of a 3D scene. For Computer Aided Design (CAD) software, you usually have a fixed camera point, and use a (3D) mouse or trackball to rotate or move the object of interest. In many games, especially the First Person Shooters, the world is static, but you move the camera around the world. The graphics card makes no distinction between those two ways, it just applies the model-view-projection matrix you provide. The major distinction is in the way we manipulate the MVP matrix with the mouse and/or keyboard. For our first-person camera, we derive the MVP matrix from two vectors; the camera position and the view angles.

= Capturing mouse movement in GLUT =

GLUT has two callbacks to capture mouse movement. One is called when the mouse is moved while at least one mouse button is pressed, the other is called when no mouse buttons are pressed. In our case, we don't want to distinguish between the two cases, so we can register the same function for both callbacks:

When your application or game is in first person mode, you usually don't want to see the mouse cursor. We can disable it in this way:

If are not in first person mode anymore, for example when you are displaying menus, or if the game is paused, you can show the default mouse cursor again with glutSetCursor(GLUT_CURSOR_INHERIT).

The motion callback function will get the current mouse coordinates relative to the top left of the window (or relative to the top left of the screen if the window is in full screen mode). However, we don't want to know the current coordinates, we are only interested in knowing how much the mouse was moved. Of course, we could subtract the coordinates from the previous call to motion from the current ones, but that doesn't work anymore when the mouse cursor is at the edge of the window or screen! The solution in GLUT is, whenever the mouse is moved, to move the mouse cursor back to the center of the window with the glutWarpPointer function. However, using this function causes GLUT to call the motion callbacks again, so we have to ignore every second call to motion:

In the above function, dx and dy variables hold the distince in pixels that the mouse cursor moved.

= View direction =

As human beings living on a planet, we make a big distinction between looking around us in the horizontal plane, and between looking up and down. Partly because the most interesting things happen at the same level above ground as we are on ourself, but also because it is easy to turn around our (vertical) axis as much as we want, but we can only turn our heads up and down so much. In FPS games, one can rotate around the vertical axis as much as you want, but up and down is restricted to +/- 90 degrees. Apart from this restriction, the change in viewing angles is equal to the amount of mouse movement times a scaling factor:

From the angles vector, we can calculate the direction we are looking at using simple trigonometry:

Given a position vector and the <tt>lookat</tt> vector, we can create a view matrix as follows:

Exercises:
 * Many games restrict looking up and down to slightly less than 90 degrees. Can you think of a reason for this?
 * What would happen if you go beyond 90 degrees for the up/down angle? Would the scene look upside-down?
 * Instead of calculating the <tt>lookat</tt> vector and using the <tt>glm::lookAt</tt> function, try constructing the <tt>view</tt> matrix using <tt>glm::rotate</tt>. Does this give the same results? Also if you go beyond 90 degrees for the up/down angle?

= Camera movement =

Since we have already used the mouse to determine the viewing angle, we are left with the keyboard for movement of the camera position. While the computer can tell how far you moved the mouse, keys are either pressed or not pressed. Although we can easily register a keyboard callback that moves the camera position by a fixed amount each time a key is pressed, this results in very jerky movement of the camera. Instead of controlling the position directly, we want to change our speed using the keyboard. If no keys are pressed, our speed is zero. If the up arrow key is pressed, we have a certain speed in the forward direction. If the down arrow key is pressed, we have a negative speed in the forward direction. If the right arrow is pressed, we have a certain speed in the sideways direction, and so on. We can register a callback with <tt>glutSpecialFunc</tt> that is called each time a "special" key (such as a cursor key) is pressed. However, we also need to know when that key is released again. For that, we can register a callback with <tt>glutSpecialUpFunc</tt>:

The two callback functions look like this:

The <tt>move</tt> variable will then contain a bitmask of the movement keys that are currently pressed. The camera position should be updated regularly, preferably once per frame. We can register a function in GLUT that is called whenever it has nothing to do:

We can then update the camera position in that function and tell GLUT to redraw the scene. In order to update the camera position vector, we need to know which direction "forward", "right" and so on is. In most games, "forward" is the direction we are looking at, but only in the horizontal plane. Usually, this is because you are standing on the ground, and even if you walk around while looking a little bit up or down, you stay on the floor. From the "forward" vector, we can also derive a "right" vector, and the "up" vector simply points in the positive y direction. The result looks like this:

Exercises:
 * Instead of directly using trigonometry, try to derive <tt>forward_dir</tt> and <tt>right_dir</tt> from <tt>lookat</tt> and the "up" vector using vector algebra.