Assignment 0 - Preliminary Points

Due Wednesday Oct. 9, 2019 at 3:00 PM

Overview

This is a relatively straightforward assignment that introduces some utilities and common functions that you will be using and writing (respectively) for the later assignments in the class. For the most part, this assignment requires only your prerequisite C/C++ knowledge and general programming experience. A small part of it also requires basic knowledge of transformation matrices, which you can quickly review with Section 1 of these lecture notes.

There are four (short) parts to this assignment in addition to an initial part where we ask you to set up your programming environment:

The files for this assignment can be downloaded here. More in-depth details for the assignment will be given in the sections below.


Part 1: Reading basic .obj files (30 points)

One of the most commonly used file formats to store graphics data is the .obj format; the file format is widely used in both research and industry applications.

In this assignment, we look at the most basic form of the .obj format, which contains a list of vertices and a list of triangular faces that together specify the geometry of a 3D model. The coordinate space in which the vertices are specified is called world space, which we will explore in the next assignment. For now, we can simply imagine the 3D model described by the .obj file as a collection of vertices and triangular faces, positioned in some sort of arbitrary coordinate space.

The structure of a basic .obj file is shown below:

v x1 y1 z1  
v x2 y2 z2  
v x3 y3 z3  
...  
v xm ym zm  
f face0v1 face0v2 face0v3  
f face1v1 face1v2 face1v3  
f face2v1 face2v2 face2v3  
...  
f facenv1 facenv2 facenv3

The lines that begin with a v contain vertex data, whereas the lines that begin with a f contain face data. Each “vertex line” starts with a v and follows the v with three floating point numbers: the x, y, and z coordinates (in that order) of a vertex in the described 3D model. Each “face line” starts with a f and follows the f with three integers specifying the three vertices that make up a triangular face in the 3D model. For example, the following line:

f 1 8 37

specifies a face that is made up of the first, eighth, and thirty-seventh specified vertices in the file. Note that this means the vertices are 1-indexed; i.e. the first specified vertex in the file is referred to as the first vertex, rather than the zeroth vertex. This is simply due to convention in the computer graphics world.

Your Task: Part 1

You are to write a program in C++ that takes as input (from the command line) one or more .obj files; stores the vertex and face data from each file into workable C++ data structures; and outputs the data from each file to standard output from the data structures. Please do your work for this part in the Part1 folder of hw0.zip. In addition, we have provided test files within the aforementioned folder.

By “workable C++ data structures,” we mean any sort of representation that allows you to manipulate the data in the program. As an example, we could begin building your representation by having a vertex struct that contains three floats and a face struct that contains three integers. These would represent an individual vertex and face respectively. Furthermore, we can use the built-in C++ vector data structure to represent our lists of vertices and faces. One possible way to handle the 1-indexing for the vertices is to push a null as the zeroth element of the vertex vector and then subsequently push on each vertex in the order they are specified in the file. The face vector would be built similarly, though we do not need to worry about 1-indexing there. We can then have these two vectors be contained within an “object” struct. Each .obj file would therefore have its own corresponding object struct.

Overall, the above-specified representation formats the .obj file data into convenient data structures that we can later access and operate with. Of course, this representation is only an example; you are free to represent the data anyway you want as long as you provide appropriate documentation.

The input format will be as follows:

./my_prog obj_file1.obj obj_file2.obj ... obj_fileN.obj

You may parse the input however you want. For simplicity, we recommend using the built-in C++ libraries, iostream and fstream ([1]), for file reading and the libraries, string, sstream, and cstdlib ([2]and [3]), for splitting up lines and converting tokens to floats/integers. Some of you who may know the parsing languages Flex and Bison ([4]and [5]) might want to use those instead. The linked references for both mentioned methods are also provided in the References section at the bottom of the page.

As a note: we’ve found from previous years that instances of string when using OpenGL will result in a segfault for NVIDIA drivers of version 331. While we are not using OpenGL yet, the whole point of this assignment is to create utility functions that you will reuse for future assignments, including those that require OpenGL. Consequently, for those of you who have NVIDIA driver 331, we highly recommend not using instances of string in your program. There are other methods from ([2]) that don’t explicitly use instances of string; another option is to use the strtok function ([8]). Update as of 2019: This is probably no longer relevant. You can try to check your driver version if you’re worried, but if you’re using a machine from the past several years, you likely have a newer driver. If you don’t even have an NVIDIA card, you definitely don’t have to worry about it.

