Programming assignment 3 - .obj File Loader/viewer
CS 354 - Introduction to Computer Graphics
Fall, 2015
Don Fussell
Note additional requirement below
DUE Tuesday Oct 13 at 11:59pm
In this assignment, you'll expand on your work in Assignment 2 to
become more familiar with OpenGL and scene descriptions by creating
your own 3D model viewer. Your system will read input files in
.obj format, apply transforms that you specify interactively,
and render the results. You'll also implement more sophisticated
camera control than in the previous assignment.
Here are some objects that you should be able
to parse and display along with code for parsing them. You may also
be able to parse and render other objects in this format that you find
on the web, but at a minimum you must be able to handle the ones we're
giving you.
.obj files contain a rather simple ASCII format that's very
easy to parse. Design your user interface to allow interactively
selecting files to be read into your scene.
Object loader
Each line of an .obj file contains a
record of some sort. The first character of the line indicates what
the type of record is, and thus what to do with the rest of the
line. The only ones you need to implement are:
- # (any text): Comments. Ignore the rest of the line.
- v x y z: Vertex data. The x y z values are the
vertex’s position in 3D. Each vertex is implicitly numbered as it's
added, starting from 1.
- f n0 n1 n2...: Face data. The n values indicate which
vertices to connect together to form the face. Each n is an index
into the list of vertices read in so far. There can be an arbitrary
number of vertices that make up a face; there are usually just 3
(triangles), but there's no guarantee. If you encounter a face that
has more than 3 vertices, split it into multiple triangles as if it
were a triangle fan.
There are several other record types that you'll encounter, but you're
free to ignore them. Once you've loaded the model, compute normals
for both faces and vertices. Assume counter-clockwise winding for the
face normals, then average the incident face normals to produce
normals for each vertex. Note that the parser in loader.h
assumes a Trimesh object with methods for adding vertices and faces is
provided in geom.h (not provided). Such an object maintains a
list of vertex positions and defines the face mesh using indices into
this vertex list. Designing this will be one significant part of this
assignment. You are free to modify loader.h as you see fit
to accomplish the project.
Camera control
Being able to move the camera around is crucial when trying to examine
an object, so the second part of this assignment is to add orbit-style
camera control. In this type of camera control, there's a point of
interest that the camera is looking at. The camera can orbit around
the point, zoom in and out, and move the point around by panning.
Implement these controls with the following mouse operations:
- Left click & drag: Orbit around the point of interest.
- Middle click & drag: Pan the object (move the point of interest
in the camera's X/Y plane).
- Right click & drag: Zoom in/out on the point of interest.
Make sure that you set the camera controls to sensible defaults when
starting up! The initial point of interest should be the center of the
object, and the zoom level should comfortably fit the object in the
window. Your camera must do perspective projection, not orthonormal
projection. As before, you're free to use the built-in OpenGL
transformation and perspective functions to implement your camera; you
don't need to do any of the matrix math yourself. You are also
allowed to use gluLookat as before if you choose.
Rendering
In addition to the camera, you will also implement several rendering
modes.
Implement the following list of rendering options:
- Point mode: Just draw the vertices.
- Wireframe mode: Draw the edges of the mesh.
- Solid mode: Draw filled triangles.
- Shaded mode: Basic lighting, see below.
- Face normals: Add a toggle to display the face normals.
- Vertex normals: Add a toggle for the vertex normals as well.
As you'll see, solid mode is not very useful; all it displays is a
solid block of pixels in the outline of the object. To make the
display a little more sensible, you'll implement a shaded mode as
well. For shaded mode, you can use OpenGL's default
lighting. The following code snippet shows you how to do this:
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
// draw triangles here
// use glNormal3f() to submit a vertex normal with each vertex
glDisable(GL_LIGHTING);
Interactive control
You must implement a REPL style interactive command based control
system for all these features except the camera control. Using
keyboard commands through the terminal (stdin for instance), you
should be able to manage the process of reading in, transforming, and
deleting objects. The command loop should output a prompt to the
keyboard, await a newline-terminated text string command, parse and
execute the command, and return to command mode with a prompt.
The commands you must be able to handle include:
- L <objectfilename>: Load and display the contents of the
specified .obj file (assume the .obj extension if it isn't
explicitly typed into the name). Remember the contents as the
current object.
- D: Delete the current object and remove it from the
display. Make the previous current object be the current object
(i.e. pop the object id stack to get a new current object).
- I: Load the identity matrix as the modeling transform.
Note that this should not change the viewing transform, i.e. you
shouldn't just load identity into the MODELVIEW matrix. It should,
however, neutralize the effects of any existing modeling transforms.
- T <tx> <ty> <tz>: Add a translation by the specified
parameters to the current modeling transformation.
- S <sx> <sy> <sz>: Add a scale by the specified
parameters to the current modeling transformation.
- R <theta> <ax> <ay> <az>: Add a rotation
counterclockwise by angle theta about the specified axis vector
(note that the axis vector should not be required to be a unit
vector) to the current modeling transformation.
- V: Subsequent transformation parameters are interpreted
as being in the viewer's local coordinate system.
- W: Subsequent transformation parameters are interpreted
as being in the world coordinate system.
There are a few things worth noting here. First, you aren't meant to
type the angle brackets in the commands, they just indicate the
presence of a parameter. The objectfilename is a character
string, all the other parameters are floating point numbers.
Next, the notion of a current object needs to be maintained, and
because you can add or delete objects at any time, you'll need to
maintain a stack of objects to keep track of which one is current.
You'll push an object onto that stack when you execute an L
command, and you'll pop an object off the stack when you execute a
D command. Of course, this means you'll need to keep around
internal representations of objects to redisplay them for as long as
they're resident.
Note that the transformations added to the modeling transform
should be applied to the objects in the order they are specified. So
you aren't just postmultiplying a new matrix to the MODELVIEW matrix
each time, since that would cause the effects of the transforms to be
in the opposite order they were specified.
None of the transforms that were specified before an object is loaded
should be applied to that object; all of the transforms that are
specified after it is loaded should apply to it.
The V and W commands allow you to get very different
effects by specifying the same transformation parameters. This spec
stays in effect until it is changed. You should be in V mode
if you want the effects to be relative to your viewing position (so
for instance rotation commands about primary axes will produce pitch,
yaw and roll effects, and translations will do panning and zooming,
but of course these are being done conceptually by moving objects, not
by changing your view). In W mode, you move objects with
respect to a fixed world coordinate system, the coordinates in which
the vertices of the objects as they are read in are specified.
Additional Requirement
As described in class, you must also implement a command line
switch that allows an initial command file to be loaded when the
program is first run. This switch will be of the form -f
filename where the filename is the full filename of the command
file to be input. This file will contain a sequence of the commands
described above just as you would input them from the keyboard and
your program will parse and execute them just as if they had been
entered from the keyboard before any interactive input is processed.
The file should be optional, if the switch is not given, no initial
file is processed.
Extra credit
As you will presumably notice doing it, there are many ways to enhance
this project if you're so inclined. Here are some suggestions:
- Name the objects and manage them by name.
So you could delete them by name. Or you could allow
more than one instance of an object to be viewed by adding an object
reference command to your list that can be used like a load command.
- Animation: You could add a looping capability to your command
set to specify a set of command within the loop to be executed
repetitively. This would allow you to create animations in your scene.
- Light objects: Add commands to put light sources in the scene.
- Material files: One of the optional parameters in an .obj file is
a material file, which contains color and lighting information for
the materials used in the mesh. You could read out that information
and feed it to OpenGL's lighting system to see the object as
designed.
- Other camera controls: Orbit cameras are great for looking at an
object, but you are probably familiar with various other first and
third person camera control systems from games. Feel free to try
some of these.
- Camera inertia: Some users find it more natural if the camera
keeps rotating around an object after they've released the mouse, as
if they had spun a globe. If you implement such functionality, make
sure to slow down the rotation gradually over time, so the object
eventually stops spinning.
Notes:
- You should use your project 2 code as your starter code, along
with the parser code in the handout. You need not keep the Menger
Cube around.
- As usual, you'll turn this in using Canvas. Also as usual, you
can develop your application on whatever operating system you like,
but before you submit it, you must make sure that it builds and runs
properly on the department Linux machines! All grading will take
place on these machines, so if your code doesn't work on them,
you're in trouble.
Make sure that all the necessary code is submitted, as projects that
don't build are worth nothing! Make sure you have included your
name and UTCS ID in a comment at the top of each of your
files. Also, include a readme explaining the usage of your program,
including any menu options or keyboard commands. If you use any slip
days, be sure to put that in your readme and email the TA as well.
To get a grade on this project, you'll need to sign up for demo
session with the TA after you've submitted your code. This is when
you'll run through all the functionality in your project, and the TA
will be able to verify that everything is working as it should. This
is also your chance to show off your extra features and slick
interface!
Last modified: 10/08/15
by Don Fussell
fussell@cs.utexas.edu