CS 4391 Spring '97 - Project 1: Transformation and Projection

Due: Monday, April 28, 1997, 2:00 PM


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, line drawing and the viewing transformation. 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. To distinguish our routines from OpenGL, ours will begin with the letters gt to specify the Georgia Tech graphics library. All of the code that you write should be placed in a file called matlib.c. Below is the list of routines that you will create for this assignment.
gtInitialize()
gtPushMatrix()
gtPopMatrix()
The gtInitialize command initializes the matrix stack, which consists of creating a single identity matrix and making it the only matrix on the matrix stack. 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. You matrix stack should allow up to 10 matrices to be stored. You should print an error message if the matrix stack overflows or underflows.

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.

/* all parameters are floats */
gtLookAt(fx, fy, fz, ax, ay, az, ux, uy, uz)
This command creates a view matrix based on the vectors From, At and Up, and it multiplies the current transformation matrix by this view matrix. That is, CTM = CTM * V, where V is the view matrix. The command translates the From = (fx, fy, fz) vector to the origin, it rotates the line segment connecting From to At = (ax, ay, az) onto the negative Z-axis, and it rotates the Up vector = (ux, uy, uz) onto the YZ-plane. We will provide most of this code for you. Our implementation will perform all of the necessary vector cross-products and normalizations and so on, and will produce a 4x4 matrix that specifies the proper transformation. You will only have to modify the code that is provided so that it works with your matrix multiply and matrix stack routines. The sample code for this will be in a file called view.c, and you should incorporate this code in your own source file.

gtPerspective(float fovy, float aspect, 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 fovy is an angle in degrees that describes the vertical field of view. The value aspect describes the ratio of the viewing frustum's width to its height. The near and far values specify the distances along the negative Z-axis at which to perform near and far clipping in Z. The values of near and far should always be positive for the gtPerspective command. We are following the conventions of OpenGL here, but this can be a point of confusion. The command gtPerspective(60, 1, 20, 100) will clip away portions of any lines that are outside of the range (-20, -100) in the Z dimension.

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 clipping distances (not values) along the negative Z-axis, just as in gtPerspective. The values for near and far may be either positive or negative for gtOrtho. Note that negative values indicate clipping planes that are behind the viewer (along the positive Z-axis).

gtBegin()
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. You should draw the lines corresponding to each pair of vertices as soon as you have both vertices. This means that you have no need to store more than two vertices at any time.

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.


Provided Routines

We will provide several routines that will act as a framework upon which you will build your graphics commands. The first of these are the following commands:
gtBeginGraphics(int width, int height)
This routine creates a framebuffer that has width pixels horizontally and height pixels vertically. Other commands will allow you to modify the contents of the framebuffer and write it out to a file.

gtGetFramebufferSize(int *w, int *h)
This routine saves the framebuffer width and height into where w and h point to.

gtClear(int r, int g, int b)
This command sets all the pixels of the framebuffer to the specified color (r, g, b). Red, green and blue values are in the range 0 to 255.

gtWriteFramebuffer(char *filename)
This command writes out the contents of the framebuffer to a file. The format of the image file is PPM, which stands for Portable PixMap. Such PPM files may be viewed using the standard public domain utility xv.

gtEndGraphics()
This command terminates use of the framebuffer, and frees up the associated memory.

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.

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.

All of the above commands will be placed in the file gtGraphics.c, and you should compile and link this file together with your code. As an example of using the above commands, a simple program called square.c will also be provided. This program will create an image file called square.ppm which you can look at by typing xv square.ppm.


Command Interpreter and Test Cases

To make it easier to test out your routines, we will also provide a program that interprets text commands and invokes your drawing routines based on these commands. This program is called linedraw.c and it makes use of the CLI routines. You will find it easy to test your newly-created graphics commands by linking to this program and then reading in various test files that draw different shapes. Here is an example test file:
	clear
	ortho -100 100 -100 100 -10 40
	begin
	vertex 0 0 0
	vertex 50 0 0
	end
If the above file was called simple.cli, you should be able to invoke the linedraw program and then type the following at the prompt:
	linedraw> read simple
	linedraw> write simple.ppm
This will read in the text of simple.cli and interpret the commands, calling the corresponding gt graphics commands. The result should be an image file called simple.ppm that contains a horizontal line segment that starts at the center of the screen and ends midway from the center to the window's right edge. You should view the resulting image by typing xv simple.ppm.

We will be providing several such test files that you can use to test your code.


Recommended Approach

First, become familiar with using the draw_line routine. You can do this by making modifications to the square.c program. 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. This is the right time to add near/far clipping. 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 to write.

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 [3];
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 to explicitly store the 1 that is the fourth element of a homogeneous coordinate of a vertex, in which case your gtVertex definition will consist of four floats instead of three. 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.

Programming tip: You will be writing subroutines that perform operations such as matrix multiply. 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 times 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 into a temporary matrix and then copy the results to the final destination when you are finished.


Submission

The project support files and test cases are in
	~cs4391/proj1/assignment
on College of Computing UNIX machines.

To submit your program, put your project files in a directory on a College of Computing UNIX machine, then execute the following command in that directory:

	shar matlib.c | elm -s "proj1" cs4391@cc.gatech.edu
If you used C++ and modified the makefile or other files we provided: subsitute matlib.c with those file names on the shar command line.

The shar and elm commands are in the /usr/local/bin directory.


Authorship Rules

The code that you turn in must be entirely your own. You are allowed to talk to other members of the class and to the instructor and the TA about high-level questions about the assignment. It is also fine to seek the help of others for general 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, or from any source other than yourself. The only exception to this rule is that you should use the routines that we provide. You should not show your code to other students. If you need help with the assignment, seek the help of the TA. Check the Programing Assignment Policy for details.


Last modified: 16-Apr-97