|
CS 428 - Fall 2011 Project 1: 3D Viewing
Due electronically: |
|
Description
It's often tricky to place a camera in a Computer Graphics environment, since you often can't see what you're doing, except by looking "through the lens". If the viewed objects are tiny, you can often spend a lot of time searching through empty space.
One solution to this (found in modeling and rendering software) is to provide a second view, which shows the camera in the scene. This is alongside a camera view of the objects. Above, is a side-by-side view of a camera's view volume, with the resulting view on the right.
Another solution is to simply provide the ability to point the camera at a particular (selected) object. But even with this, it can still be hard to get the view you want without any visual feedback of where the camera is, relative to the viewed object (other than the "through the lens" view).
Objective
This project will help you understand the geometry of 3D viewing, and the transformations needed to implement it. You will also learn how to do viewing and transformations in OpenGL, use the matrix stack, as well as how to use the OpenGL clipping planes.
Program
You will be provided with skeleton code for this program, which supplies the necessary user interface and main program structure. You must develop the code for the actual viewing, transformations and OpenGL. You will basically be filling in the missing parts in functions (which are marked with "// ...") and replacing parts that are labeled as temporary code. In its current state, the program doesn't do much. Note: you should remove any temporary code you encounter, once you start that relevant part.
The skeleton code for this project can be found (on the iLab machines)
in the directory
~decarlo/428/proj1
Also included is a Makefile
and a README.txt file describing the code structure and how
to compile and run the program. Note that this program uses the
vecmath library (unlike homework 1), so if you didn't set
this up already, then you'll need to do this now.
Handing in
Hand in the following:
You will need to create a tar archive to hand in:
tar cf handin.tar Makefile *.java descrip.txtHere are the instructions on how to hand in this file (same as in homework 1).
This assignment is a bit vague so that you can make a lot of the design decisions yourself. There are several approaches to the problems here. Use your best judgement to try and get the most useful result. Ask for help or clarification when you need it.
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.
The viewed object (a cube) will also be drawn with respect to its own object coordinate system using a modeling transformation. A method for drawing the cube is in the class Cube.
To summarize, there are two cameras -- each with its own projection. And there are three coordinate systems -- one for each of the cameras (one for the world view, one for the camera view), and one for the viewed object (the cube). The transformations for the cameras are called viewing transformations. These three transformations are stored using the classes World, Camera and Obj. Additional information about the drawing state is also stored here (such as whether the camera is orthographic or perspective). Each of these static classes have accessor methods to retrieve the current transformation parameters. For instance, World.tx() is the x-axis component of the translation.
In the classes WorldView and CameraView, there are methods called draw(), projection() and transformation(). All of the code you will write will be either in these methods, or called directly from them. For viewing:
In these functions, you will update the projection matrix (not the model view matrix); you'll use glMatrixMode to ensure this. You must use either glFrustum or glOrtho here. No other method of setting the projection (such as gluPerspective) will be accepted. Also, be sure to preserve the aspect ratios to eliminate viewport distortion when the window is resized (when you get this working, the cube in the window should appear square when viewed head on, no matter how you resize the window). Basically, the aspect ratio of the view frustrum must equal the aspect ratio of the window. There is some temporary code in CameraView.projection() that does this now, so it will be easier for you to get the program working.
The values and ranges of the scrollbars in the user interface are
chosen to best work when the front of your view volume is about 2
units wide (from -1 to 1, for example), such as in the first
assignment, where the call was like:
gl.glOrtho(-1.0, 1.0, -1.0, 1.0, near,
far).
Of course, you'll have to adjust for the aspect
ratio.
Keep the range close to [-1,1] as the initial camera position, as well as the slider ranges were chosen to accomodate this range. If you use a larger range (such as [-width,width] which can be in the hundreds, as width is in pixels), then your cube will appear as a tiny speck.
Here are two example views of the world and camera windows (in each, world is on the left, camera on the right):
|
Perspective view |
|
Orthographic view |
The other two functions are:
Think carefully about
where to place the view-volume in the world coordinate system (you
will probably use one of those transformation diagrams with the
arrows, as you saw in class ; here
it is). This is not easy to figure out, so don't be shy about
asking for help.
Also take a look at the class SimpleGLCanvas. You'll notice that the current window width and height are stored (for use in projection()). Also, the screen is cleared for you, and the current OpenGL error status is checked (if you perform any invalid OpenGL operations, a message will be displayed if you ran the program with the -debug switch).
Transformations
There are many ways of specifying the position of a camera. For this assignment, the camera will be positioned using the "position", but the orientation of the camera is 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 camera to its position.
Specifically, this means that the resulting matrix for the view
camera is:

