CS 4451, Fall 2003
Project 1: Transformations and Projection
Due: Friday, September 12, 2003, 11:59pm
Objective
The goal of this assignment is to write the routines that allow a user to
transform and view 3D graphical objects. In particular, you will write the
routines for creating line drawings of both orthographic and perspective
scenes. The transformation routines that you create will implement a matrix
stack and will allow you to arbitrarily rotate, translate and scale an
object. To make this assignment easier, routines will be provided for you
that implement line clipping and line drawing. The images that you create
for this exercise will be three dimensional line drawings of scenes with no
hidden surfaces or filled polygons. All of the lines will be uniformly
white so that you will not need to perform color interpolation.
Routines You Will Create
In this assignment you will be creating routines that mimic the behavior of
several OpenGL library routines. Below is the list of routines that you
will create for this assignment.
-
gtInitialize()
-
The gtInitialize command should initialize your matrix stack to have just
a single matrix on the stack. This matrix should be the identity matrix.
-
gtPushMatrix()
gtPopMatrix()
-
The gtPushMatrix command replicates the matrix at the top of the matrix
stack and places this new matrix on top of the stack. This new top matrix
is now the current transformation matrix. The gtPopMatrix command pops the
top matrix off the stack, causing the next matrix down to become the current
transformation matrix. An error message should be printed if a pop is
attempted when only one matrix is on the stack. As described under the
gtInitialize command, the matrix stack is initially created with an identity
matrix as the only matrix on the stack. Your stack only needs to handle up
to 10 matrices on it at any one time.
-
gtTranslate(float tx, float ty, float tz)
-
Multiply the current transformation matrix on the right by a matrix
specifying a translation of (tx, ty, tz). The current transformation matrix
is defined to be the top matrix on the matrix stack.
-
gtScale(float sx, float sy, float sz)
-
Multiply the current transformation matrix on the right by a matrix
specifying a (possibly non-uniform) scaling of (sx, sy, sz).
-
gtRotate(float angle, float ax, float ay, float az)
-
Multiply the current transformation matrix on the right by a matrix that
specifies a rotation of "angle" degrees about the axis (ax, ay, az). The
rotation is counter-clockwise as one looks from the position (ax, ay, az)
towards the origin. For example, the command gtRotate (30.0, 1.0, 0.0, 0.0)
specifies a 30 degree rotation counter- clockwise around the x-axis.
-
gtOrtho(float left, float right, float bottom, float top, float near, float far)
-
Specifies that a parallel projection will be performed on subsequent
vertices. The direction of projection is assumed to be along the z-axis.
The six values passed to this routine describe a box to which all lines will
be clipped. The "left" and "right" values specify the minimum and maximum x
values that will be mapped to the left and right edges of the framebuffer.
The "bottom" and "top" values specify the y values that map to the bottom
and top edges of the framebuffer. The "near" and "far" values specify the
nearest and farthest z values that will be drawn. The eye point is assumed
to be facing the negative z-axis, so the "near" and "far" values actually
define clipping planes along negative z.
-
gtPerspective(float fov, float near, float far)
-
Specifies that a perspective projection will be performed on subsequent
vertices. The center of projection is assumed to be the origin, and the
viewing direction is along the negative z-axis. The value "fov" is an angle
in degrees that describes the field of view. In order to make it easier to
write this routine, we will assume that all screen sizes will be square, so
you don't need to worry about the vertical and horizontal field-of-view
being different. The "near" and "far" values specify the locations along
the negative z-axis at which to perform near and far clipping in z (just as
in the gtOrtho command).
OpenGL uses a separate matrix to do projection that is different than the
current transformation matrix and its associated stack. This means that in
OpenGL, you can specify projections at any time before you draw lines and
polygons. We will do the same for our assignment. Which ever projection
that you specify (gtOrtho or gtPerspective) should be the last
transformation that is applied to the line endpoints, regardless of where
those procedure calls appear with respect to other transformations.
-
gtBegin(GT_LINES)
gtEnd()
gtVertex3f(float x, float y, float z)
-
The gtBegin and gtEnd commands signal the start and end of a list of
endpoints for line segments that are to be drawn. Each call to the routine
gtVertex3f between these two commands specifies a 3D vertex that is a line
endpoint. White lines are drawn between successive odd/even pairs of these
vertices. If, for example, the four vertices v1, v2, v3, v4 are given in
four sequential gtVertex3f commands then two line segments will be drawn,
one between v1 and v2 and another between v3 and v4.
The vertices of the lines are modified in turn by the current transformation
matrix and then by which ever projection was most recently described
(gtOrtho or gtPerspective). Only one of gtOrtho or gtPerspective is in
effect at any one time. These projections do not affect the matrix stack
and the current transformation matrix.
Your gtBegin, gtVertex3f and gtEnd commands must be able to draw any
number of lines. You should draw the lines as soon as both vertices are
given to you (using gtVertex3f), so there is no need to store more than
two vertex positions at any time.
Code Provided
We will provide the following low-level routines that you must use in
order to complete this assignment:
-
gtBeginGraphics (int w, int h);
gtEndGraphics ();
gtWriteFramebuffer (char *filename);
gtClear (int red, int green, int blue);
The above low-level graphics routines draws nothing on the screen,
but instead operates purely on a memory-only version of the framebuffer.
Here are descriptions of each of the provided library routines:
-
gtBeginGraphics (int w, int h);
-
Creates a framebuffer in memory that is w pixels across and h pixels high.
-
gtEndGraphics ();
-
This routine tells the graphics library that you are finished making
graphics calls.
-
gtWriteFramebuffer (char *filename);
-
This routine writes the entire contents of the framebuffer to an image
file. The type of the image file that it writes is known as a PPM file,
which stands for Portable PixMap. This file format is understood by many
image display programs on various platforms.
-
gtClear (int red, int green, int blue);
-
This clears all the pixels in the framebuffer.
In order to use the provided graphics library, you will need to add the
following include line to your C code:
#include "gtGraphics.h"
In addition, you must also link to these library routines. The
source code for these will be provided for you, along with an example
makefile and test cases, in the directory ~turk/public/cs4451/prog1 (for
linux). (Source code for Windows is in the directory
~jang/public/cs4451/prog1/src-win. See the section "Windows vs. Linux"
below for an explanation of differences.)
We provide for you an example program called "drawtest.c" that links to the
gtGraphics routines and that draws a bunch of random lines on the screen.
You should compile and link this program in order to make sure that the
gtGraphics routines are working correctly. There is a second, more
important file called "draw.c". This program tests out the various matrix
manipulation and line drawing routines that you will be creating. In the
directory ~turk/public/cs4451/prog1/results contain images that the "draw"
program should produce once your new commands are working properly.
Two routines will be provided for you that will perform the necessary
clipping and drawing of lines. This means that there is no need for you to
write any clipping or line drawing code. These routines are:
near_far_clip(near, far, x0, y0, z0, x1, y1, z1)
This routine clips the line from (x0, y0, z0) to (x1, y1, z1) to the
specified "near" and "far" clip distances along the z-axis. The "near" and
"far" values are floats. The other six values are pointers to floats so
that they may be altered by the routine. This routine returns a value of
1 if at least part of the line is visible, and 0 if it is entirely outside
the window. DO NOT DRAW THE LINE IF THIS ROUTINE RETURNS 0.
draw_line(float x0, float y0, float x1, float y1)
This routine draws a white line from (x0, y0) to (x1, y1). The coordinates
are 2D pixel coordinates for the currently defined framebuffer. If, for
example, the framebuffer is 100 pixels wide, the left edge of the screen is
x = 0 and the right is x = 100. This routine performs clipping to the
framebuffer window.
What You Will Write
You will write code in at most two files: matlib.c and matlib.h. All of
your routines should be contained in matlib.c, and matlib.h should contain
any header information that you require. We have provided a "dummy" version
of matlib.c that you can use as a starting point for creating your own
complete version.
Suggested Approach
First, become familiar with using the draw_line routine. Second, implement
the matrix stack and the gtTranslate and gtScale commands. Test them out by
applying the current transformation matrix to the line endpoints and then
just draw lines by ignoring the z-values. Third, write the gtOrtho
command. This should be fairly straightforward once you have already drawn
some lines by ignoring the z-values of the transformed vertices. Fourth,
implement the gtPerspective command. The best way to test out this routine
is to carefully work out some simple test cases on paper and match the
execution of your code with these worked-out examples. Finish by
implementing the gtRotate command. Logically this command should be
implemented together with gtScale and gtTranslate, but it is a little
tricker.
You will need to create and manipulate matrices and vertices in order to
implement the transformation routines. Here are possible definitions:
typedef float gtMatrix [4][4];
typedef float gtVertex [4];
Because the last row of a typical transformation matrix is 0 0 0 1, you may
instead choose to use 4 by 3 matrices. You may also decide not to store the
implicit 1 that is the fourth element of a homogeneous coordinate of a
vertex. Two important routines that you will need to write are
matrix-matrix multiplication and matrix-vector multiplication. Your choices
of data structures will affect the details of these routines.
You will probably write routines that perform operations such as matrix
multiply and vector cross-product. It is easy to accidentally write a
routine that clobbers some of the results if the routine is called using the
same matrix more than once. For example, the invocation "mult_matrices (a,
b, b)" is meant to multiply a time b and put the result in b. If you are
not careful, however, you will overwrite part of b before you use all of the
values in that matrix. The best way to avoid this is to place all your
results in a temporary matrix and then copy the results to the final
destination when you are finished.
Authorship Rules
The code that you turn in entirely your own. You are allowed to talk to
other members of the class and to the Professor and the TA about general
implementation of the assignment. It is, for example, perfectly fine to
discuss how one might organize the data for a matrix stack. It is also fine
to seek the help of others for general C or C++ programming questions. You
may not, however, use code that anyone other than yourself has written.
Code that is explicitly not allowed includes code taken from the Web, from
books, from previous assignments or from any source other than yourself.
The only exception to this rule is that you should use the GT Graphics
Library routines and the test code that we provide. You may NOT use other
library routines for matrices and stacks such as STL (for C++). You should
not show your code to other students. Feel free to seek the help of the
Professor and the TA for suggestions about debugging your code.
Development Environment
You may use any environment you wish for developing your code, but it must
compile in Windows (MSVC++ 6.0, not Visual Studio .NET) or in Linux on the
machines in the States Lab. For MSVC++ 6.0, this means providing the project and
workspace files along with the .h and .c source files. For Linux, this
means providing the Makefile along with the source files. This assignment doesn't
use any special or external libraries, so the code ought to be cross platform.
However, there are a couple of reasons why the code is not platform
independent. These are explained below.
Windows vs. Linux
- MSVC++ 6.0 treats "near" and "far" as reserved obsolete keywords, so they
cannot be used as variable identifiers. In the provided source code for
windows (~jang/public/cs4451/prog1/src-win), all "near" and "far" identifiers
have been changed to "near_c" and "far_c". In linux, "near" and "far" may be
used as identifiers without any problems.
- MSVC++ 6.0 and linux employ different functions for generating random numbers.
Getting random numbers in MSVC++ requires srand(), rand(), and RAND_MAX. Linux
uses drand48(). Note that the code provided reflects these differences.
Setup For MSVC++ 6.0
Setting up MSVC++ 6.0 for working on this assignment is fairly simple. The
following procedure is suggested.
- Start MSVC++ 6.0 (not Visual Studio .NET).
- Create a new empty console application. In the menu, click File --> New,
select "Win32 Console Application", type in a Project name, select a directory
location (under which a subdirectory named after the Project name will be
created), and click OK. Select "An empty project" and click Finish, then OK.
- Copy all provided source files into the newly created project directory.
- Add the source files to the project. In the menu, click Project --> Add
To Project, Files.... Select all the .h and .c files except draw.c and click OK.
- Click the FileView tab and expand the list to see the source files.
- Note that once you have confirmed the program works, you will want to remove
drawtest.c from the project and add draw.c. To remove drawtest.c, click on it
in the FileView and press delete.
Viewing .ppm Files
The computers in the States Lab have the right software loaded to view .ppm files.
In WinXP, CNS has installed Maya which has an image viewer that can handle .ppm files.
Fortunately, this is the default viewer associated with the .ppm file type on these
machines and you can simply double-click a .ppm file to view it. In linux, the
appropriate image viewer can be invoked by typing "kview" from the command prompt.
It may also be invoked via the RedHat menu --> Graphics --> More Graphics Applications
--> Image Viewer.
What To Turn In
Create a .zip (not .rar, .arj, .ace, or anything else) of all source files and
any project/workspace files (or the Makefile) necessary for compiling and linking your
program. Name this zip file "p1{lastname}.zip", where {lastname} is your last
name without spaces. For example, Justin Jang would create "p1jang.zip".
If creating .zip files is too hard for you linux users, you may use tar and gzip,
but a .zip file is preferred.
Submit your assignment by email to the TA (jang@cc.gatech.edu). Put "cs4451c: prog1"
in the subject line. In the body of the email, please specify the programming
environment under which the code should be compiled and linked. If you are submitting
tar+gzip file, please provide the command line statements for extracting the content,
e.g. tar xvf ....