For the output, we want you to print out the data as it is stored in your data structures. For example, if you have the vertices from a .obj file stored in a vector, then you could iterate through each vertex in the vector and print out the x, y, and z coordinates for each one. This is just a simple test to make sure that your program is storing the data correctly.

Please format the output as follows:

obj_file1:  
 
v x1 y1 z1  
...  
v xm ym zm  
f face0v1 face0v2 face0v3  
...  
f facenv1 facenv2 facenv3  
 
obj_file2:  
 
v x1 y1 z1  
...  
v xm ym zm  
f face0v1 face0v2 face0v3  
...  
f facenv1 facenv2 facenv3  
 
...  
 
obj_fileN:  
 
v x1 y1 z1  
...  
v xm ym zm  
f face0v1 face0v2 face0v3  
...  
f facenv1 facenv2 facenv3

To help you debug, we have provided several simple tetrahedron .obj files in the Part1 folder that you can use to test your program. Because the amount of data in each tetrahedron file is small, it should be fairly easy to check the output of your program by eye. We have also provided two large .obj files–bunny.obj and armadillo.obj–from the Stanford 3D scanning repository. For these bigger .obj files, we recommend piping the output of your program into a file and checking your output with other students. In addition, we have also provided a sample Makefile, which you can use as a basis for your own Makefile.


Part 2: Working with Eigen (20 points)

Throughout this class, we will be using the C++ matrix library, Eigen, to handle our multitude of matrix needs in computer graphics. This is a short exercise to get you used to using this library.

The most updated version of Eigen should be provided in hw0.zip. Alternatively, you can download the most updated version from the main website ([6]), which has the documentation for the library. We have also provided a cheat sheet for commonly used Eigen functions ([7]). Both linked references can be found in the References section at the bottom of the page.

The main data structure from Eigen that we recommend using for this assignment (and for most of the class) is MatrixXd. This is the most flexible of Eigen’s data structures as it alows you to specify a matrix of any arbitrary dimension. The “d” in the name indicates that the matrix will store double-precision values.

As an example, including Eigen/Dense and using the namespace, Eigen, we can declare the 4x4 matrix:

m = 4 11 7 2 0 5 6 7 1 1512 7 13 0 12 10

with the following code:

Matrix4d m;         // m is constructed as a 4x4 matrix
m << 4, 11, 7, 2,   // row1
     0, 5, 6, 7,    // row2
     1, 15, 12, 7,  // row3
     13, 0, 12, 10; // row4

As another example, we can declare a 4x1 “matrix” (i.e. vector), such as:

v = 3.4 1.9 2.4 1

with the following code:

Vector4d v;
m << 3.4, 1.9, 2.4, 1;

Using the namespace, std, we can print to standard output any sort of Eigen matrix, m, with a simple call to cout:

cout << m << endl;

Basic matrix operations can be done as expected with the +, -, and * operators. Syntax for more complicated operations such as the dot product, transpose, and inverse can be found in the provided cheat sheet ([7]) in the References section at the bottom of this page. Additionally, the references provide documentation for Eigen data structures with explicit sizes such as Matrix4d for 4x4 matrices and Vector4d for 4x1 vectors. You may also use these if you feel more comfortable with them.

As a word of caution, operations like transpose and inverse will throw an error if you try to write the output of the operation to the input variable. For instance, the following code:

m = m.inverse();

will throw an error because m is used as both the input and output. We need to instead declare a new matrix for the inverse function to write to, as shown in the following line:

Matrix4d inv_m = m.inverse();

Don’t worry about learning all the Eigen syntax right now. You will gradually pick it up as you do this assignment and the later assignments.

Your Task: Part 2

You are to write a program in C++ that takes as input (from the command line) a single text file that contains a list of translation, rotation, and scaling vectors; creates the corresponding translation, rotation, and scaling matrices for the specified vectors; and outputs to standard output the inverse of the product of all the matrices. Each line of the input file will be in one of the three following formats:

where a line beginning with t indicates a translation vector, a line beginning with r indicates a rotation vector, and a line beginning with s indicates a scaling vector. For computing the product, the transformation matrices should be left-multiplied in the order that their corresponding vectors are specified in the file; e.g. if the file contains transformations A, B, and C in that order, then the product is CBA. Remember that matrix multiplication is associative, so the product CBA can be computed as C(BA) if you find that easier to implement.

