CPSC 324 Fundamentals of Computer Graphics Spring 2006

CPSC 324 Renderer Practicalities

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.


Directory Organization

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).


Directory/Code Setup

You should set up your directories as follows:

  1. Create a directory called cs324 in your home directory. (You can name this directory something else if you prefer, but it will be referred to as cs324 throughout the documentation.)
  2. Copy the code and build directories from /classes/s06/cs324 to your cs324 directory:
      cp -r /classes/s06/cs324/code ~/cs324
      cp -r /classes/s06/cs324/build ~/cs324
    
  3. Create a directory scenes in your cs324 directory to hold scene files you create. (But don't copy the scenes directory in /classes/s06/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:

  1. Open the file .bashrc (yes, the filename starts with a dot) file in your home directory. You should have such a file, but if you don't, a new file can be created. If you are using a graphical tool to locate and open the file, you may need to tell it to show hidden files.
  2. Add the following two lines at the end of the file:
    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/classes/s06/cs324/lib
    export LD_LIBRARY_PATH
    
  3. Save the file.

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.


#includes

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"

Compilation, and the Care and Feeding of Makefiles

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:


Compiling in emacs

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:

  1. Save all of the currently-open files.
  2. Make sure that the current buffer contains a source file.
  3. Type M-x compile. M-x stands for meta-x, and is achieved on PC keyboards by pressing and releasing the Esc key, followed by pressing and releasing the x key. (Some keyboards actually have a key labelled "meta".)
  4. emacs will now prompt you with compile command: down in the "minibuffer" at the bottom of the emacs window. If it doesn't already say make after "compile command:", edit it so that it does. Then press enter. This tells emacs to carry out the make command in the current directory. (To make this work, a minimal makefile has been provided in each of the code subdirectories.) Your current buffer window will be split in half and the output from make will be displayed.

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:

  1. Wait for the compilation process to complete.
  2. Go to the first error (if any) by pressing C-x ` (that's control and x simultaneously, then the backquote). emacs will automatically load the correct file and place the cursor on the line in question.
  3. Fix the bug.
  4. Press C-x ` again to go to the next error.

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.


Patching

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".

Failed Hunks

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:

Repeat as needed if you have multiple reject files.

Reading 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.


Useful Commands

grep

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

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

C-s and C-r

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).


Valid HTML 4.01!