|
CS 428 - Fall 2003 Project 1: Adventures in Transformation
Due electronically: Monday, October 13, 11:59 pm (EDT) |
|
Goal
To become adept at manipulating the 3-D world through matrix transformations. You will become familiar with the OpenGL transformation system, and cement your understanding of the significance of the order in which transformations are applied to vertices.
Description
This project will introduce you to the OpenGL transformation pipeline. The task will be to provide two different visualizations of a scene; in one perspective, the view will show a coloured cube whose position, scale, and orientation can be set by user controlled parameters. In the second perspective, as well as displaying the cube, the view frustum of the camera from the first perspective will be drawn.
Program
You will be provided with skeleton code for this program. You will need to fill in various methods in Cube.java, Camera.java, and Trackball.java.
The skeleton code for this project can be found on the cereal machines
in the directory:
~tedmunds/cs428/proj1
Also included is a Makefile and
a README file describing the code structure and how to
compile and run the program.
Handing in
We will be using an online handin program as described on the machines page. The project name for this assignment is "Project 1". Note that you can only hand in under this project name if you hand in before the deadline. After the deadline, there will be another project listed - "Project 1 - LATE" to which you will be able to hand in in the same way. If you hand in to BOTH "Project 1" and "Project 1 - LATE", your on-time assignment ("Project 1") will be IGNORED.
What to hand in?
As mentioned on the machines page, you will be handing in a single file - an archive of all the files that you need to hand in. This file should contain:
Drawing the Cube
The first thing to do is to get some pixels on the screen (this will make it
possible to discern whether anything else you do works properly). In the class
Cube, there is a method called drawCube(). In this
method, you need to write the code to draw the 6 coloured squares that make up
the cube. The cube should be centred at the origin and have edges of length
1.0. The sides should be coloured as follows:
positive X face: red
negative X face: cyan
positive Y face: blue
negative Y face: yellow
positive Z face: green
negative Z face: magenta
Until you've written the transformation code, you'll only be able to see the green side.
Viewing
There are two cameras in our setup; the world camera, and the view camera. Each has its own camera transformation. However, the world camera can "see" the view camera's view-volume. Both cameras have a corresonding 3D viewing window into the world. Each of these windows maintains a separate OpenGL matrix stack, viewport, projection, etc... Let's refer to the left window in the above image as the world view, and the right window in the above image as the camera view.
Recall that the view transformation may seem backwards. It says how to move the world in front of the camera, not how to position the camera in the world.
The viewed object (a cube) will also be drawn with respect to its own object coordinate system.
In the Cube class, there is a method called modelTransformation(). This is where you you will apply the Model part of the ModelView transformation to position the cube in the world coordinate system.
You'll be using the parameters contained within the Cube class (which are controlled by the sliders on the user interface) to control the transformations that are applied. Remember that the cube rotations (and scaling) should be applied in the cube's local frame.
In the Camera class, there are methods called projectionTransformation() and viewTransformation(). These are the methods that control how the world is brought in front of the camera and squashed onto the image plane:
Transformations
There are many ways of specifying the position and orientation of an object. For this assignment, we'll be using two different mechanisms, one to position the cube, and another to position the cameras.
The cube will be positioned absolutely with respect to the world origin, and its orientation will be described using three Euler angles: rotation amounts around the X, Y and Z axes.
The rotation around the X-axis (called pitch) is applied first, followed by the rotation around the Y-axis (called yaw), followed by the rotation around the Z-axis (called roll). This is then followed by a translation of the cube to its position.
Specifically, this means that the resulting matrix is: Translate x Rotate_roll x Rotate_yaw x Rotate_pitch x Scale
On the other hand, the camera will be positioned using the virtual trackball (see below). This means that rather than constructing the absolute transformation each time the camera is positioned, the transformation will be iteratively built. Given that CURRENT is the current transformation, and INCREMENT is the transformation that will move the currently displayed world to the desired display, the camera's view transformation is: INCREMENT x CURRENT
View volume
In the Camera class, there are the same modelTransformation() and draw() methods as were seen in the Cube class. These are for the view volume visualization in the left-hand window.
You need to complete the draw() function to draw a view volume (as a bunch of lines) using OpenGL. Make sure that you draw the frustum in such a way that it is easy to distinguish the near plane from the far plane.
When Camera.draw() is called to draw the view volume, there needs to be the appropriate Model transformation on the stack. This is accomplished by a call to Camera.modelTransformation() (already made for you). You need to fill in this method to apply the transformation that positions the camera in the world frame (note that this is the "opposite" of the transformation in Camera.viewTransformation() that positioned the world in front of the camera.
Since the camera is controlled by a trackball (see below), the Model transformation is not built from scratch every time it is applied (the way the cube's was). Instead, it will be built from a combination of the existing Model transformation (CURRENT - not the same as the CURRENT used in Camera.viewTransformation()) and an incremental transformation (INCREMENT - not the same as the INCREMENT used in Camera.viewTransformation()).
Because of the nature of the trackball interface, the incremental transformation will be most easily expressed in the camera's frame (rather than the world). This means that INCREMENT will be the transformation that moves the camera from its current position to the desired position IN THE CAMERA'S FRAME. Thus the desired transformation is: CURRENT x INCREMENT
Bear in mind that you don't have to do things this way. You can apply the transformations in any order you want; they'll just be different - and possibly more difficult to compute - transformations.
Trackball
One of the most common user interface mechanisms for interacting with two dimensional representations of a three dimensional environment is the virtual trackball (or a real trackball, if you have one lying around). You could use the same slider interface that is used to control the cube's position and orientation to control the camera, but a more intuitive interface is to allow the user to rotate (and pan and zoom) the camera using the mouse.
If you imagine a sphere sticking out of your monitor and centred on the centre of the canvas whose camera is being controlled, then you can use the mouse to "grab" a point on the surface of the sphere and rotate the sphere by moving the mouse to another point. The rotation of this virtual trackball can then be mapped to the rotation of an object (or in our case, the rotation of the scene seen by the camera).
The first problem is to map the mouse's position to a position on the virtual trackball. In the Trackball class, you need to fill in the method computeTrackballLocation() to perform this mapping. The mapping is made possible by the geometry of a sphere. For a hemisphere sitting on the z=0 plane and centred at the origin, for any point on the z=0 plane that is within the hemisphere's radius of the origin, there is a single point on the hemisphere that has the same x and y coordinates as the point on the plane.
Thus, for a point (x,y) where x^2+y^2 <= radius^2, you can determine the
corresponding point on the hemisphere from the equation of a sphere:
x^2 + y^2 + z^2 = radius^2
However, this mapping only applies for points that are inside the sphere. For points outside the sphere, you need to make sure that you don't cause numerical errors by treating them cavalierly. Since these outside points are a problem, it makes sense to have the virtual sphere be larger than the window on which the mouse manipulates the trackball. Make use of Trackball.trackballDiameter to scale your virtual trackball.
Note that even if your virtual trackball is larger than the window, you are still not absolved of responsibility for sensibly handling points outside the sphere - the windowing system allows you to drag your mouse outside the window in which you pressed the mouse button down, so it is still possible to reach coordinates that are outside the virtual sphere.
When the (left) mouse button is pressed, the trackball coordinates will be computed (by a call to computeTrackballLocation() and stored in rotationStartVector. As the user drags the mouse around, calls will be made to rotateCamera(); you need to fill in this method to compute the rotation necessary to move from the coordinates stored in rotationStartVector to the current trackball coordinates, and based on that rotation, update the camera's incremental model and view transformations (through setPendingModelRotation() and setPendingViewRotation()). The rotations that you send will depend on how you interpret the rotations in Camera.viewTransformation() and Camera.modelTransformation().
The rotation of the camera should be about a point Camera.trackballFocalDistance along the camera's current view direction. Consider carefully what sequence of transformations is necessary to accomplish this.
Vector math
In computing the trackball coordinates, you might find it necessary to do some basic vector operations (subtraction, cross product, etc...). The javax.vecmath package has been installed, and its documentation is available online. It's part of the Java3D API.
Don't write code that implements vector operations (add, subtract, cross
product, ...); use these classes. Note that certain operations (like cross
product) only are defined for vectors, not for points. If you can't figure out
how to use this stuff, ask! We'll be using it on the remaining assignments.
Extensions
The following is a list of optional extensions (some are required if you are a CS graduate student).
Come up with an alternative solution that preserves the aspect ratio, doesn't shrink the view too much (it must to some degree, however), and avoids this problem. (It may not be clear what all this means, until you have written the program, and have tried resizing the drawing windows a little.)
You will also need to modify Camera.viewTransformation() and Camera.modelTransformation() to handle translations as well as rotations.
Note that in the same way that the camera rotation has a particular distance from the camera at which the rotation is applied, the panning has a distance (the same distance) at which object translation in the image should have the same magnitude as the translation of the mouse (this will be clearer once you have any magnitude of panning, and feel that it is too much or too little).
Hints