A Monkey's Guide to Matrices
In the course of trying to enhance the glxCamera class in the ocx on this site, I found I didn't understand enough about matrices. I built the sample app so I could see exactly what was going on. The sample contains 2 objects – a camera (yellow box) and a model (white box). The panel on the left shows the current matrix for each object. The matrix is implemented as a VB class and contains a child class which draws the xyz axes of the matrix. On the panel you can select an active axis and a transformation from the comboboxes, then use the Up (or Right) and Down (or Left) arrow keys to transform the model or the camera. The objects can be transformed around their local axes, the world axes, or an arbitrary axis. The local axes can be selected by picking them with the mouse, but the other axes must be selected using the comboboxes.
In the first image we are standing outside of the camera and viewing it as an object. In the image below, we are viewing from the camera. The yellow box is the camera icon and the blue ball represents the other viewpoint ('from outside').
Using matrices for transformations
In the sample app, each object has its own matrix which represents its placement in the scene relative to the origin. When the model draws, it first pushes the current gl matrix, then draws itself, then pops the matrix to restore things to how they were before it drew. (This isolates the effects of its drawing commands to the scope of the Push/Pop operations.) To draw, the model multiplies the current gl matrix by its own matrix, then draws itself, then draws the axis icon. The effect is that all primitives which the model draws are transformed by the cumulative matrix. This is exactly equivalent to issueing a series of glTranslate, glScale, and glRotate commands.
Public Matrix As CMatrix
Public Sub Draw()
glPushMatrix
Matrix.Apply 'call glMultMatrix
glCallList ListID 'draw the model
Matrix.Draw 'draw the axes
glPopMatrix
End Sub
The camera drawing is a bit more complex, because it has 2 ways to draw – it can either 'draw' itself as the 'active camera' (i.e. as the viewing transform) or it can draw an icon of itself in the scene (when we are actually viewing from somewhere else). Let's look at the more typical case – drawing itself as the active camera – since this is what a camera is for.
The following code is (a somewhat edited version of) the main drawing routine in the 'CXXX' class:
Public Sub Draw()
Dim m!(0 To 15)
glPushMatrix
'draw camera
Camera.Draw
'draw grids
With gCtl
If m_Grids Then .DrawGrids
End With
'draw model
Model.Draw
glPopMatrix
CheckError
End Sub
The camera matrix will provide the viewing transformation by multiplying the current gl matrix (which at this point happens to be the identity matrix) by its own matrix. This operation is equivalent to calling gluLookAt. If you look at the source for gluLookAt in the Mesa library, you will see that gluLookAt builds a transformation matrix m and then calls glMultMatrixd(m), which is exactly what our camera will do.
The values in the matrix, however, represent the position and orientation of the camera (because we are using the same matrix as the model). These values are the same values as the eye, center, and up values which would be used in a call to gluLookAt. If we were drawing the camera as an object in the scene, we would use this matrix. But we are trying to set the viewing transform, which is the inverse of a modeling transform (see pg. 100 in the Red Book for a discussion). So before we can use the matrix we must invert it. (The Push/Pop stuff is how the Matrix saves and restores its current matrix when making temporary changes to it.)
Public Sub Draw()
Matrix.Push
Matrix.Invert
Matrix.Apply
Matrix.Pop
End Sub
The effect of this is to transform the entire scene (all subsequent commands) by the opposite of the camera position. For example, if we are starting from 0,0,0, and we want the camera to be at 0,0,10, we move the entire scene by 0,0,-10. (The 'camera' is a fiction which just describes this matrix which is placed on the stack before the scene is drawn.)
The camera has a second drawing routine which it uses when it is not active. In this case, it behaves like the model except that its orientation must be reversed to reflect the viewing characteristics of the camera. I did this by just rotating 180 degrees. (This has the side-effect of reversing the sense of the keys, perhaps the Matrix class needs 'reflect' routine for this.)
Public Sub DrawIcon()
glPushMatrix
Matrix.Apply
'draw the box for the camera
glRotatef 180, 0, 1, 0
glCallList ListID
'draw the axes
Matrix.Draw
glPopMatrix
End Sub
In order to be able to see the camera in the scene as an object, the main drawing has a switch to choose between using gluLookAt or the camera to set the viewing transform:
If m_Free Then
gluLookAt 0, 0, 10, _
0, 0, 0, _
0, 1, 0
Else
Camera.Draw
End If
The meaning of the matrix values
In the next image, the model has been rotated around its y axis by 30 degrees. The panel on the left shows the new values of its matrix.
The Matrix class stores the matrix as an array: m!(0 to 15). The panel shows the array as 4 column vectors.
In the array, elements 12, 13, 14 are the x,y,z translation values. In this case, the model's values are 0,0,0 and the cameras are 0,0,5.
The first 3 columns of the matrix describe the axes of the models local coordinate space. If you imagine that the axes are one unit long each (they are actually 2 so that they will be easier to see), then the elements 0,1,2 are the coordinates of the red ball, 4,5,6 are those of the green ball, and 8,9,10 are those of the blue ball. These values are the unit vectors which define the x,y,z axes, in this case:
x – (.866,0,-.5)
y – (0,1,0)
z – (.5,0,.866)
Scaling and rotation affects the upper left 3x3 submatrix. After scaling, the column vectors are no longer of unit length.
That's it. You can check the links below for discussions from people with more expertise.