Please do your work for this part in the Part2 folder of hw0.zip. We have also provided test files within the aforementioned folder. The length of each test file should be small enough for you to easily check the output of your program with a calculator or any mathematics software. You may also compare your output with that of other students. In addition, we have provided a sample Makefile, which you can use as a basis for your own Makefile.

For those of you who need a review of transformation matrices, please refer to Section 1 of these lecture notes.


Part 3: Putting the previous two programs together (30 points)

For this last part, you will work with our own file format, designed for this class. While the .obj file format nicely specifies data for one 3D model, it is unable to specify data for multiple models. In addition, in computer graphics, we often want to associate a 3D model with a set of transformation matrices that we apply to the vertices of the model, as we will see later in the next assignment. However, we cannot do anything of the sort in a simple .obj file.

There is unfortunately no standard file format that specifies multiple 3D models AND associates each model with a set of transformation matrices. Often, what happens in practice is people design their own file format to contain this desired data. For the purposes of this class, we introduce our own designed file format, which we will add to in the later assignments as we deal with more complicated data. You will find that our file format is fairly intuitive and flexible for general purpose use.

We introduce our file format with the following demonstrative example. All of the following is contained within one text (.txt) file:

object1 object1_filename.obj  
object2 object2_filename.obj  
object3 object3_filename.obj  
 
object1  
t tx ty tz  
r rx ry rz angle_in_radians  
 
object1  
t tx ty tz  
s sx sy sz  
 
object2  
r rx ry rz angle_in_radians  
s sx sy sz  
 
object3  
t tx ty tz  
r rx ry rz angle_in_radians  
s sx sy sz

The idea is as follows. Given the above file, we want to load in the data from object1_filename.obj, object2_filename.obj, and object3_filename.obj into our program and associate each set of data with the labels, ‘object1,” “object2,” and “object3” respectively. From there, we want to create two copies of object1, one copy of object2, and one copy of object3. The vertices of the first copy of object1 are transformed with some translation followed with some rotation. The vertices of the second copy of object1 are transformed with some rotation followed with some scaling. Note how we want different transformations to be applied to the different copies of object1. As for the other objects, our object2 has its vertices rotated, then scaled, while our object3 has its vertices translated, rotated, then scaled.

The general idea is to specify the names of any .obj files we need to handle at the top of the file, and then, for each model we want our program to process, specify the label we associate with the model followed by the set of transformations we want to apply to it.

Your Task: Part 3

Use the code you wrote for Parts 1 and 2 to write a program in C++ that takes as input (from the command line) a single text file with our above-mentiond file format; loads all the data into appropriate data structures; applies all the specified transformations to their respective objects; and outputs, for each object, the transformed vertices to standard output. Please format the output in a manner similar to the following:

object1_copy1  
x1 y1 z1  
...  
xm ym zm  
 
object1_copy2  
x1 y1 z1  
...  
xm ym zm  
 
object2_copy1  
x1 y1 z1  
...  
xm ym zm  
 
object3_copy1  
x1 y1 z1  
...  
xm ym zm

where the above output corresponds to the example file we had above. The vertex coordinates being printed are the transformed coordinates.

Please do your work for this part in the Part3 folder of hw0.zip. We have also provided test files within the aforementioned folder. You will need to create your own Makefile for this part, but it should end up being very similar to the Makefile from Part 2. As a note, test_file5.txt involves bunny.obj, which is a large file; so we recommend piping the output for that test file into a separate file and comparing it with that of other students.

One approach to organizing the data is to have a C++ vector of “object” structs, where each object struct contains three C++ vectors: a vector of vertices, a vector of faces, and a vector of transformation matrices. While parsing the file, you could first create C++ vectors storing the vertex and face data of each involved .obj file, and then make copies of them appropriately when you create individual object structs for each specified object copy. Of course, this is just an example representation; you are free to represent the data however you wish. Just be sure to provide appropriate documentation.


Part 4: The PPM Image Format (20 points)

In the next few assignments, you will need to know how to output an image in the Portable Pixel Map (PPM) format. Before we go into the format, we need to first define the following terms:

