CPSC 324 | Fundamentals of Computer Graphics | Spring 2006 |
The projects in this course are built around the CPSC 324 Renderer package. This document provides information on how to set up your environment and work with this package.
The CPSC 324 Renderer makes use of three directories (and some subdirectories):
The separation of the code and build directories, while it might seem a bit odd, is very convenient for handin purposes (and makes it easier to ensure that old compiled versions aren't hanging around and confusing things).
You should set up your directories as follows:
cp -r /classes/s06/cs324/code ~/cs324 cp -r /classes/s06/cs324/build ~/cs324
The CPSC 324 Renderer requires several libraries which are not available in the standard location on the lab machines. This can be addressed by telling your environment where to find them; the easiest solution is to modify your .bashrc file so this environment setup is done each time you start up a terminal window. To do this:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/classes/s06/cs324/lib export LD_LIBRARY_PATH
Things should now be set properly in any new terminal windows that you open. If you get error messages about "error while loading shared libraries" when you try to start the renderer or about libicuuc.so.32, libicudata.so.32, and undefined references in libxerces-c.so when you compile, then your environment is not properly set. See me if you need help modifying your .bashrc, or if you tried to make the changes but are getting these error messages.
There are a number of elements in the renderer configuration which are tied to the specific directory setup of the lab machines. You should be able to work on the renderer from any campus computer where you are logged into your CS account (such as in Lansing 310 or the dual-boot machines in the library). It also should work over VNC (though I haven't tested this). If you have Linux on your own computer and want to work there, see me.
The makefile (see below) has been set up so that it looks for files that you've #included in the code directory. As a result, if you #include a file which is not in the same directory as the file you are writing the #include in, you'll need to include some of the pathname. For example, you may notice that camera/camera.h contains the following line:
#include "linalg/linalg.h"
It is not sufficient to just write #include "linalg.h" because the compiler will then look for linalg.h in the same directory as camera.h, and it won't find it.
If you do want to #include a file in the same directory, you can include the directory name or not as you wish e.g. scene/scene.h has the line
#include "sceneobject.h"
because sceneobject.h and scene.h are in the same directory. It would also be fine to use
#include "scene/sceneobject.h"
Because a large number of files are involved in the CPSC 324 Renderer package, a makefile has been provided to help automate the task of compiling everything. Besides greatly reducing the number of commands needed to compile all of the files, make (the program which processes the makefile) only recompiles files whose source code has changed since the last compile - greatly speeding up the compilation process when many files are involved. The makefile, named Makefile, lives in the build directory; make should be invoked from there.
For the most part, compilation is just a matter of changing to the build directory and running
make
Things will churn for a while (make prints out the commands it is running), and, if all goes well, the result will be the executable render (also in the build directory). If there are compiler errors, fix them and re-run make until the build is successful.
There are some things to be aware of with the Makefile setup used by the renderer:
If you add or remove any (source code) files, you will need to update the Makefile. To do this:
Remove the appropriate entries if you have deleted a file instead. In all three cases, if you need wrap to the next line, end the previous line with \.
Once the Makefile has been updated, run
make depend
before recompiling. This updates the Makefile.dep file in the build directory with information about what files depend on what other files. If this information is not up-to-date, things which really should be recompiled may not be and you can get strange errors when you try to run the program.
If you add or remove any #includes in a source code file, you should run
make depend
before recompiling. This updates the Makefile.dep file in the build directory with information about what files depend on what other files. If this information is not up-to-date, things which really should be recompiled may not be and you can get strange errors when you try to run the program.
You can remove any already-compiled files and force the next build to compile everything from scratch with
make clean
(Then run make to actually recompile.) This is useful to try if you get strange unresolved symbol errors when you compile or odd problems when you run the program, particularly if you forgot to make depend after updating some #includes.
You are welcome to use any editor you are comfortable with, however, emacs provides some handy benefits when it comes to locating and fixing compiler errors. In particular, if you compile within emacs, emacs will take you to the line where a compiler error occurred with a single keystroke.
To compile:
Note that if you want to execute make depend or make clean, you'll need to switch to the build directory and run these from your terminal window (see above).
To locate and fix compiler errors:
If you want to really automate the process, you can set up emacs so that a single keypress (I use the insert key) will prompt you to save any unsaved files and will then invoke make. (No more confusion because you forgot to save something before compiling!) To do this, paste the following at the end of your ~/.emacs file and restart emacs:
;; define a function to save buffers and then compile (defun save-and-compile () "save and make" (interactive) (save-buffer) (compile "make") (other-window 1) (end-of-buffer ()) ; (goto-char (point-max)) (other-window 1) ) ;; bind save-and-compile to insert (global-set-key [kp0] 'save-and-compile) (global-set-key [insert] 'save-and-compile)
Then just make sure the current buffer displays a source code file, and press insert. Ask about making these changes if you've never fiddled with your ~/.emacs file (or don't know what one is), or if you want to use a key other than insert.
Updates to the CPSC 324 Renderer package may be made available during semester. Rather than just providing new versions of these files (because then you'd have to make some changes by hand to avoid overwriting your own code), the changes are being distributed as a diff file (or "patch"). The patch file contains just the changed bits along with enough information to figure out where to apply the changes; a program called patch automates the task of applying a patch file to a bunch of files. The advantage to patching files rather than just copying new ones is that your additions are left unchanged.
To apply a patch to your files, change to your cs324 directory and run:
patch -p1 < <patchfile>
where <patchfile> is replaced by the name of the patch file you want to apply.
If the process is successful, you should see a series of output lines of the form "patching file ...". patch is pretty smart about figuring out how to make the proper changes, but it isn't perfect and if it can't apply a patch, you'll see a message like "Hunk #2 FAILED at 282" followed by something like "1 out of 17 hunks FAILED -- saving rejects to file code/scene/scene.cc.rej".
If you get one or more failed hunks, you'll need to apply those changes by hand. The reject file indicated in the message contains the diffs that couldn't be applied, and is always named "something.rej" where "something" is the file that the patch should be applied to. To fix things by hand:
less something.rej(Within less, advance a full screen by pressing space, go back a full screen by pressing 'b', advance a single line by pressing enter, quit by pressing 'q'.)
Repeat as needed if you have multiple reject files.
The reject file shows you just the differences between the old file and the new file, along with a little context so you (and patch) can figure out where the changes go. It is divided into sections corresponding to different regions of the file. For example:
*************** *** 275,280 **** DEBUG(DEBUG_SCENE,"configuring scene"); DOMNodeList * children = sceneNode->getChildNodes(); for ( int ctr = 0 ; ctr < (int)children->getLength() ; ctr++ ) { DOMNode * node = children->item(ctr); --- 282,327 ---- DEBUG(DEBUG_SCENE,"configuring scene"); + DOMNamedNodeMap * attributes = sceneNode->getAttributes(); + + string version = getStringAttr(attributes,VERSION_KEY); + + /* warning if SCENE_VERSION is older than the scene file */ + for ( int pos1 = 0, pos2 = 0 ; true ; ) { + + /* add extra . at end to simplify special-casing - don't have to + treat last part specially */ + int next1 = (version+".").find('.',pos1); + int next2 = (SCENE_VERSION+".").find('.',pos2); + + /* if scene file at end, renderer is newer (or both the same) - OK */ + if ( next1 == (int)string::npos ) { + break; + } + + /* renderer version at end but not scene file - scene file is newer */ + if ( next2 == (int)string::npos ) { + cout << "WARNING: renderer scene version " << SCENE_VERSION + << " is older than scene file version " << version << endl; + break; + } + + /* neither is at end - have to compare */ + int first, second; + + sscanf(version.substr(pos1,next1-pos1).c_str(),"%d",&first); + sscanf(SCENE_VERSION.substr(pos2,next2-pos2).c_str(),"%d",&second); + + if ( second < first ) { + cout << "WARNING: renderer scene version " << SCENE_VERSION + << " is older than scene file version " << version << endl; + break; + } + + pos1 = next1+1; + pos2 = next2+1; + } + DOMNodeList * children = sceneNode->getChildNodes(); for ( int ctr = 0 ; ctr < (int)children->getLength() ; ctr++ ) { DOMNode * node = children->item(ctr);
Each section (corresponding to a different region of the file to be fixed) is separated by ***************. This example only has one section.
Within a section, a line of the form *** 275,280 **** marks the beginning of the original file content and a line of the form --- 282,327 ---- marks the beginning of the new file's content. The numbers are the line numbers of the beginning and end of the section in the files used to create the patch file; you can ignore them.
The goal is to locate the part of your file which looks like the original file part, and modify it to match the new file part (repeating as needed for each section in the reject file). You may notice that some lines are prefixed by a '+', a '-', or an '!'. Lines without any of these symbols are the same in both the original file and the new file - no change is needed, and they are provided only to give you context for locating where the changes are to be made. Use these lines to help you locate where in your file that changes go. Lines marked by '-' in the original file part are lines which are not present in the new file - delete them from your file. Lines marked by '+' in the new file part are lines which are not present in the original file - add them to your file (you can cut and paste, but be sure to remove the '+' after doing that). Lines marked by '!' are found in both the original file part and the new file part - these are lines which correspond to each other but which have changed. Delete the old lines from your file and replace them with the new lines (you can cut and paste, but be sure to remove the '!' after doing that). In this case, there is just a big section of new code to add - look for the DEBUG(DEBUG_SCENE,"configuring scene"); and DOMNodeList * children = sceneNode->getChildNodes(); lines in the file, and add everything marked with '+' between them.
grep searches files for occurrences of a given pattern. The basic usage is
grep <pattern> <file(s)>
where <pattern> replaced by the pattern you are looking for and <file(s)> is replaced by a list of filenames to search.
For example,
grep Run *
will search all files in the current directory (* is a wildcard which matches all files in the current directory) for the string "Run", and prints the matching lines along with the name of the matching file. grep is case-sensitive, so use
grep -i Run *
if you want to find all occurrences of Run, run, rUn, etc. You can specify several sets of files to search e.g.
grep Run machine.cc mipssim.cc ../lib/*.h
to search machine.cc, mipssim.cc, and all header files (ending with .h) in the directory specified by the relative path ../lib.
find searches for files or directories with a particular name. The basic usage is
find <dir> -name "<pattern>" -print
where <dir> is replaced by the name of the directory where the search begins and <pattern> is replaced by the pattern specifying the files/directories you are looking for.
For example,
find . -name "*.cc" -print
finds all files/directories whose names end with .cc, searching the current directory (.) and its subdirectories. To find all files/directories with names starting with s in ../lib and its subdirectories, use
find ../lib -name "s*" -print
If you want to find a particular file, there's no need for the wildcard in the pattern:
find . -name "machine.cc" -print
In emacs, you can search forward from the cursor position with C-s (control + s) and backward with C-r. After pressing C-s or C-r, begin to type the string you want to search for. (It will appear in the "minibuffer" at the bottom of the emacs window.) If you want to search for additional occurrences, press C-s or C-r more than once. To stop searching and have the cursor remain at the location where the thing was found, you can press an arrow key or C-a (the keyboard shortcut for putting the cursor at the beginning of the current line). To cancel the search and return the cursor to where it was when you started the search, press C-g (the universal quit-an-emacs-command command).