In this assignment, undergraduates will explore shape representations in graphics by implementing a program for basic mesh transformations. In particular, you will use a matrix-based framework to transform the vertices of a mesh using affine transformations.

Objectives

This assignment is designed to teach students concepts with shape representation in computer graphics. Specifically, students will:

  • Understand triangle mesh file formats, specifically by implementing a basic file parser that reads and writes files the Wavefront .obj format.
  • Understand triangle mesh data structures, implemented through a data structure that stores triangle meshes internally so that one can apply transformations to the mesh.

Part 1: Triangle Mesh File Formats

Your code will implement a basic reader and writer for Wavefront .obj files, as discussed in class. You need only to support reading lines that begin with v (for a mesh vertex), f (for a mesh face) and comments (that start with #).

Lines that are vertices will follow the form v x y z, where x, y, and z are three floats representing the \((x,y,z)\) position of the mesh vertex.

Lines that are faces will follow the form f v1 v2 ... vk where v1, v2, through vk are integer indices that refer to specific mesh vertices \(\{v_1, v_2, \ldots, v_k\}\). Note that this indices begin counting with 1 instead of 0! Also, while the .obj standard supports polygonal faces of arbitrary vertices, your code need only support loading triangles (and thus you can assume that \(k = 3\). You can either skip or report an error if an .obj file has more than 3 vertices for a given face.

You should also implement methods to write the .obj file from your internal data structure, so as to save the updated mesh after you have smoothed it.

Part 2: Storing Triangle Meshes in Memory

After parsing the .obj file, you should create an internal data structure to store the triangle mesh. I recommend doing this with a Mesh class. If your previous implementations included it, you may also want to consider making this class a subset of your abstract Surface from A03/A04. But, in this assignment you will not be required to render triangle surfaces using your own code, so the functionality for processing ray hits will not be necessary.

Regardless of what data structure you use for storing the mesh, you will find that using your class to store three-dimensional vectors from will be necessary here to store the vertex positions. For storing mesh topology, you may want to consider a variety of different data structures as we discussed in class (i.e. separated triangles, triangle-neighbor, or halfedge). You may pick any of the data structures in class, but please document in the README which data structure you used.

Your Mesh class must provide two functions as an interface for the renderer, getVertList() and getFaceList(). The first function, getVertList() returns a Float32Array of size \(3n_v\) where \(n_v\) is the number of vertices in the mesh. This array is a flattened list of the vertex positions, i.e. \((x_0,y_0,z_0,x_1,y_1,z_1,\ldots)\). The second function, getFaceList() returns a Uint32Array() of size \(3n_t\) where \(n_t\) is the number of triangles in the mesh. This array is a flattened list of the integer indices for the triangles, i.e. \((v_1^0,v_2^0,v_3^0,v_1^1,v_2^1,v_3^1,\ldots)\).

Part 3: Mesh Transformation

After storing the mesh in memory, you should provide the user functionality to transform the mesh using a sequence of transformations that you encode as matrix based operation. To do so, you should let the user interactively enter a sequence of operations. Your code must support the following commands:

  • Translation, specified as values t_x, t_y, and t_z, where \((t_x,t_y,t_z)\) are floats for the translation in each of coordinate axes.

  • Rotation, specified as r_x, r_y, r_z, and theta, where \((r_x,r_y,r_z)\) is an arbitrary vector to rotate around and \(\theta\) is the CCW angle of rotation.

  • Scaling, specified as s_x, s_y, and s_z, where \(s_x\), \(s_y\), and \(s_z\) are floats for the amount to scale by in each of the coordinate axes.

You are welcome to let the user specify this with any interface you want, in my test code I simply used sliders for all 10 of the parameters above.

After reading all of the user’s parameters, you should construct the \(4\times4\) matrix that expresses a combined sequence of transformations. Specifically, you should create three separate matrices, one each for the translation, rotation, and scaling operations. You should then construct the sequence of applying the scaling, followed by the rotation, followed by the translation.

After computing this combined matrix, you should transform all vertices of the input mesh using this sequence, treating each vertex as a 4-dimensional homogeneous coordinate with \(w=1\). You should only have to transform all vertices once for any sequence of transformations.

I recommend coding all of this by implementing a \(4\times4\) Matrix class that includes a function for matrix-matrix multiplication as well as a function that takes as input a three-dimensional vector and returns the three-dimensional vector that is the result of promoting to a four-dimensional vector (with \(w=1\)), multiplying by the matrix, and then projecting back to three-dimensions.

You may also want to add static functions to the Matrix class for computing the above three matrices for each transformation. For computing these, you will find it handle to create matrix operations for computing an identify matrix and a transpose matrix. But otherwise, you won’t need many other functions in this class just yet.

Part 4: Execution, Display, and Parameters

In the default repository, I have provided both a sample HTML file with a default glcanvas, and a sample Javascript file gl-rasterizer.js that includes rendering functionality with webGL.

Your code needs to provide a Mesh class with the functions Mesh.getVertList() and Mesh.getFaceList() as described above. You will pass this mesh to the webgl renderer in two places.

Upon reading and storing the mesh, you should make one call to the function startRender() and pass it the mesh. This will initialize the GL canvas and create buffers for rendering the mesh object.

For transformation, I recommend creating two instances of the mesh, one which is the initial mesh and a second which is the transformed mesh. Upon setting parameters for the transformation, you should update the mesh information with the renderer. To do this, you will call updateMesh(), again passing it the mesh that contains the newly create mesh vertices.

If the base repository for the assignment, I’ve provided a sample a05.js that shows how startRender() and updateMesh() might be used. You’ll also see an example of a very stripped down Mesh object that I create inline to demonstrate the interface. I expect that you will remove almost all of this code in your final submission.

For testing, we have included a variety of sample .obj files in your repository, but you may want to try out models you can find online too. Note that you may discover than many freely available meshes are not watertight manifolds, which may cause undesirable effects when building your data structure.

Finally, as the rasterizer only provides a static view, you may find it useful to test with an external program that your meshes are correct. I recommend using the freely available, open-source, cross platform program, MeshLab to display your triangle meshes. There are also a variety of online .obj viewers that work in your browser, but you might not find them as easy to use.

Part 5: Written Questions

Please answer the following written questions. You are not required to typeset these questions in any particular format, but you may want to take the opportunity to include images (either photographed hand-drawings or produced using an image editing tool).

These questions are both intended to provide you additional material to consider the conceptual aspects of the course as well as to provide sample questions in a similar format to the questions on the midterm and final exam. Most questions should able to be answered in 100 words or less of text.

Please create a separate directory in your repo called written and post all files (text answers and written) to this directory. Recall that the written component is due BEFORE the programming component.

  1. Briefly describe your implementation design choices for your 4x4 matrix class. Considerations can include (1) ease of writing mathematical expressions, (2) performance, (3) utility for converting from other types and constructing.

  2. Exercise 6.8 on pg. 137-138 of the textbook.

  3. Exercise 12.1 on pg. 321 of the textbook.

  4. How do triangle strips help store a mesh more efficiently?

  5. Exercise 6.6 on pg. 137 of the textbook.

Grading

Deductions

Reason Value
Program crashes due to bugs -10 each bug at grader's discretion to fix


Point Breakdown of Features

Requirement Value
Consistent modular coding style 10
External documentation (README.md) 5
Class documentation, Internal documentation (Block and Inline). Wherever applicable / for all files 15
Expected output / behavior based on the assignment specification, including

Parsing the .obj file and storing it in a mesh data structure10
Displaying the transformed mesh using the provided rasterizer10
Allowing the user to vary parameters for the transformations15
Correctly computing (separately) translation, rotation, and scaling matrices15
Using the combined transformation matrix to transform vertex positions10
Writing transformed mesh as an .obj output file10

70
Total 100