Here we will discuss the basics of how to handle dynamic rotation, panning, and zooming in our framework. Events that trigger these actions are caught by the window system interface (GLFWController, in our case) and then passed up to our generic Controller. Since these events usually need to affect the entire scene, the Controller will massage the data a bit and then pass it off to the ModelView class as opposed to any (or all) specific instance(s).
For reasons we shall discuss in class, rotation and pan requests are accumulated into a 4x4 matrix; zoom requests will be implemented by modifying the field of view.
As you study the material on this page, you should refer to the relevant class and instance variable declarations and method implementations in the Controller and ModelView classes in mvcutil/. Note especially the class variables holding viewing and modeling data in class ModelView and the following Controller methods:
When clicking and dragging to perform a rotation or a pan, the model on the screen must "follow" the mouse. This means the operation must be performed in eye coordinates. Similarly (although implemented in a somewhat different way), dynamic zooming must also be done so that the results are intuitive and consistent with the current view.
The user interaction that will trigger a dynamic scene rotation will be a left-click and drag operation. Examine the Controller event handling related to mouse events. You will see a "Left click down" event with no modifier key (e.g., SHIFT key) depressed will let the Controller know that subsequent mouse movements are to be interpreted as rotations. With each incremental movement of the mouse (i.e., pixel by pixel), Controller::handleMouseMotion is called. What follows here is a description of what happens pixel-by-pixel as the mouse is moved with the left button depressed.
It will
be critical for the rotation to "follow" the mouse. Thus, for example, if I click and drag to the right, I want to
rotate about the screen y-axis; if I click and drag down, I want to rotate about the screen x-axis. This is a frequent
source of errors in implementation; note, for example, that if I see a mouse Δx > 0, I want
to rotate about the y-axis by an angle proportional to Δx:
A rotation request will be implemented by creating a 4x4 matrix that encodes the rotation and then concatenating that new matrix onto a matrix that has the accumulation of all prior requests. The order of matrix concatenations is critical. We will use Mdynamic_view on this page to denote the accumulated set of requested transformations. (It will accumulate both rotations and panning operations.) There is a class variable ModelView::dynamic_view that stores this matrix. (See the extracts from the ModelView and Controller class definitions below.)
There are class methods in the cryph utilities that can be used to create the 4x4 matrices needed to perform rotations of various sorts and specified in various ways. (See the "Static Public Member Functions" section of the Matrix4x4 class at the link above.) If we wish to concatenate a matrix corresponding to a rotation about the y-axis of rotY degrees, we do the following (note the order of multiplication):
cryph::Matrix4x4 newRotY = cryph::Matrix4x4::yRotationDegrees(rotY); ModelView::dynamic_view = newRotY * ModelView::dynamic_view;
This is only half the story since there are (at least) two ways to interpret such a dynamic rotation request, depending on whether we are using Viewing Strategy #1 or Viewing Strategy #2:
cryph::Matrix4x4 newRotY = cryph::Matrix4x4::yRotationDegrees(rotY); double d = eye.distanceTo(center); cryph::Matrix4x4 preT = cryph::translation(0, 0, d); // WHY? cryph::Matrix4x4 postT = cryph::translation(0, 0, -d); // WHY? cryph::Matrix4x4 the_newRotY_MatrixIReallyWant = postT * newRotY * preT;
The newRotY matrix computations and related processing are done in response to handling mouse events. ModelView::addToGlobalRotationDegrees is used to handle the events; ModelView::getMatrices is called by your render methods to retrieve the final dynamic view matrix. This separation of responsibilities (event callback versus display update) is especially relevant when using Viewing Strategy #1. Many rotations are accumulated as the mouse moves around, but we must actually do the pre- and post-translation only on the final accumulated rotation.
Of course there are other "viewing strategies" one might envision that would affect this processing as well. Nevertheless, let's just show representative implementation excerpts from these two methods that would occur for our two viewing strategies:
// PART 1 in ModelView::addToGlobalRotationDegrees cryph::Matrix4x4 newRotY = cryph::Matrix4x4::yRotationDegrees(rotY); dynamic_view = newRotY * dynamic_view; // Note the order!
Then:
// PART 2 in ModelView::getMatrices double d = eye.distanceTo(center); cryph::Matrix4x4 preT = cryph::translation(0, 0, d); // WHY? cryph::Matrix4x4 postT = cryph::translation(0, 0, -d); // WHY? cryph::Matrix4x4 M_ECu = cryph::Matrix4x4::lookAt(eye, center, up); mc_ec = postT * dynamic_view * preT * M_ECu;
// PART 1 in ModelView::addToGlobalRotationDegrees cryph::Matrix4x4 newRotY = cryph::Matrix4x4::yRotationDegrees(rotY); dynamic_view = newRotY * dynamic_view; // Note the order!
Then:
// PART 2 in ModelView::getMatrices cryph::Matrix4x4 M_ECu = cryph::Matrix4x4::lookAt(eye, center, up); mc_ec = dynamic_view * M_ECu;
The ModelView class does not know which viewing strategy you used when specifying eye, center, and up, so it must be told explicitly what to do when processing dynamic rotations. You can use the ModelView class method setDynamicRotationsAboutCenter to specify this. By default (hence if you do not use this method), ModelView will assume you are using Viewing Strategy #1, and it will apply the pre- and post-translation. If you are using Viewing Strategy #2, call ModelView::setDynamicRotationsAboutCenter(false); (typically once from the set3DViewingInformation function in main.c++).
The user interaction that will trigger a scene pan operation will be a Shift-left-click and drag. Examine the Controller event handling related to mouse events. You will see a "Left click down" event with the SHIFT key depressed will let the Controller know that subsequent mouse movements are to be interpreted as panning the scene. With each incremental movement of the mouse (i.e., pixel by pixel), Controller::handleMouseMotion is called. Each incremental motion will result in a call to ModelView::addToGlobalPan. That routine will update the dynamic view matrix in a manner analogous to what we saw with rotations. It will then be used in getMatrices, again, just as we saw with rotations.
The provided implementation of ModelView::addToGlobalPan will be adequate for our purposes. It does the following. Be sure you understand all of this as there may be homework and/or exam questions related to this.
The scroll wheel will be used to zoom in and out. Specifically, scroll wheel up causes the view to zoom in (i.e., objects in view get larger); scroll wheel down causes the view to zoom out (i.e., objects in view get smaller). A scroll wheel event is detected by the GLFW or GLUT Controller subclass which then passes it to the Controller::handleScroll method shown above. That method simply calls ModelView::scaleGlobalZoom to scale a global zoom factor that is used by ModelView::compute2DScaleTrtans for 2D applications or ModelView::getMatrices to alter the xy dimensions of the view frustum for 3D applications.
As with the rest of the material on this page, be sure you completely understand this process.
If you want to augment or change mouse handling behavior (or any other event handling behavior), you can just override other GLFWController or Controller methods in your Controller subclass.