This transformation (with different parameters, with W superscripts) is the same the world camera transformation W.
This is the definition of V. It seems backwards: it describes the transformation from the canonical camera to the world. Don't start adding in spurious minus signs in this formula to "fix" it because you think it seems backwards: it's supposed to be this way!
There are several benefits to this order. With the translation happening last, it is more intuitive to the user, since it is not affected by the rotation. Once you get this working, briefly try putting the translation first, and see what it's like (try it when the roll is 180 degrees). For the same reason, it is worth having the roll after the pitch and yaw, so that they remain more axis-aligned. However, the pitch does affect the yaw -- this sort of thing can't be completely avoided with ordered rotations.
These rotations are in sequence -- the X rotation will move the Y and Z axes of the object; and Y rotation will also move the Z axis. You should be rotating around the transformed axes (which are still the Y and Z axes in the transformed frame). This means that once you perform any X rotation at all, your Y and Z rotation will no longer be rotating around the Y and Z axes of the object, but those of the containing coordinate system. The solution here is like what we described in class, and ends up being about the simplest thing you can do. (By the way -- when you get this working, you can see the effect of "Gimbal lock" by setting your Y rotation to 90 degrees, and notice how both the X and Z rotations become redundant for this value! Try it for 80 degrees, too. Turns out this isn't just a problem for CG; look here if you're curious.)
For the object transformation M, the order of application is
basically the same, with scaling applied first:

View volume
In the class ViewVolume, draw the view volume (for the world camera) using lines to show its boundaries. In the examples here, the front (near) plane of the view volume is drawn with blue lines, with a thick blue line on top. This helps determine the orientation of the volume. Make sure you draw your view volume in a way that reveals which is the front and top (which can be hard to tell in a perspective view, at times).
You need to write a function to draw a view volume (as a bunch of lines) using OpenGL. Then, when you call this function, you need to have the appropriate transformation on the matrix stack. Look in the textbook, or at the manpages for glOrtho and glFrustum for a description of where the view volume actually is, for these calls. You will need certain values that are recorded in Camera from CameraView.projection().
Clipping
As you know, the primitives drawn in the camera view window are clipped: geometry outside of the view volume is discarded. The sides of the view volume represent the sides of the window, but there is also the front and back of the view volume as well (near and far planes) -- that's six planes in total.
In order to illustrate whether an object is inside the view volume, you will be drawing clipped geometry in the world window, as in the following:
|
Cube entirely inside view volume: no clipping |
|
Cube partially clipped |
|
Cube entirely outside: completely clipped |
Basically, you must set up six clipping planes: one for each of the view volume sides. See the review of OpenGL clipping for how to do this in OpenGL.
Drawing options
The user interfaces allows for the specification of whether the world window has its geometry clipped or not (the value of which is determined using the World.isClipping() method). Be sure only to clip the geometry in the world window when this is true.
Also in the user interface, the user can choose between perspective and orthographic cameras. You can determine the current choice using the Camera.isPerspective() method. When the user makes this choice, CameraView.projection() is called so you can update the camera viewing projection, and WorldView.draw() is called so you can draw the updated view volume. It's up to you to use the correct projection for the camera window, and also that the view volume has the right shape.
Vector math
In computing the location of the clipping planes (used in drawing the view volume), 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 on the course web page. It's part of the Java3D API.
For this assignment, you'll probably use the Vector3d and Point3d classes.
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.
This package is already installed on iLab and properly configured in the course setup script. If you followed the instructions on the lab info page for setting things up, then you have already installed vecmath.jar (and added it to your classpath).
Bug
The world and camera view windows each have their own redraw callback function. In order for the program to work properly, the camera view callback should be called first, since the window coordinates there must be conveyed to the Camera class by Camera.setCurrentView. Otherwise, the view volume cannot be drawn properly. However, the order windows are redrawn has changed with the JDK version, and also seems to depend on the platform. Unfortunately, we haven't set aside the time to figure out how to address this properly. (If you happen to know, drop us an email!)
If they are drawn incorrectly on your system, then the easily observed behavior is that the view volume will be drawn using the previous camera window size (which is not set yet when the program starts up, and changes whenever the window is resized). Most obvious is that it view volume will be missing on the first redraw, and out-of-date whenever the window is resized. But after that (such as when adjusting the sliders), everything should look fine.
If you observe this behavior, please flip the value of View.camera_window_added_first and see if that fixes the problem. (The code is set up so the bug does not occur in the iLab, at least as far as we have observed.)
Extensions
The following is a list of optional extensions (required if you are a CS graduate student).
Come up with a way of doing this that makes clever and careful use of the OpenGL clipping planes, so that you only see the wireframe lines outside the view volume. One property that your solution should have, is that if you decide to draw the wireframe inside the view volume, and polygons outside, the change to your code should be trivial.
The cube drawing method in Cube already has an argument that determines whether the cube is drawn as polygons or as wireframe.
Implement a view of the normalized device coordinates (exactly how you do this is up to you). Make sure your clipping planes and wireframe cube still work, too! You'll know when you get it right since when you draw the view volume, it will turn into a cube without any changes to your code to draw the view volume.
Note that the parameter normalized is already in the World class; just set the value of View.normalized_extension to true to unhide the checkbox.
Hints