**Cubes**
[A Graphics Codex Programming Project](../projects/index.html)
![Figure [teaser]: A dog modeled with translated, rotated, and scaled cubes. By the end of this
project you'll know how to create scenes like this in three different ways and be familiar
with our C++ and OpenGL tools.](dog.jpg border=1)
*Prerequisites*
- Read online before you start:
- Graphics Codex Project [Recommended Tools](../tools/index.html) document
- G3D [Developer Tools](http://casual-effects.com/g3d/G3D10/build/manual/devtools.html)
- G3D [Build System Instructions](http://casual-effects.com/g3d/G3D10/readme.md.html)
- Read as you progress through this project in the [Graphics Codex](http://graphicscodex.com):
- Preface
- Introduction
- C++
- Version Control Basics
# Introduction
Welcome to your first Graphics Codex Project!
In this project you'll create 3D scenes from simple cubes in three ways, by directly editing a
data file, by using a GUI scene editor, and by programmatically generating a data file.
Programmatically constructing a scene in memory at run time is a fourth way, which you won't
take on just yet.
All projects in this series each have a five major sections:
Introduction
: Motivation for the project and your practical and educational goals.
[Specification](#specification)
: Precisely what you should implement, stated in a formal manner.
[Report](#report)
: Questions to answer that will ensure you've figured out both the basic and some advanced
elements of the challenge.
[Advice](#advice)
: Implementation tips using the recommended tools. This is optional, but very helpful if you
are using those tools.
[Gallery](#gallery)
: Images showing examples of what others have created for this project.
The Cubes project introduces a number of tools and libraries that may be new to you. So, I
include a tools Warmup section as well, which is structured as a tutorial. Most other project
descriptions are shorter because they do not have tutorials. They require more active
engagement on your part to to translate the specification into a program design and
implementation plan, however.
Educational Goals
---------------------------------------------------------------------------------------------
In this project, you'll gain familiarity with:
1. *Some programmatic 3D modeling skills:*
1. Coordinate system and units
2. Positioning objects in 3D space
3. A first-person camera controller
4. The Model/Entity/Surface *scene graph* design pattern
2. *Some representative programming tools:*
1. The C++ programming language
2. The Subversion (svn) revision control system
3. The G3D library
4. The Doxygen documentation generation program
5. The Markdeep markup processor
3. *Scalable software development:*
1. Automatic memory management
2. Overview documentation
3. Entry point documentation
4. Building your own tools
To achieve that excellence and deep knowledge instead of just learning a few tricks, it is
important that you approach the work with the right expectations. You can simply trust me that
it is worth spending time focusing on mathematical fundamentals, the details of physics,
software design, and development workflow in order to produce 3D graphics. Or, you can read
the optional Pedagogy section of this document, in which I explain the overarching educational
goals of the project series and why I designed them this way.
Warmup
============================================================================================
This walkthrough will guide you through the usual workflow for the specific
[tools](../tools/index.html) that I recommend for these projects, particularly the G3D
Innovation Engine, C++, and Subversion version control. Just doing the typing and clicking as
directed will give you enough familiarity to then apply the information from the related
chapters you've read. (If you're using a different toolset, make sure that you can perform
equivalent tasks. If you prefer git to Subversion, the _Graphics Codex_ version control chapter
gives a listing of the git commands that you'll need.)
Where this walkthrough says to enter specific code, please actually type it--do not copy from
this document and paste it into your editor. Typing the code yourself should prompt you think
about what it means, and if you make a mistake will give you an opportunity to debug it.
## Create an Empty Project
Create local repository/snapshot and workspace for your project, as discussed in the _Graphics
Codex_ Version Control Basics chapter. In TortoiseSVN, right click in a folder and select "SVN
checkout", then type in the remote URL of your project. Or, at the command line, use "`svn co `
url".
Create a new project as described in the
[G3D Creating a New Project](http://casual-effects.com/g3d/G3D10/build/manual/guidenewproject.html)
documentation: If you're using Visual Studio, copy the contents of the G3D starter
project directory. If you're using iCompile, run `icompile` in your project directory and
respond to the prompts.
You should now have a directory with subdirectories for `data-files`, `source`, and so on. In
the root directory are `ice.txt` and/or the Visual Studio project files with names ending in
`.sln` and `.vxproj`.
Verify that your program is working and your system is configured correctly. Using Visual
Studio, load the `.sln` file and press F5. With iCompile, run
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
icompile --run
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The program should compile, launch, and display a 3D scene. You can exit with the ESC key. If
it didn't run correctly, debug your environment to ensure that it conforms to the one described
in the [tools](../tools/index.html) document.
### Experiment with the GUI
By default,
[`G3D::GApp`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_g_app.html)
creates a
[`G3D::FirstPersonManipulator`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_first_person_manipulator.html)
that allows you to move the 3D camera. I'm telling you the name of the class right now so that
you'll know how to find and configure it later, but right now all that you need to know
is...video game controls work.
G3D initially launches with a scene's built-in cameras selected. Press F2 to switch to the
Debug Camera.
The manipulator on the camera uses common first-person PC video game controls. The `W`, `A`,
`S`, and `D` keys on the keyboard will translate the camera forward, left, back, and right
relative to its own axes. Try this now.
If you press the right mouse button (or press "control" and the mouse button for a
single-button mouse under OS X), then the mouse rotates the yaw and pitch of the camera. It
requires you to press a button because otherwise using the mouse with the GUI would also move
your viewpoint.
There are some other modifiers; run the G3D viewer or read the documentation to find out about
them. You can also use a USB game pad to navigate. G3D contains other manipulators with
different control styles, and you can write your own or use none at all. This is only the
default.
Move the camera around a bit to get a feel for the controls. Exit your program by pressing the
"ESC" key.
### Switch to the Optimized Build Target
The G3D starter project has two build targets: Debug and Release. The Debug target allows
detailed inspection of your code in the debugger and enables many consistency checks. The
Release build runs much faster.
You can switch between targets in Visual Studio from the (unlabelled)
[Solution Configurations](https://msdn.microsoft.com/en-us/library/wx0123s5.aspx) list box.
With iCompile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
icompile --opt --run
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
builds and runs the Release build and the absence of `--opt` defaults to the Debug build.
In general, it is safe to run in Release mode for most of development. You should immediately
drop back to the debug build whenever something goes wrong with your program because it contains many
assertions and input checks.
Experiment with the developer tools provided within your window, and particularly try loading
other scenes (via the `SceneEditorWindow`'s scene drop-down list). Exit the program when you're
ready.
### Commit the Files to Version Control
Your _project_ is in version control, but your _files_ are just sitting in the local workspace
right now. Following the process described in the Version Control Basics chapter, you'll now
add and commit the files.
When you add the source files to version control, you must be careful not to add the generated files.
Generated files would consume too much space in the repository and create frequent merge conflicts.
The easiest way to do this is to delete everything that you don't want added and
then execute a recursive command on the whole tree.
Delete the `temp`, `build`, `x64`, `Debug`, `Release`, `.vs`, or any other build
directories.
From the `data-files` directory, delete `log.txt` and `g3d-license.txt`.
Delete any IDE temporary files, such as ones beginning with `#` or ending in `~`, and the
Visual Studio `.pdb` or `.opendb` files. You can keep the `ice.csv` file, which is a
spreadsheet tracking your program's growth if you're using `iCompile`.
Recursively add the remaining files. At the command line, execute:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
svn add *
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
or, for TortoiseSVN, select all files and subdirectories in Explorer and right click to
add. You can also simply right click and select "Commit" and then select the files that you
wish to add from the dialog that will appear.
Commit these files with:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
svn commit -m "Initial checkin"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on the command line or right-clicking using TortoiseSVN and selecting "commit". Recall that
Subversion automatically pushes each commit to the remote repository, so your files are now
without an explicit "push" step.
## Create a new 3D Scene
The `App` class in your program inherits from `G3D::GApp`, which creates a
[`G3D::Scene`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_scene.html)
during initialization. That `Scene` parses files ending with the extension `.Scene.Any`, which
are human-readable files with syntax similar to C++ code, JSON, and Python literals. These
files can be produced by hand, by another program, or using the GUI provided by
[`G3D::SceneEditorWindow`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_scene_editor_window.html)
in the default `G3D::GApp`. We'll begin by creating a scene by hand.
In your editor, create a new file `data-files/scene/whiteCube.Scene.Any` with the contents:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ linenumbers
// -*- c++ -*-
{
name = "White Cube";
models = {
cubeModel = ArticulatedModel::Specification {
filename = "model/cube/cube.obj";
preprocess = {
setMaterial(all(), Color3(1, 1, 0));
};
};
};
entities = {
skybox = Skybox {
texture = "cubemap/whiteroom/whiteroom-*.png";
};
sun = Light {
attenuation = ( 0, 0, 1 );
bulbPower = Power3(4e+006);
frame = CFrame::fromXYZYPRDegrees(-15, 207, -41, -164, -77, 77);
shadowMapSize = Vector2int16(2048, 2048);
spotHalfAngleDegrees = 5;
rectangular = true;
type = "SPOT";
};
cube0 = VisibleEntity {
model = "cubeModel";
frame = CFrame::fromXYZYPRDegrees(0, 0, 0, 0, 0, 0);
};
camera = Camera {
frame = CFrame::fromXYZYPRDegrees(0, 0, 5);
};
};
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Launch your program and the new "White Cube" scene should appear in the scene drop down box of
the GUI.
## Fix a Scene Error
When you load the scene, you'll notice that cube in this scene is actually yellow and not
white. You should fix that. You can either guess how, or look at the
[`G3D::UniversalMaterial::Specification`](http://casual-effects.com/g3d/G3D10/build/manual/class_g3_d_1_1_universal_material_1_1_specification.html#a12d19df20a48ad996ae5b6538bdd3d9b)
documentation to see the arguments that can provided in the scene data file for constructing
materials. When you've made your changes, press the reload button in the Scene Editor Window
without ever exiting your program.
### Add the Scene file to SVN
Whenever you create a new file, it is a good idea to add it to
revision control right away so that you don't later forget. Execute:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
svn add data-files/scene/whiteCube.Scene.Any
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
or the TortoiseSVN equivalent. This command will mark the file for addition to your repository.
You can see this by running `svn status`. However, they haven't actually been added yet. To
do that, commit your changes with:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
svn commit -m "Added single cube scene"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
or the TortoiseSVN commit dialog.
Now your file is in the remote repository. If you modify the file, then you will need to
commit the new version. But you never need to add this file again. (From now on, I'm going to
assume that you know to add and commit as necessary.)
## Debugging
### Fixing a Compile-Time Error
End your program with the "ESC" key and open `App.cpp` in your editor. In the `App::onInit`
method about 50 lines from the top, add this code:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
debugPrint("Target frame rate = %f Hz\n", 1.0f / realTimeTargetDuration());
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile your program. You should see an error that looks something like:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ none
App.cpp(58): error C3861: 'debugPrint': identifier not found
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The exact wording of the error will depend on your compiler. The error is telling you
that the code I told you to add is broken.
Look at the [API index](http://casual-effects.com/g3d/G3D10/build/manual/apiindex.html) and you'll see
that the correct function name is [`G3D::debugPrintf`](http://casual-effects.com/g3d/G3D10/build/manual/namespace_g3_d.html#a364214608a0b19645fd04f84254833d6).
You don't need to type the `G3D::` part. That's a C++ namespace, and I list it in the projects only
so that you'll know when I'm referring to a G3D function, a built-in C++ function, or something
that you're writing.
Change the code to call `debugPrintf` instead of `debugPrint`, compile, and run. Look for the output
of this command. It will appear in the console if you're compiling on a Unix system (OS X or Linux)
and in the Debug pane of the Output window in Visual Studio.
### Debugging and Fixing a Run-Time Error
Edit your `App::onInit` method again and add this line of code (anywhere):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
const shared_ptr< Entity >& sphere = scene()->entity("Sphere");
sphere->setFrame(Point3(0.0f, 1.5f, 0.0f));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This line of code will move the object named "Sphere" to the coordinate (0, 1.5, 0) in meters.
The only problem will be that there is no object named "Sphere" in the scene. When you compile
and run your program, there will be an error (if you're in Release mode, it will probably
crash).
To debug the program, run it under the debugger in Debug mode. In Visual Studio, select "Debug"
on the dropdown and press F5. With iCompile, run "`icompile --lldb`" and then press "r" when
the debugger starts.
When the program crashes while a debugger is attached, the debugger presents the error. It
should be something like "`e_Ptr was nullptr`". In this case, we know exactly where the problem
is. Pretend that we don't know, and let's see how you could figure it out on your own.
In Visual Studio, look at the Call Stack window. In lldb, type `bt`. The call stack will
look something like:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ none
App::onInit() Line 63
G3D::GApp::beginRun() Line 1424
G3D::GApp::onRun() Line 785
G3D::GApp:run() Line 768
main(int argc, const char** argv) Line 42
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
followed by some operating system level call frames. This says that when your program crashed,
it had started with `main`, which called `GApp::run`, and so on, until at line 63 the problem
occured.
In Visual Studio, double-clicking on the `App::onInit` line in the Visual Studio Call Stack window
will bring up that line of code in your program. You can hover the mouse over the `sphere` variable
to see its value...which is `empty`. That's why the program crashed. There was no sphere, so
the sphere pointer is null. You can also look at the Autos or Locals window to see the values of
other variables. The `this` pointer refers to your `App` instance. Expand the tree control for it
so that you can see the current values of all of its properties.
In lldb, the `f` command allows you to select a call frame by number. We're already on the top
call frame, so there is no need to use it right now. The command `fr v -a` shows all local
variables, and `p sphere` prints the value of the sphere variable.
The program will break and enter the debugger whenever an exception, stack overflow, or
segmentation fault occurs. You can also force entry to the debugger by setting a breakpoint
("F9" in Visual Studio, "break" in lldb) or breaking the program during execution
(Ctrl-Break in Visual Studio, Ctrl-C in lldb).
Remove the broken code that I told you to add for this exercise before continuing.
## Documentation
### Entry Points
Generate documentation for your project using the Doxygen tool. On Windows, run:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
builddoc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
in the same directory as your project files and the `Doxyfile`. On Unix platforms, run:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash
icompile --doc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The documentation will appear in `build/doc`. It contains the contents of your `doc-files`
directory and a series of HTML files that describe every class, method, and function. The
`index.html` file is the starting point for reading the documentation.
Wherever code is preceeded by a Doxygen style comment of the form `/** ... */` in the header,
those comments will appear in the generated documentation. Try adding documentation to your
`App` class and then re-running the documentation generator. Within Doxygen comments you can
use a subset of Markdeep formatting and HTML.
### Maintaining your Journal
The [G3D Developer Tools](http://casual-effects.com/g3d/G3D10/build/manual/devtools.html)
document summarizes the built-in features of G3D for supporting development of your
program (versus supporting the program's own features). One of the most useful ones is
the automatic journalling feature.
Press the "F4" key to take a screenshot in any G3D program. This brings up a dialog that allows
you to add a caption and descriptive text. It will then add the image to your journal and even
add it to version control. You'll still want to write text and add diagrams and code listing
directly in `journal.md.html`, but this tool makes it easy to document your features and
debugging process.
Specification
============================================================================================
*Track how much time you spend on this project*. You're required to include this in your final
report.
1. Structure a directory in svn with exactly the following subdirectories, some of which may be
empty for this project:
1. `data-files` -- _Files needed for running your program_
2. `source` -- _Your `.cpp` and `.h` source code_
3. `doc-files` -- _Files needed when viewing your documentation and report_
4. `journal` -- _Log of your software development progress_
2. Create the following scenes in human-readable `.Scene.Any` data files, using only the files
`models/crate/*`, `cubemap/whiteroom/whiteroom-*.png`, and texture maps (images):
1. A single, white 1 m^3 cube rotated 45 degrees about the vertical axis, with center at (0m, 0m, -2m),
created by manually editing a scene file.
2. A model of the Cornell Box that is pictured in Figure [fig:cornell-photo] created by
a combination of the starter program's scene editing GUI and manual file editing.
3. A texture-mapped curving staircase with 50 steps created by writing a C++ helper function to
generate the scene file on disk.
- You may not use the GUI or manually edit this file.
- The individual cubes must be stretched to rectangular slabs.
- The material must be recognizable and appropriate for a staircase. E.g., wood,
concrete, marble, or stone.
4. An interesting scene of your own design, containing at least 20 cubes.
3. The following two forms of documentation (these are requirements for every project, so I won't mention them
in future projects explicitly):
1. Create overview and entry point (i.e., method, class, function, variable) documentation for
your software using Doxygen. The entry point documentation is largely created for you from
the starter project.
2. Maintain a journal in `journal/journal.md.html` separate from the report that demonstrates
your progress, particularly describing key design decisions and changes and bug symptoms and
solutions.
![A textured staircase](stairs.jpg width=150px border=1)
Your solution stored in the remote repository should not have any unused files, dead code, or
"TODO" comments. You are not required to remove empty or unnecessarily overridden methods
(e.g., `onGraphics3D`) for this project, although you may with to remove them to simplify your
code.
Report
==================================================================================
Prepare your report as a Markdeep document in `doc-files/report.md.html`.
The report will appear in the `build/doc` directory when read. Knowing this allows you to
insert relative links to Doyxgen-generated files (which will be in the same directory) and to
embed images that you have stored in `doc-files`.
Your report should be as brief as possible while covering the following points.
1. *System Overview*: Assume that someone who doesn't know anything about G3D or your program
will have to modify it in the future. Describe the structure of your program for this
person in your report, with links to major classes and methods. This should only take about
one paragraph for describing the starter program and one more paragraph to describe the code
that you added to it. I encourage you to use lists, tables, diagrams, and hyperlinks.
2. *Coordinate System*: Make simple, isometric view, labelled axis-diagrams of the 2D coordinate
and 3D coordinate systems (by hand; don't write code for this) used for G3D, and include it
in your report. On the 3D coordinate system, show the direction of increase of the yaw,
roll, and pitch angles. I would personally either use a Markdeep diagram or draw this on
paper and take a photograph.
3. *Results*: Some parts of the specification require you to create a scene. Include images of
those scenes taken in a way that clearly illustrates that they satisfy the
specification. For example, you may need to show a set of axes to make clear that the white
cube has been appropriately oriented. Avoid capturing the GUI in your result images unless
you specifically need to show the GUI for some reason. Crop images to an appropriate
size.
There are two kinds of visual results one might present in a report:
_Scientific_ results demonstrate correctness (or not) of a system. They may be cropped and
have their histogram adjusted in Photoshop, and you can draw obvious annotations on them
(such as text and arrows that clearly are not part of the result). You may not resize them,
paint on them, or otherwise modify them in a way that is misleading about the actual pixels
produced by your program. This is an important science/engineering issue.
_Art_ results can be manipulated as much as you wish. In fact, and important part of
creating such results is knowing how much to program and how much to manually adjust. You
may end up manually correcting every frame of an animation or compositing elements of
different sequences together. If it is ambiguous whether an image is intended as art, just
label clearly how you manipulated it.
4. Briefly explain the process that you used to make your custom scene. Include tools, assets,
and planning steps. Think of this as the best notes that you'd write for someone else who
was trying to make something similar, if you only had fifteen minutes to write down the
instructions.
5. Typeset this equation into your documentation using LaTeX embedded in Markdeep: (you may not
look at the source of this page, which as a Markdeep document itself, obviously has the
solution embedded in it!)
\begin{equation}
\frac{d f(x)}{d x} = \lim_{h\rightarrow0} \left[ \frac{f(x+h) - f(x - h)}{2h} \right]
\end{equation}
6. *Questions*: Knowing how to use documentation, experimentation, and reverse engineering to
discover how a system works are important skills. In this project you copied a lot of code
that I wrote. To gain mastery over that code, figure out the answers to the following
questions and write them in your report. You're going to have to get your hands dirty on
this--the answers aren't just sitting there.
1. What are the differences between the `Scene*` and `shared_ptr