As an example of manipulating the ModelView matrix in OpenGL, we are going to construct a program that allows the user to rotate a collection of objects interactively by clicking and dragging with the mouse. The full source code for this example is available here.
Imagine that some collection of objects we would like to draw to the screen is encased in an invisible sphere. When the user clicks in the display window, we can imagine that they are clicking on the surface of the sphere. Further, when the user clicks on the sphere and drags the mouse to a new location on the sphere they are specifying a rotation that should carry the starting point over to the ending point. What we would like to do is to translate information about where the drag began and ended into a rotation with an axis of rotation and an angle of rotation.
The first problem is to map a point in the window to a point on the surface of some sphere. In this example, the window used has size 480 by 480. We will set up an imaginary coordinate system in this window whose origin is at the center of the window. The imaginary sphere will be centered at that origin.
When the user presses the mouse button our mouse handler function will get called with the position of the mouse in window coordinates. We first map from window coordinates to 'centered' coordinates by doing
oldX = x-240; oldY = 240 - y; // convert from window coordinates
The equation for a sphere in three dimensions with radius 240 centered at the origin is
x2 + y2 + z2 = 2402
Now that we know the x and y coordinates we want we can compute z:
oldZ = sqrt(240*240-oldX*oldX-oldY*oldY);
When the user releases the mouse button, a similar set of calculations will determine the location of the end point on the imaginary sphere.
How do you translate two points on the surface of a sphere into a rotation? First, make vectors pointing from the center of the sphere out to the points. Then, compute the cross product of those two vectors. The cross product vector is the axis of rotation.
float uX = newY*oldZ-newZ*oldY; float uY = newZ*oldX-newX*oldZ; float uZ = newX*oldY-newY*oldX;
Further, this cross product vector can tell us the angle of rotation. The length of the cross product equals the the product of the lengths of the two vectors times the sine of the angle between them. Since both of the original vectors sat on the surface of a sphere with radius 240 we know their lengths. Since all the lengths are now known, we can solve for the angle:
float angle = asin(sqrt(uX*uX+uY*uY+uZ*uZ)/(240*240))*180/3.14159265;
Since OpenGL wants angles specified in degrees and the arcsin function returns a result in radians, we have to convert this angle from radians to degrees.
To get the effect of rotating the collection of objects we have to modify the base ModelView matrix that is used as the starting point for all subsequent transformations that put the objects in place. Usually, this base transformation is the identity transformation:
Setting the ModelView matrix to include the desired rotation could be done with the usual glRotated function call. However, the code that computes the details of the rotation is in the mouse handler function while the code that applies the transformation is in the drawing function. To get the necessary transformation matrix from one place to another we will have to ask OpenGL to give us a copy of the ModelView matrix. This is not very difficult to do. The copy of the transformation matrix will live in a global array called the current transform array.
Because the ModelView matrix is a 4 by 4 matrix we need an array of size 16 to store its entries.
Once we have computed the details of the rotation we will load our CT matrix into the ModelView matrix, do the rotation, and then get a copy of the resulting matrix.
glMatrixMode(GL_MODELVIEW); glLoadMatrixf(CT); glRotated(angle,uX,uY,uZ); glGetFloatv(GL_MODELVIEW_MATRIX,CT);
The call to glMatrixMode is needed at the outset here because OpenGL maintains a number of matrices behind the scene. Before we carry out any manipulation that may affect a matrix, we have to use glMatrixMode to specify which matrix we want to work with.
At the top of the function that does the actual drawing, we put in a couple of lines to load our pre-computed rotation matrix.