The PPM image format itself is a primitive image format that represents an image as an ASCII text file with a .ppm extension. Within the text file is information regarding the image size, maximum pixel intensity, and red, green, and blue (RGB) data for each individual pixel. As one would expect, storing all that information in one text file results in a very inefficient format when considering file size. However, because of the simplicity of text files, it is very easy to write programs to process and generate PPM files. Consider the following example:

P3  
3 4  
255  
255 255 255  
128 128 128  
0 0 0  
255 0 0  
0 255 0  
0 0 255  
255 255 0  
255 0 255  
0 255 255  
128 0 0  
0 128 0  
0 0 128

The first line is a required header for PPM files and is always P3. This line simply indicates that the file is a PPM image and serves no other purpose.

The second line specifies the x and y resolutions of the image in that order. In the example above, the line “3 4” specifies that the image is of size 4x3 pixels. In other words, this image has 4 rows of pixels vertically along the y-direction and 3 columns of pixels horizontally along the x-direction.

The third line indicates the maximum pixel intensity. In the above example, we set the maximum pixel intensity to 255.

Each line after that specifies the RGB values for an individual pixel. The pixels are specified from left to right, top to bottom. For instance, the first line of RGB data (“255 255 255”) specifies the pixel in the first row, first column of the pixel grid. The second line of RGB data (“128 128 128”) specifies the pixel in the first row, second column of the pixel grid. The third line (“0 0 0”) specifies the pixel in the first row, third column. And since the image is of size 4x3, the fourth line (“255 0 0”) specifies the pixel for the second row, first column. And the seventh line (“255 255 0”) specifies the pixel for the third row, first column.

Overall, the example image above displays a small 4-pixels-by-3-pixels image of a grid where:

Your Task: Part 4

Write a program in C++ that outputs a PPM image of a colored circle centered on a different colored background. The input for the program will be read in from standard input as so:

./ppm_test xres yres

where the integers xres and yres are the desired x and y resolutions of the output image. The diameter of the circle should equal half of xres if xres is smaller than or equal to yres or it should equal half of yres if yres is smaller than xres. You may assume that xres and yres will be positive, even integers for this mode of input. You may use whatever two colors you like for the circle image.

As a hint, one approach is to use the inequality for the inside of a circle:

x2 + y2 r2

with (x,y) = (0, 0) being the center of your pixel grid and r being the radius of the circle. Every pixel in your pixel grid whose x and y coordinates satisfy the above inequality would be considered inside the circle.

Your program should output the PPM image to standard output (i.e. use printf or cout).

We recommend using the software, ImageMagick, to the view the image that your program produces. ImageMagick allows you to display your image by piping the standard output of your program into display:

./ppm_test xres yres | display -

Alternatively, you can also save the image as an image file using convert:

./ppm_test xres yres | convert - my_image_name.png

Please do your work for this part in the Part4 folder of hw0.zip. You will need to create your own Makefile for this part, but it should end up being very similar to the ones from the previous parts.


What to Submit

Before submitting, please comment your code clearly and appropriately, making sure to give details regarding any non-trivial parts of your program.

Submit a .zip or .tar.gz file containing all the files that we would need to compile, run, and test your work for parts 1, 2, 3, and 4 in the respective folders. In addition, please submit in the .zip or .tar.gz a README with instructions on how to compile and execute your program as well as any comments and information that you would like us to know.

We ask that you name your .zip or .tar.gz file lastname_firstname_hw0.zip or lastname_firstname_hw0.tar.gz respectively, where you replace the “firstname” and “lastname” fields with your actual first and last name.

Please submit your zip or tar.gz to Canvas in the area for Assignment 0, on the Assignments page there:

To reach Assignments on Canvas, open a new web tab at: https://caltech.instructure.com/courses/1085/assignments


References

[1] http://www.cplusplus.com/doc/tutorial/files/

[2] http://stackoverflow.com/a/236803

[3] http://www.cplusplus.com/reference/cstdlib/

[4] http://aquamentus.com/tut_lexyacc.html

[5] http://ds9a.nl/lex-yacc/cvs/lex-yacc-howto.html

[6] http://eigen.tuxfamily.org/index.php?title=Main_Page

[7] http://eigen.tuxfamily.org/dox/AsciiQuickReference.txt

[8] http://www.cplusplus.com/reference/cstring/strtok/


Written by Kevin (Kevli) Li (Class of 2016).
Links: Home Assignments Contacts Policies Resources