**Meshes** [A Graphics Codex Programming Project](../projects/index.html) ![Figure [teaser]: A scene composed of solids of revolution. Make objects like this in 100 lines of code!](teaser.jpg border=1) Introduction ============================================================================================ In this project, you'll learn to create models like those in Figure [teaser] in about 100 lines of code. You'll extend your solution to the [Cubes project](../cubes/index.html) or the [G3D starter project](https://casual-effects.com/g3d/G3D10/samples/starter/) to create indexed face meshes by procedural generation. These meshes are the mainstay of modern 3D graphics. Prerequisites --------------------------------------------------------------------------------------------- - Complete the [Cubes](../cubes/index.html) project - Read in the [Graphics Codex](http://graphicscodex.com): - A Model of Light - Surface Geometry Educational Goals --------------------------------------------------------------------------------------------- - Learn to think topologically about meshes - Gain expertise in working with file formats - Design a simple mesh abstraction for procedural geometry - Master some common trigonometric and modular arithmetic idioms from graphics - Experiment informally with materials and lighting - Improve workflow and project time management File Formats -------------------------------------------------------------------------------------------- Data files are interchange formats that allow the use of different tools for creating and processing data. They allow the same input to be used with many programs, and are a way of connecting programs to each other. By working with standard data file formats, we can increase the number of programs that our own can interact with and gain access to large repositories of information. The structure of an interchange format often differs from the in-memory data structure that it maps to, because a file has different requirements for space and time complexity of operations. You'll continue to use the G3D `.Scene.Any` file format that you learned about on the Cubes project for scenes. In this project, you'll go beyond loading the stock 3D models from those scenes. Instead, you'll generate your own meshes and save them in a standard interchange format. There are many 3D file formats, and many of those are quite complex. The "Simple 3D File Formats" topic in the _Graphics Codex_ describes some of the easier ones to manipulate. In this project, you'll work with an obscure standard format: the [Geomview ASCII Object File Format](http://www.geomview.org/docs/html/OFF.html) (OFF). It has the advantage of being really simple to generate...and G3D can already load it. Later you'll work with OBJ and FBX files, which are more mainstream. Rules for this Project ------------------------------------------------- During this project, you are not permitted to directly invoke the following classes and methods or look at their source code: `G3D::Welder`, `G3D::MeshBuilder`, `G3D::MeshAlg`, and `G3D::ArticulatedModel::loadHeightfield`. You may read their documentation and reproduce their functionality with your own implementations if you wish. You are not permitted to leverage `G3D::ArticulatedModel`'s ability to directly load an image as a heightfield when specified in a scene file, or to use `G3D::HeightfieldModel`. You _may_ look at the G3D Procedural Geometry sample program. Beware that it uses different data structures. Specification =============================================================================== Implement the following by extending the Cubes project. For all shapes, there should be no colocated vertices. For the meshes, you may use arbitrary indexed convex-polygon faces that G3D will automatically tessellate into triangles on load. _This project's report requires you to plan your approach before you begin writing code, so read the whole document before beginning implementation._ 1. Create `data-files/model/cube.off`, an axis-aligned cube with unit edge lengths, centered at the origin. You may make this file in a text editor directly, or write a procedure to generate it. 2. Create a procedure (function or method) named `makeCylinder` that generates a file `data-files/model/cylinder.off` given a radius and height. This file should be an indexed convex-polygon mesh for a cylinder about the $y$-axis that is centered at the origin. 3. Create a user interface for invoking `makeCylinder`. Use number boxes for the parameters and a button labelled "Generate" to actually launch the execution. Display a message on screen while generating and then flush the [`G3D::ArticulatedModel`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_articulated_model.html) cache, and reload the current scene on completion. 4. Write a procedure that generates a regular heightfield in `data-files/model/heightfield.off` with elevation given by the grayscale values of an image file. Use a file dialog to select the file, a number box to scale the $x$ and $z$ integer coordinates by, and a number box to set the scale that 1.0 corresponds to for height. Make a button labelled "Generate" that launches the generator. Display a message on screen while generating and then flush the `G3D::ArticulatedModel` cache, and reload the current scene on completion. 5. Write a procedure that generates a drinking glass from a contour by creating a solid of revolution and saves it as `data-files/model/glass.off`. You may make a user interface for the contour parameters, but are not required to. Make a number box that selects the number of rotational slices and a button labelled "Generate" that launches the generator. Display a message on screen while generating and then flush the `G3D::ArticulatedModel` cache, and reload the current scene on completion. 1. The glass must have nonzero thickness for the bowl part that contains the liquid. It cannot have any "exposed backfaces". 2. The glass must have a stem or thick base that is not penetrated by an inner well (i.e., you can't just double all surfaces). 3. The glass must be geometrically *closed* and *water tight* (i.e., it should topologically be a sphere) 6. Create a visually interesting `data-files/scene/demo.Scene.Any` scene by combining the elements from your program. Apply materials and lighting in the `.Scene.Any` file. Report ========================================================================== 1. Describe the main algorithms of your program, using pseudocode or diagrams as appropriate. 2. Summarize your implementation design in no more than two paragraphs. Provide sufficient information to aid future implementors seeking to reproduce your work. Why did you make the program this way? What were your main iteration structures? What were the key G3D routines that you used? 3. Demonstrate that your program is functioning correctly by showing clear pictures of each of the shapes with the wireframe visible. For the heightfield, show the source image as well. Examples are given below. 4. Show one or more images of your demo scene (without wireframe). Describe it in a single paragraph, indicating both what it is and how you approached the construction. You must feature a mesh generated by your program, but can augment the scene with other models if you wish. 5. Answer these questions in at most three sentences each: 1. G3D uses some real-time rendering tricks to fake reflections of the sky, but if you fly along the shore of my island, you'll see that the water doesn't actually reflect the mountains. How could you simulate the image of the missing reflection in a plane of water or a mirror using only nonreflective 3D models? 2. The underlying renderer converts all models into triangles, which you can see in the wireframe renderings. Why are triangle meshes the preferred primitive compared to, say, quadrilaterals, which would be more space efficient and are often easier for modeling? 3. The regular tessellation we used for heightfields is inefficient. It will allocate the same number of triangles for flat portions of the heightfield that need few triangles as for very hilly ones that need many to accurately represent the shape. Propose an alternative algorithm for computing a more efficient triangular tessellation for a given heightfield. Do not actually implement your algorithm. You may invent one on your own or use external resources to discover existing algorithms. 6. Describe what you learned while working on this project. Distinguish software engineering, algorithmic, and mathematical skills. Include the reading and ideas that you had to implicitly work through even if not required. 7. *Workflow* 1. Make a table planning major tasks for this project and then recording your planned and actual time on them, as show in Table [tbl:workflow]. Each task should be between 0.5 and 1.5 planned hours. Roll up smaller ones and break down longer ones. Break each task directly corresponding to a deliverable into three versions: a complete first draft, a minimum viable product, and the polished version. Infrastructure tasks that are necessary precursors for others can remain as a single version. 2. How long did the project take you, from start to finish (excluding time spent reading, including time writing the report)? Distinguish time to the minimum viable product vs. polishing time. Report this as two numbers, in hours. Task | Description |Planned Time(h)| Actual Time ---------------------|------------------------------------------------------------|--------------:|------------: Report Draft | Formatted Markdeep document with placeholder text. | 0.50 | 0.25 `makeCylinder` Draft | Empty function with correct signature connected to GUI. | 0.25 | 0.10 `makeCylinder` MVP | `for` loops generating all correct vertices and possibly-buggy faces. | 0.25 | 0.50 Report MVP | Probably-correct answers and quick screenshots. | 0.50 | 0.50 `makeCylinder` Polish| 100% correct and add GUI for number of slices. | 0.25 | 0.30 Report Polish | Tweak table layout, rewrite question#5 answers from scratch, grammar and spelling pass, take more demo scene shots and Photoshop contrast on them. | 0.50 | 0.75 ... | ... | ... | ... [Table [tbl:workflow]: Sample project planning table.] ## Correctness Examples ![Cube](cube.jpg width=155 border=1) ![Cylinder](cylinder.jpg width=155 border=1) ![Heightfield](heightfield.jpg width=155 border=1) ![Drinking glass](wineglass.jpg width=155 border=1) ## Demo Examples ![Gothenburg, Sweden ([source](http://www.liedman.net/2014/06/25/sunshine/))](gothenburg.jpg height=100 border=1) ![Artificial terrain](island.jpg height=100 border=1) ![Table setting](table.jpg height=100 border=1) Advice ===================================================================================== ## How to use this section The advice sections of these projects are (intentionally) written in a way that won't make much sense to you before you've started coding. I intend you to do the reading from the books, start on the project, and then get stuck about 90 minutes in to the project. How do you know when you're stuck? Easy: tasks start taking longer than you estimated. For the first project, tasks probably took longer than expected because you were just getting accustomed to the tools. But from now on, when something is taking a long time to implement or debug, it is almost always because you're not really sure what the code is supposed to do. When you're stuck--that is when you read the Advice section. The problem that you're stuck on is usually a problem that everyone encounters when completing this project, including me. The advice here will make sense in the context of that problem. It is a record of what I had to figure out when completing it myself and what I've told students to help them on similar projects. If you have a problem and find something in the advice section that helps, you should feel good about yourself. That is a sign that you're on the right track. You're following the same path that people who successfully completed the project were on and hit the same roadblocks along the way. After reading the Advice section, you may still be unsure how to proceed with your code. Or, none of the advice may seem relevant. In this case, you need to go back to the reading. Rarely can anyone understand technical writing on the first pass. The second time through a chapter, you'll spot all kinds of information that you then appreciate. For example, plan to reread the C++ chapter of the Graphics Codex in the middle of every project. Workflow ------------------------------------------------------------------------------------- The _first_ task I took on for this project was writing the report. That's right...I started with the _last_ piece of the document. I did that because writing the report helped me to understand what I was tackling. It is easier to make images and objects if you know how they'll be used. Attempting to answer the questions before creating the implementation will also help you to think about the project in the right way. I don't expect you to be able to answer them beforehand, but trying to is very valuable. Track the amount of code that you're writing versus my reference solution. If your solution starts to get much longer than mine, consider whether an alternative design would reduce the amount of code that you need to write (and debug, and later maintain...) My solution contained about 160 source lines of code in `.cpp` and `.h` files. I measured this by running Find in Files in Visual Studio with the search string "`;`" in the `source` directory and then looking at the bottom of the results for the count. About 60 lines of that were from the G3D Starter project after I'd stripped out unnecessary parts for this project. The remaining 100 lines were my solution, including the GUI code and some debugging and testing code. I also created six `.Scene.Any` files for testing and rendering results and ten OFF files. For every image that I produced and test that I ran, I retained a scene file that had the exact object and camera positions. This allowed me to easily go back and reproduce the results. My journal was about 300 lines long and included about 20 images. In addition to documenting successes, every bug that I encountered was documented in the journal, including of course the solution to fixing it. This helps me when I encounter similar bugs on later projects, or if I'm handing off a project to another team member later. When you make your planning schedule, ensure that you're working in iterative passes. Complete all "draft" tasks before moving on to any "MVP" ones, and then complete those before "polish". G3D APIs ------------------------------------------------------------------------------------- To write text files, you can use [`G3D::TextOutput`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_text_output.html), [`fprintf`](http://www.cplusplus.com/reference/cstdio/fprintf/), or [`std::cout`](http://www.cplusplus.com/reference/iostream/cout/) to write to the file. I prefer the `TextOutput` because it generalizes to indenting and has a matching `TextInput` for parsing. For convenience, directly add your user interface to the [`G3D::GApp::debugPane`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_g_app.html#a682d93c21b914a28f6baeb4b6270d1a6) instead of making a new window. See the following: - [`G3D::GuiPane`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_file_dialog.html#aa6faec311dce2ec6faa7ded11d654068) - [`G3D::GApp::drawMessage`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_g_app.html#a1d796dc185b9528b900d936a8b537f8d) - [`G3D::msgBox`](http://casual-effects.com/g3d/G3D10/build/manual/namespace_g3_d.html#afa98304f088797b023e37167c507373c) - [`G3D::FileDialog::getFilename`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_file_dialog.html#aa6faec311dce2ec6faa7ded11d654068) Use `G3D::GuiPane` to create your [`G3D::GuiNumberBox`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_gui_number_box.html)es and [`G3D::GuiButton`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_gui_button.html)s. You can use [`G3D::Image`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_image.html) to load the image for the heightfield. Since this is your first time using G3D's GUI system, I've provided an example of how to use it in Listing [gui]. The overall design is easy to use: - Add controls to `G3D::GuiPane`s - Pass pointers to the underlying data that you want the control to modify - Use C++ lambda expressions for callbacks on buttons. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // inside App::makeGUI: GuiPane* heightfieldPane = debugPane->addPane("Heightfield"); heightfieldPane->setNewChildSize(240); heightfieldPane->addNumberBox("Max Y", &m_heightfieldYScale, "m", GuiTheme::LOG_SLIDER, 0.0f, 100.0f)->setUnitsSize(30); heightfieldPane->addNumberBox("XZ Scale", &m_heightfieldXZScale, "m/px", GuiTheme::LOG_SLIDER, 0.001f, 10.0f)->setUnitsSize(30); heightfieldPane->beginRow(); { heightfieldPane->addTextBox("Input Image", &m_heightfieldSource)->setWidth(210); heightfieldPane->addButton("...", [this]() { FileDialog::getFilename(m_heightfieldSource, "png", false); })->setWidth(30); } heightfieldPane->endRow(); heightfieldPane->addButton("Generate", [this](){ shared_ptr image; try { image = Image::fromFile(m_heightfieldSource); // ...insert your heightfield generation code here... } catch (...) { msgBox("Unable to load the image.", m_heightfieldSource); } }); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [gui]: Sample C++ GUI code in G3D, using lambda callbacks] The result of executing the code from Listing [gui] looks like Figure [fig:gui]. ![Figure [fig:gui]: User interface for the heightfield](gui.png) If you're new to C++, remember to pass `shared_ptr`, `String`, `Array`, and any other large class by reference or `const` reference, e.g., `const Array< int >& index`. Directly initialize large objects as well, rather than assigning to them: `TextOutput to("myfile.off");`, not `TextOutput to = TextOutput("myfile.off");`. Markdeep ------------------------------------------------------------------------------------ Markdeep's [diagram features](https://casual-effects.com/markdeep/features.md.html#toc1.9) are very helpful for this project. I created diagrams in the report first, showing how the patterns worked in terms of my loop variables. For example: ****************************** * i + (j-1)*10 * * * * ⋯----------*---------⋯ * * / \ * * / \ * * / \ * * / \ * * / \ * * / \ * * / \ * * / \ * * ⋯-*-----------------*-⋯ * * * * i + j*10 i+1 + j*10 * ****************************** (that's not a real pattern from this lab). I then implemented my algorithms based on the diagrams once I could see the patterns. Structure your report using sections that match the major bullet points and lists or subsections for the minor points. Make links from your report to the generated documentation. When read, your report will be in the `build/doc-files` directory. Scene Files ------------------------------------------------------------------------------------ I made one `.Scene.Any` file for each shape so that I could easily test them as they were generated. I created these files by modifying the `test.Scene.Any` file that is provided with the starter project. Use relative paths so that your program will work on any computer. For example, specify the model for the cube as `model/cube.off` and _not_ something like `C:\brk3\cubes\data-files\model\cube.off`. Remember that you can reload scenes without restarting your program. Just modify the scene file in a text editor and press the reload button within your G3D app. This saves time re-launching the project. You can also drag and drop new scenes that aren't listed in the scene editor dropdown. I adjusted the lighting from the original test scene to Listing [lighting]. You can probably guess out what most of the options do. The least obvious one is `shadowMapBias`, which avoids patterns of dark splotches called "shadow acne". ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sun = Light { bulbPower = Power3(2e6,1.7e6,1.4e6); shadowMapBias = 0.01; track = lookAt(Point3(-90, 200, 40), Point3(0, 0, 0)); shadowMapSize = Vector2int16(4096, 4096); shadowMapBias = 0.02; spotHalfAngleDegrees = 7; spotSquare = true; type = "SPOT"; }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [lighting]: Adjusted sun that I used for my pictures.] Once you start generating your own OFF model files, you'll find that some of them can be quite large. It is particularly easy to produce heightfields that are tens of megabytes. You can reduce the disk footprint by storing them inside zipfiles. As a rule of thumb, ASCII-based 3D models such as OFF and OBJ formats usually compress at a ratio of about 5:1. All G3D routines can directly load files inside of zipfiles without first unzipping them. (You can access this functionality for your own code in later projects using [`G3D::BinaryInput`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_binary_input.html) and [`G3D::TextInput`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_text_input.html).) If you have a directory named `model` that contains a file `heightfield.zip` that contains a file named `berkshire.off`, then just refer to it as `model/heightfield.zip/berkshire.off` in your `.Scene.Any` file and it will load correctly. As in the Cubes project, you can experiment with assigning materials to objects to make your scenes more interesting. To make the wineglass transparent, I used the code from Listing [material]. If you know a bit about the physics of optics, then you'll notice some strange values in that material. For example, the index of refraction is way too low and it glows slightly. These are to compensate for the approximations used by the real-time renderer, which make the glasses look too dark and refract too severely. In the upcoming Path Tracing project you'll build a more realistic offline renderer that produces more realistic images from more realistic data. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ glassModel = ArticulatedModel::Specification { filename = "model/wineglass.off"; scale = 0.3; preprocess = { setMaterial(all(), UniversalMaterial::Specification { lambertian = Color3(0.02); glossy = Color4(Color3(0.3), 0.8); transmissive = Color3(0.9, 1.0, 1.0); emissive = Color3(0.01); etaReflect = 1.0; etaTransmit = 1.03; }); }; }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [material]: Realtime material for the transmissive wineglass.] To produce the image of the island with multiple colors, I generated painted four heightfield source images in Photoshop. I then produced different heightfield meshes for sand, grass, mountain, and snow. The scene file loads all of these to the same location and applies different materials to them. I then put a translucent (and nonrefractive) water plane down using a scaled-up `square.ifs`. I adjusted the sun and ambient occlusion settings to make the shape of the hills stand out clearly. Algorithms ------------------------------------------------------------------------------------ The order of the algorithms in the specification is intentional. It guides you through the shapes in a way that builds skills. Making the tetrahedron and then the cube helps you to learn the file format. The cylinder takes the repetition of the cube and forces you to explicitly recognize the symmetry. I used triangles on the top and bottom and quadrilaterals ("quads") on the sides. The heightfield is actually easier than the cylinder in some sense, because it has uniform tessellation in $x$ and $z$...it is like the sides of the cylinder, without the special cases for the "top" and "bottom". I made my heightfield a little fancy by bringing the sides down to the ground at the edges to make the image cleaner...that's not in the spec, so you don't have to do that. The drinking glass seems very challenging at first. But it is just a generalization of the cylinder, extending the "sides" to have arbitrary displacements. In fact, when you've finished the project, you'll realize that the cube and cylinder can also be generated by the same code. However, I don't recommend solving the project that way because first implementing the cube and cylinder explicitly will teach you skills that make the glass easier. The referenced G3D sample Procedural Geometry program creates geometry directly into a runtime `ArticulatedModel`. I found it easier for this project to instead create my own data structures for storing the vertex and face arrays. I didn't use `G3D::Polygon`, which is intended for nonindexed polygons, but made my own `Poly` [`typedef`](http://en.cppreference.com/w/cpp/language/typedef) from a [`G3D::SmallArray`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_small_array.html), and then abstracted the index and vertex arrays and saving logic into a `Mesh` class. The basic logic behind the main loops looks the same as in the sample project, however. The simplest way to generate a heightfield from an image is to produce quads, which will be converted into triangles by a regular pattern on load. That's what I did. A slightly better approach is to find the length of each 3D diagonal across the quad and explicitly split it into triangles so that the inserted edge is the shortest. Recall that the points on a unit circle in 2D are given by $(\cos \theta, \sin \theta)$. From this, you can derive the counter-clockwise winding (from above) points in 3D around the $y$-axis. Point $P_i$, the ith of $n$ equally-spaced points on a circle parallel to the $xz$ plane, is given by: $$ P_i = r \cdot \left(-\sin \theta_i, y, \cos \theta_i \right) $$ where $r$ is the radius of the circle and $$\theta_i = \frac{2 \pi i}{n}.$$ This is the core equation that you need for generating both the solid of revolution and the cylinder, since in each case there are circles of vertices about the $y$-axis. When iterating on a circle, the modulo operator `%` is often handy. The idiom in Listing [mod] gives the indices in a "circular array" of a point and its neighbor. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for (int i = 0; i < n; ++i) { const int thisIndex = i; const int nextIndex = (i + 1) % n; ... } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [mod]: Using modular arithmetic on indices] There are many ways to create the silhouette of the glass. You _could_ simply guess coordinates and type them in by hand. I think that would take a lot of trial and error to make something nice. You might instead draw the glass silhouette on graph paper and then transcribe the coordinates. The way that I created my glass was to draw Figure [silhouette] in Photoshop and then extend my program using that image at runtime. If you want to use a similar approach, I'll leave you with just that hint and you can figure out what the colors signify and how to write the code to use them. (You can't use my image, either...draw your own!) ![Figure [silhouette]: An image I used in computing the drinking glass](silhouette.png border=1 height="180px") Thinking Topologically ------------------------------------------------------------------------------------ There's an instructive joke that, to a topologist, a doughnut and a coffee mug are the same shape: a torus. This is funny to a scientist because mathematicians stereotypically drink a lot of coffee (and doughnuts go well with coffee). It is instructive because it is true, and memorable. The *geometry* of a surface describes its exact shape. The *topology* of a 2D surface in 3D space describes the connectedness of the points on it, independent of how it is deformed. This is important for 3D graphics in part because meshes animate, but often don't change how they are connected in the process. For a mesh, we can limit consideration of the points on the surface to the vertices because every other point is on a face between vertices. So, a mesh is a graph in which the nodes are the vertices and the graph edges are the edges of the faces. A mesh is *closed* and *watertight* if every edge is between two faces. For a closed mesh, an interesting topological property is its genus, which is just the number of holes around which the closed shape is wrapped. Holes are interesting because no matter how you deform the vertices, the holes will always exist. ![Morphing between a coffee
mug and a doughnut.
[Public Domain image by Lucas V. Barbosa](https://commons.wikimedia.org/w/index.php?curid=1236079)](morph.gif) Consider the mesh of a torus (e.g., a doughnut). It has one hole...as does a coffee mug. This means that we can transform one into the other, at least, as long as there are enough triangles to have some flexibility. Take the doughnut (imagining that it is made of very stretchy rubber) and pull out one side without ripping it. You now have a doughnut that is fat on one side. Push in on the fat lobe without pushing one side through the other and there's a dent. That dent becomes the bowl of the coffee mug. This long digression brings us to an essential point for completing the project at hand: a cube, a cylinder, and a drinking glass are all topologically equivalent to a sphere. _You can produce a cube, a cylinder, and a drinking glass using the exact same code_. All that changes is the number of vertical and angular slices, and the offsets (in this case, $y$ and radius) of the vertices. If you're having trouble with the drinking glass code, consider that it is just a generalization of the cylinder code (using the mesh from the heightfield.) Debugging ------------------------------------------------------------------------------------ This is our first project with somewhat in-depth code, and presents a good opportunity to discuss debugging strategies. I liberally used [`G3D::debugAssert`](http://casual-effects.com/g3d/G3D10/build/manual/debug_assert_8h.html#adb3cef18983da8440d55b7557f83358c), [`G3D::debugAssertM`](http://casual-effects.com/g3d/G3D10/build/manual/debug_assert_8h.html#af7e8ca29ffa4af20db0de5a93e1b81e9), and [`G3D::alwaysAssertM`](http://casual-effects.com/g3d/G3D10/build/manual/debug_assert_8h.html#ac765dd93625b827ed4487eaa30a7c532) throughout my code. I sought to programmatically verify every assumption made. I tend to run my programs in optimized, release mode by default. It is faster, which is helpful for optimizing workflow. As we start using larger data files and running more complex algorithms, this will be essential for completing projects in a timely manner. The caveat to running an optimized build is that as soon as something goes wrong, your first response should be to switch to the debug build. That will enable G3D's assertions and your own. Do this before you attempt to debug the program or reason about it. Always debug on the simplest case that triggers the error. For example, don't generate a 1000-sided cylinder and try to figure out what is wrong with it. Instead, use a three-sided cylinder. You can inspect the output file directly at that size, and can single-step through your code if necessary. For the heightfield, a 2x2 image is a good place to start. Then try 4x3 (asymmetry will flush bugs with swapping $x$ and $z$), and finally step up to a larger size. I debugged the solid of revolution tested by first creating a triangular bipyramid, which is the simplest solid of revolution. It contains five vertices and six triangular faces. Note that this does not test the center strip case. When debugging the cylinder and solid of revolution, I made one strip at a time (top, center, and then bottom) and verified the 3D model for each before proceeding to the next. Otherwise it would have been too hard to identify the source of the problem when something was generated incorrectly. Coding Style ------------------------------------------------------------------------------------ Now that you're moving code between projects, correct and useful documentation is very important. Remember; you're not programming for the computer, and you're not programming for me--you're programming for your future self and partners. The weakest form of implementation documentation is a comment. (I'm not talking about interface documentation, where comments are valuable.) Strong interfaces, clear method names and helper methods, clear variable names, useful auxiliary classes, and consistent use of design patterns are much better forms of documentation. Sometimes a comment is justified because you need to explain _why_ you did something unexpected in the implementation. But in general, most *comments are a sign that your code is poorly written*. If the code is good enough, then it doesn't need many comments because it is obvious what the interfaces are and how the implementations work. Don't comment bad code. Rewrite it. Complex algorithms and fancy optimizations are exceptions to this advice: there, you need to explain the tricky thing that was going on. But does the problem really justify doing something tricky? Did you profile your code before and after the change? Was the speedup worth it? I'd rather have code that is correct and robust than faster than required. Coding conventions become important when bringing together code from many sources. These conventions include the use of capitalization and whitespace. It doesn't matter what conventions you use so long as they are consistent across the codebase. Since you're using a lot of G3D code, I recommend that you follow the G3D project's coding conventions, which are largely the official Java conventions adapted to C++ syntax. You have the full G3D source code available to you, which abundantly demonstrates the coding conventions. Gallery ============================================================================================== For more inspiration and some impressive extensions to the base specification, look at some examples of work created by my students for this project: - [Williams College CS371 2016](https://www.cs.williams.edu/~morgan/cs371-f16/gallery/1-meshes/index.html) - [Williams College CS371 2014](https://www.cs.williams.edu/~morgan/cs371-f14/gallery/1-Meshes/index.html) - [Williams College CS371 2012](https://www.cs.williams.edu/~morgan/cs371-f12/gallery/1-Meshes/index.html) - [Williams College CS371 2010](https://www.cs.williams.edu/~morgan/cs371-f10/gallery/1-Meshes/index.html)