CAVE User's Guide

March 31, 1995

CAVE Library version 2.4.3
Electronic Visualization Laboratory
University of Illinois at Chicago
851 S. Morgan St., Room 1120
Chicago, IL 60607-7053
(312) 996-3002
(312) 413-7585 fax
cavesupport@evl.eecs.uic.edu
(c) 1995 Electronic Visualization Laboratory, University of Illinois at Chicago



TABLE OF CONTENTS

1. Purpose of the CAVE User's Guide

2. CAVE Description

3. CAVE Equipment

3.1. Overall structure
3.2. Projectors and mirrors
3.3. Stereo glasses
3.4. Stereo emitters
3.5. Wand
3.6. Tracking systems
3.7. Audio system
3.8. Workstation

4. Designing CAVE Applications

4.1. Inside-out paradigm
4.2. Interactive controls
4.3. CAVE programming tools
4.4. Stereo considerations
4.5. Graphics details
4.6. Multiprocessing considerations
4.7. CAVE Geometry

5. CAVE Library

5.1. Overview
5.2. CAVE functions
5.3. CAVE macros, variables, and miscellaneous functions
5.4. Form of a basic CAVE program
5.5. Compiling a CAVE program

6. CAVE Simulation

6.1. Introduction
6.2. Simulated tracking
6.3. Simulated wand controls
6.4. Simulated display

7. Supporting software

8. CAVE configuration files

9. Sample programs

9.1. CAVE sample program 1
9.2. CAVE sample program 2




1. Purpose of the CAVE User's Guide

This CAVE User's Guide contains all the information an application developer needs to successfully create a CAVE experience. We assume that the reader has a basic knowledge of the C programming language and is familiar with the Silicon Graphics GL Library.

2. CAVE Description

The CAVE (CAVE Automatic Virtual Environment) is a projection-based VR system that surrounds the viewer with 4 screens. The screens are arranged in a cube made up of three rear-projection screens for walls and a down-projection screen for the floor; that is, a projector overhead points to a mirror, which reflects the images onto the floor. A viewer wears stereo shutter glasses and a six-degrees-of-freedom head-tracking device. As the viewer moves inside the CAVE, the correct stereoscopic perspective projections are calculated for each wall. A second sensor and buttons in a wand held by the viewer provide interaction with the virtual environment.

The current implementation of the CAVE uses three walls; we can project on the three side walls, or two walls and the floor. The projected images are controlled by an SGI Onyx/RE2 with three graphics pipelines. For testing, you can run the CAVE using one, two or three walls simultaneously. The number of CAVE walls used does not affect your program. The CAVE library automatically determines how many walls you want to use and does the necessary setup when your program starts.

A diagram of the CAVE environment is shown in Figure 1.

3. CAVE Equipment

3.1. Overall structure

CAVE hardware should be configured by system and video engineers so it is usable. Under normal operation, CAVE users should only be concerned with turning on and off the different components (although, due to ongoing CAVE research, the hardware configuration could change occasionally, at which time you should be notified by your system administrator.) The following is a description of the CAVE equipment.

3.2. Projectors and mirrors

The projectors and the mirrors for the side walls are located behind each wall. The projector for the floor is suspended from the ceiling of the CAVE. The projectors are very sensitive to almost everything. It takes at least one hour to align and calibrate each projector and mirror so they match at the corners of the CAVE. Please be careful not to move the projectors or mirrors if you have to walk in that area.

NEVER TURN OFF the projectors; this can cause them to go out of alignment. Use the standby button on the remote control to activate them. Press and hold the standby button on the back of the projector or the standby button on the remote control for a couple of seconds. DO NOT TOUCH any other control that might cause the projectors to go out of alignment.

Once you are done using the CAVE, remember to put all the projectors to SLEEP by pressing the standby button. The projection tubes have a limited life span that can be extended by putting them on standby when not in use.

3.3. Stereo glasses

To see the virtual environment in stereo, users wear Stereographics' CrystalEyes stereo glasses made of liquid crystal. The glasses are very fragile. In order to see stereo properly, they have to be turned on by pressing a small button located on the right side of the frame. To turn them off, press the same button. They will not work if the user is facing away from the emitters.

3.4. Stereo emitters

The stereo emitters are little white boxes placed around the edges of the CAVE. They are the devices that synchronize the stereo glasses to the screen update rate of 120Hz or 96Hz. They are always on. You should not have to do anything with them.

3.5. Wand

A wand (a 3D mouse) with buttons is the interactive input device. Currently, EVL has two wands; both wands use the Ascension Flock of Birds tracking system, but have different control devices. The primary wand has three buttons and a pressure-sensitive joystick. It is connected to the CAVE through a PC which is attached to one of the Onyx's serial ports. A server program on the PC reads data from the buttons and joystick and passes them to the Onyx. The older wand just has three buttons, and is attached to the mouse port of the Onyx. When using the older wand, be sure that the mouse pointer is on the main screen (or wherever your DISPLAY variable points) while the CAVE is running, or your program will not be able to detect the state of the wand buttons.

3.6. Tracking systems

Currently the CAVE supports various tracking systems. The primary system is an Ascension Technologies Flock of Birds. An alternative system, used for the Immersadesk and desktop CAVE systems, is a Logitech sonic tracker. There are also "simulated" tracking options available, using either the keyboard and mouse or a spaceball. The use of one or another is transparent to the CAVE programmer, since it is defined in the CAVE configuration file. All systems have two sensors, one for tracking the user's head, and another for the wand.

3.7. Audio system

The audio system components are: an Indy workstation, speakers, a MIDI interface, and synthesizer.

The Indy functions as a "sound server" for the CAVE. Commands are sent to the workstation over the network, and it then either generates sounds internally, or controls the synthesizer.

The speakers are located in the corners of the CAVE. They are always turned on. DO NOT TOUCH any of the controls on the speakers. Everything is controlled from the synthesizer.

The MIDI interface and synthesizer are located on a rack at the entrance to the CAVE. To use the synthesizer, you must load a set of dummy instruments before you can use it from your CAVE program.

3.8. Workstation

The current implementation of the CAVE runs using a Silicon Graphics Onyx with three Reality Engine 2s. Each Reality Engine is attached to a CAVE wall. When using the 120 hz display mode, the front wall is the "master" for stereo video synchronization in the CAVE. This means that if you are running the CAVE without using all the walls, at least the front wall should be active.

4. Designing CAVE Applications

4.1. Inside-out paradigm

Designing a program for the CAVE is a little bit different than designing a graphics program for a workstation. The programmer must be aware of the differences in order to produce a high quality application for the CAVE.

As graphics programmers, we are used to the concept of having our workstation screen be a window enabling us to look out into the scene being rendered. To see and explore our graphics, we move the window. To manipulate objects, we provide a set of 2D controls (such as a mouse or a set of sliders) with an abstract manipulation function assigned to them.

The CAVE presents a different visual paradigm: the inside-out paradigm. In the CAVE, we are not looking at our scene, we are INSIDE the scene. We can walk around objects, we can manipulate them and we can travel through the environment.

This difference is very important to remember when designing applications for the CAVE, because we can completely immerse a viewer in the scene. We can develop more intuitive interfaces to manipulate objects, to control the scene, and to navigate the space than we design for 2D monitors.

4.2. Interactive controls

The CAVE currently has a 3D wand with buttons and a joystick for interacting with the virtual environment. The wand has a sensor at the front tip that returns its (x, y, z) position inside the CAVE to the application program. The sensor also returns three orientation angles: azimuth, elevation, and roll.

Each one of the buttons can be ON (pressed) or OFF (not pressed). Different functions can be assigned to the buttons: grabbing an object, flying through the environment, starting a particular activity in the model, etc.

4.3. CAVE programming tools

There are several programmings tools to simplify the task of designing, implementing, and testing a CAVE application. These tools are briefly mentioned here and explained in detail in later sections of this manual.

The CAVE Library has all the functions necessary to create a CAVE program. It deals with the synchronization of all the CAVE devices, the synchronization of the walls, the calculations of the stereo transformation and many other CAVE-specific tasks.

The CAVE Simulator is a library that runs on any workstation which supports SGI's IrisGL library. The simulator allows a programmer to do preliminary CAVE application development, place objects in a scene, or choose the rendering model without having to use the actual CAVE hardware. The simulator is extremely useful for those developing CAVE applications at remote sites (i.e., sites that don't have a CAVE handy).

The CAVEviewer is similar to the Simulator, except that it provides a set of Motif controls and can dynamically load the application-specific code for a CAVE program. It is intended for distributing demonstrations of CAVE applications through Mosaic.

4.4. Stereo considerations

For every frame of an animation, two views are produced, one for the left eye and one for the right eye. The CAVE library display routine will call your drawing routine twice for every frame. Do not perform application-related calculations in the same routine that calls the drawing commands for a scene.

You should separate frame calculations from drawing (or display) procedures, or you should have some internal controls in the code to be sure the calculations are performed only once for each frame (e.g. only when your rendering function is drawing the left eye's view (see Section 5.3.2)).

4.5. Graphics details

Your program does not have to perform any window or projection commands. The CAVE library does that for you, in order to produce the correct stereo perspective. The CAVE by default is set to RGB mode, double buffered, with z-buffering. DO NOT ISSUE THE SWAPBUFFERS COMMAND; it is handled internally by the CAVE library. You are responsible for any other graphics commands, such as lighting, object transformations, smoothing of lines, or clearing the screen.

4.6. Multiprocessing Considerations

In the previous version of the CAVE, we used up to five SGI machines connected via ScramNet reflective memory. Four of the machines were dedicated to running the application for each of the four CAVE walls, and an additional machine was used to synchronize the other four. Under this scheme, four separate copies of a user's application were running, one per machine. Applications ran in serial mode; at each frame they calculated new data and then rendered it for display in the CAVE.

The new version of the CAVE runs on a multiprocessor SGI Onyx; this one machine performs all the CAVE tasks. When an application is run, it forks into several concurrent processes. There is an independent rendering process running for each wall, plus a computation process to perform calculations. With this system, some method is needed to communicate changes in data between the application and rendering processes; normally, forked processes all have separate copies of a program's data, which means that changes made by the computation process would not be seen by the rendering processes.

One quick solution is to not use the computation process, but instead to do all computations in the rendering processes. Unfortunately, this is not the most efficient solution. The computations are done by the same process as the rendering, so the advantages of parallelism are lost, and the program will run more slowly than it could. Also, each rendering process will have a separate copy of the program's data; if a program uses a large amount of data, the Onyx may run out of free memory.

The preferred method for CAVE applications is to use shared memory. By putting data in shared memory, computations can be performed in parallel with the rendering, with all processes using the same data. The CAVE library provides a utility function to create a shared memory arena, from which you can then allocate shared data. Initialization of shared memory should occur before calling CAVEInit, so that all the processes will have access to the shared data.

Data that does not change does not need to be in shared memory. If, however, the data is very large, it probably should be stored in shared memory anyway, so as not to fill up memory with multiple copies of the data after the program forks.

When porting an existing application to the CAVE, you should design it for the CAVE's multiprocessing architecture. Calculations should be separated from graphics code, so that they can be put in the separate processes. Any global variables used to share data between calculations and rendering will need to be in shared memory. This typically involves adding an extra level of indirection, and then amalloc'ing the variables from a shared arena.

4.7. CAVE Geometry

The standard CAVE is a 10-foot cube. The origin of the coordinate system (0, 0, 0) for the CAVE is normally located at the center of the floor, that is, 5 feet away from any wall. This means that you have from +5 to -5 feet horizontally and 0 to 10 feet vertically to define objects inside the CAVE. The exact location of the CAVE origin is defined in the configuration file by the "Origin" option. If you wish to change its location, you must change all the configuration settings that are given in CAVE coordinates (Origin, TransmitterPosition, and DeskCorners) to use the new same coordinate system.

All the walls of the CAVE share the same reference coordinate system, as shown in Figure 2. The coordinate system is a right-handed system. All locations and orientations returned by the trackers to the CAVE library will follow this convention.

By default, the near and far clipping planes of the CAVE are located at 0.1 and 100.0 feet. Those values can be changed by modifying the values of two global variables in the CAVE library: CAVENear and CAVEFar. Both are defined in "cave.h".

5. CAVE Library

5.1. Overview

A library of C functions and macros has been developed to control the operation of the CAVE. The CAVE library takes care of all the tasks that have to be performed to correctly operate the CAVE. CAVE functions keep all the devices synchronized, produce the correct perspective for each wall, keep track of which walls are in use, and provide the applications with the current state of all the CAVE elements. This section describes in detail each of the routines and macros of the CAVE library.

NOTE: The names of all CAVE functions, macros, and global variables start with the word CAVE (CAVEDisplay, for example). New applications should be developed with the names given in this guide; programs that use older style names (not documented here) should include the header file "cave.old.h" in addition to "cave.h".

5.2 CAVE Function Calls

The following are the basic CAVE library functions which control the operation of a CAVE program. CAVEInit, CAVEDisplay, and CAVEExit are used by all CAVE applications; the rest are optional. These functions should be called from your main process; they cannot be called from a rendering process.
void CAVEConfigure(int *argc,char **argv,char **appdefaults)
Initializes the CAVE configuration. The CAVE library's internal shared memory arena is created, the various global variables are initialized, the configuration files are read, and then any configuration options given in appdefaults or argc/argv are set (in that order). See Section 8 for a description of the CAVE configuration options.
appdefaults is an array of strings; each string should look just like a line in a configuration file. The last entry in the array must be NULL.
Options set with argc/argv consist of pairs of arguments; the first argument is the keyword with a leading '-' (eg "-walls"), and the second argument contains the rest of the option (eg "front left"). One additional option available with argc/argv is "-caveconfig", which specifies another configuration file to read.
After calling CAVEConfigure, argc & argv will be modified to remove all configuration options, leaving the rest of the command line for the application. NULL may be passed for argc/argv or appdefaults.
CAVEConfigure is called by CAVEInit; if you call it directly, you should do so before calling CAVEInit. Only the first call to CAVEConfigure will do anything.

void CAVEDisplay(void (*function)(),int num_args,...)
This function passes the CAVE library a pointer to your drawing routine. Your routine will be called by the rendering processes once per eye view per frame (i.e. twice per frame for stereo, once per frame for monoscopic mode). All rendering should be done from this routine; any GL calls made directly by the main computation process will have no effect on what is displayed in the CAVE. CAVEDisplay blocks until the next swapbuffers call by the rendering processes.
The first argument is a pointer to the drawing routine. The second argument is the number of arguments that the drawing routine receives (5 is the maximum). If your routine does not take any arguments, pass zero (0). The remainder are the arguments to be passed to your routine. These are stored as void *'s, and so MUST be pointers (also, they should use shared memory if they point to values that the computation process may change).
CAVEDisplay can only be called after CAVEInit.

void CAVEExit(void)
Ends a CAVE program. This function will signal all the CAVE processes to halt, and then calls exit.

void CAVEFrameFunction(void (*function)(),int num_args,...)
This function passes the CAVE library a pointer to a routine which should be called once per frame. The routine will be called exactly once per frame whether the CAVE is in mono or stereo mode; it is called at the beginning of a frame, before both the init and display routines. CAVEFrameFunction blocks until the next swapbuffers call by the rendering processes.
The first argument is a pointer to the frame routine. The second argument is the number of arguments that the routine receives (5 is the maximum). If your routine does not take any arguments, pass zero (0). The remainder are the arguments to be passed to your routine. These are stored as void *'s, and so must be pointers.
CAVEFrameFunction can only be called after CAVEInit.

void CAVEInit(void)
Initializes the CAVE environment. This function starts the rendering processes, and initializes the trackers and graphics. After CAVEInit is called, the rendering processes are separate from the main computation process; only the computation process will return to your program from CAVEInit.

void CAVEInitApplication(void (*function)(),int num_args,...)
This function passes the CAVE library a pointer to your graphics initialization routine. Your routine should do any GL initialization that is required for your display functions. The rendering processes will call this routine exactly once, at the beginning of the next frame. CAVEInitApplication blocks until the next swapbuffers call by the rendering processes.
The first argument is a pointer to the graphics initialization routine. The second argument is the number of arguments that the graphics initialization routine receives (5 is the maximum). If your routine does not take any arguments, pass zero (0). The remainder are the arguments to be passed to your routine. These are stored as void *'s, and so must be pointers.
CAVEInitApplication should be called after CAVEInit, and before CAVEDisplay.

void CAVEStopApplication(void (*function)(),int numargs,...)
This function is used to suspend an application's display processes without actually exiting. It clears the display, initialization, and frame functions (set by CAVEDisplay, CAVEInitApplication, & CAVEFrameFunction), and then has the display processes call function. This routine will not return until after function has been called. Note: you do not have to call CAVEStopApplication before exiting a CAVE program, unless you want the graphics processes to call a "clean-up" function.

5.3. CAVE macros, variables, and miscellaneous functions

CAVE macros simplify access to the wand information. The global variables provide various information about the state of the CAVE.

5.3.1 Controller macros

CAVEBUTTONn = ON
CAVEBUTTONn = OFF
There are three buttons attached to the wand. They can be accessed through the above macros, where n = 1, 2, 3, or 4.
CAVEBUTTON1 corresponds to the left wand button
CAVEBUTTON2 corresponds to the middle wand button
CAVEBUTTON3 corresponds to the right wand button
CAVEBUTTON4 corresponds to the fourth button on the Logitech flying mouse
ON and OFF are macros defined in "cave.h". ON = TRUE (BUTTONn is pressed). OFF = FALSE (BUTTONn is not pressed).
CAVE_JOYSTICK_X
CAVE_JOYSTICK_Y
The PC-based wand has a pressure-sensitive joystick in addition to buttons. These two macros will give the X & Y coordinate values of the joystick, normalized to be in the range [-1.0,1.0]. (Note: when the joystick is not being pressed, these values will be close to, but not exactly, 0).

5.3.2 Global Variables

The following are global variables used by the CAVE library. CAVENear and CAVEFar can be changed by an application. The other variables are meant for information only; your program should not change them.
int CAVENear,CAVEFar
The near and far clipping plane distances for the CAVE's perspective projection. These are not shared; each rendering process has independent copies.
int CAVEEye
The eye view which is currently being drawn when your display function is called; the possible values are CAVE_LEFT_EYE and CAVE_RIGHT_EYE. This variable is not shared, since the rendering processes are not synchronized except when they call swapbuffers. In monoscopic mode this variable always has the value CAVE_LEFT_EYE.
int CAVEWall
The wall which a rendering process is responsible for. Possible values are CAVE_FRONT_WALL, CAVE_LEFT_WALL, CAVE_RIGHT_WALL, CAVE_FLOOR_WALL, CAVE_BACK_WALL, CAVE_CEILING_WALL, CAVE_ARPAFLOOR_WALL, CAVE_DESK_WALL, CAVE_DUAL_EYE_WALL, CAVE_SIMULATOR_WALL, CAVE_SIMULATOR1_WALL, and CAVE_SIMULATOR2_WALL.
float *CAVEFramesPerSecond
The current frame rate. This is pointer to a float because it is stored in shared memory, and so is the same for all processes.
float *CAVETime
The current "CAVE time". This records the number of seconds since CAVEInit. The variable is updated in the display loop, once per frame, and is stored in shared memory.
char *CAVEVersion
A string identifying the version of the CAVE library. It contains the version number and release date.
CAVE_CONTROLLER_ST *CAVEController
A structure containing the status of the wand controls. The 'button' entry is an array of ints that give the state of the buttons (0 or 1); the 'valuator' entry is an array of floats that give the state of any valuators. The PC-based wand has two valuators - the joystick X and Y. The CAVEBUTTON and CAVE_JOYSTICK macros access this structure.

5.3.3 Miscellaneous Functions

int CAVEButtonChange(int button)
Returns a flag indicating the change in a button's state, compared to the last time the function was called. 0 indicates the button has not changed, 1 indicates that it has been pressed, and -1 indicates that is has been released. button should be 1, 2, 3, or 4. The button states are remembered by this function in each process independently.
void CAVEDisplayBarrier(void)
Provides a synchronization barrier for the display processes. When this function is called from an application's display routine, it will wait until all of the display processes reach the barrier before returning. This function should not be called from any other processes; furthermore, it must be called by all the display processes that the library started, or the callers will block indefinitely.
void CAVEFreeLock(CAVELOCK lock)
Frees up a CAVE lock, releasing the shared memory that it uses. The lock should be one returned by CAVENewLock().
void CAVEGetEyePosition(int eye,float *x,float *y,float *z)
Returns the position of an eye. The first argument indicates which eye's position you are requesting; it should have the value CAVE_LEFT_EYE or CAVE_RIGHT_EYE. The remaining three arguments return the position, in CAVE coordinates.
void CAVEGetOrientation(CAVEID oname,float *angle)
Returns the orientation of a sensor or eye. The oname argument indicates which object's orientation you are requesting; it should be one of CAVE_HEAD, CAVE_WAND, CAVE_LEFT_EYE, or CAVE_RIGHT_EYE (note that the eyes will have the same orientation as the head). The orientation is returned in angle, which should be an array of three floats; angle[0] is the elevation (X rotation), angle[1] is the azimuth (Y rotation), and angle[2] is the roll (Z rotation).
void CAVEGetPosition(CAVEID posname,float *pos)
Returns the position of a sensor or eye. The posname argument indicates what position you are requesting; it should be one of CAVE_HEAD, CAVE_WAND, CAVE_LEFT_EYE, or CAVE_RIGHT_EYE. The position is returned in pos, which should be an array of three floats.
float CAVEGetTime(void)
Returns the current "CAVE time", i.e. the number of seconds since the CAVE was initialized. The difference between this and *CAVETime is that CAVEGetTime computes the time when it is called, whereas *CAVETime is only updated once per frame.
void CAVEGetVector(int vectorid,float vector[3])
Computes a given tracker unit vector. The vector to return is specified by vectorid, which can be one of: CAVE_HEAD_FRONT, CAVE_HEAD_BACK, CAVE_HEAD_LEFT, CAVE_HEAD_RIGHT, CAVE_HEAD_UP, CAVE_HEAD_DOWN, CAVE_WAND_FRONT, CAVE_WAND_BACK, CAVE_WAND_LEFT, CAVE_WAND_RIGHT, CAVE_WAND_UP, CAVE_WAND_DOWN. The unit vector is returned in vector.
void CAVEHeadTransform(void)
Sets up a transformation using the head tracking data, which can be used to position an object at the same location and with the same orientation as the user's head.
int CAVEInStereo(void)
Returns 1 if the CAVE is displaying stereoscopic images, 0 if it is monoscopic.
int CAVEMasterDisplay(void)
Returns true for the process which is drawing the 'master' wall, false for all others. This should be used when exactly one display process should execute something.
void CAVENavConvertCAVEToWorld(float inposition[3],float outposition[3])
Converts a position (inposition) in physical CAVE coordinates (such as a tracker position) to navigated world coordinates. The converted position is returned in outposition.
void CAVENavConvertVectorCAVEToWorld(float invector[3],float outvector[3])
Converts a vector (invector) in the physical CAVE coordinate system to the navigated world coordinate system. The converted vector is returned in outvector.
void CAVENavConvertVectorWorldToCAVE(float invector[3],float outvector[3])
Converts a vector (invector) in the navigated world coordinate system to the physical CAVE coordinate system. The converted vector is returned in outvector.
void CAVENavConvertWorldToCAVE(float inposition[3],float outposition[3])
Converts a position (inposition) in navigated world coordinates to physical CAVE coordinates (the coordinate system used by the trackers). The converted position is returned in outposition.
void CAVENavLoadIdentity(void)
Resets the navigation transformation matrix to identity.
void CAVENavRot(float angle, char axis)
Performs a rotation of the CAVE, adding it to the navigation transformation. angle is in degrees; axis should be 'x', 'y', or 'z'.
void CAVENavScale(float xscale, float yscale, float zscale)
Performs a scaling of the CAVE, adding it to the navigation transformation.
void CAVENavTransform()
Applies the current navigation transformation. This should be called in the draw routine when you wish to use world (navigated) coordinates rather than physical (tracker) coordinates.
void CAVENavTranslate(float xtrans, float ytrans, float ztrans)
Performs a translation of the CAVE, adding it to the navigation transformation.
CAVELOCK CAVENewLock(void)
Creates a new CAVE lock structure which can be used for mutual exclusion between CAVE processes using shared memory. A CAVE lock has two modes - read locking and write locking. Any number of processes can set a lock for read locking simultaneously; only one process can write lock it at any time. The lock returned by this function can be passed to CAVESetReadLock(), CAVESetWriteLock(), CAVEUnsetReadLock(), CAVEUnsetWriteLock(), and CAVEFreeLock(). Roughly 1300 locks can be allocated given the current size of the CAVE library's arena.
void CAVEResetTracker(void)
Signals the tracking process to reset the tracker hardware.
void CAVESetReadLock(CAVELOCK lock)
Sets a CAVE lock to indicate that the calling process will be reading the associated shared data. While the read lock is set, any number of other processes may also obtain read locks, but any processes requesting write locks will be blocked until all the read locks are released (by CAVEUnsetReadLock()).
void CAVESetWriteLock(CAVELOCK lock)
Sets a CAVE lock to indicate that the calling process will be writing the associated shared data. While the write lock is set, no other process may obtain a read or write lock on the given CAVE lock. The write lock is released by CAVEUnsetWriteLock().
void *CAVEUserSharedMemory(int size)
Creates a shared memory arena which can be used by your program. The argument is the size of the arena in bytes. The return value is a pointer to the arena which can be passed to amalloc in order to allocate space from it (be aware that amalloc requires some extra space for overhead - a few hundred bytes of general overhead, plus 16 bytes per amalloc'ed chunk of memory). This function should be called before CAVEInit, so that all processes will have access to the shared memory.
void CAVEWandTransform(void)
Sets up a transformation using the wand's tracking data, which can be used to position an object at the same location and with the same orientation as the wand.

5.3.4. Macros for the wand tracker

CAVEGetWand(x, y, z)
The position of the wand is stored in (x, y, z). The values are floats.

CAVEGetWandOrientation(azi, elev, roll)
The orientation of the wand is stored in (azi, elev, roll). The values are floats.

CAVEGetWandFront(sx, sy, sz)
(sx, sy, sz) is a unit vector pointing in the same direction as the wand.

CAVEGetWandLeft(sx, sy, sz)
(sx, sy, sz) is a unit vector pointing to the wand's left.

CAVEGetWandUp(sx, sy, sz)
(sx, sy, sz) is a unit vector pointing in the wand's up direction.

CAVEWandOrientation
Macro that expands as the GL transformations necessary to orient an object in the same direction as wand. For example, if you want to draw a cylinder at the end of the wand with the wand's orientation, do the following:
        pushmatrix();
        CAVEWandOrientation;
        CAVEGetWand(wx, wy, wz);
        translate(wx, wy, wz);
        draw_cylinder(); /* Your routine */
        popmatrix();

5.3.5. Macros for the head tracker

CAVEGetHead(x, y, z)
The head position is returned in (x, y, z).
CAVEGetHeadOrientation(azi, elev, roll)
The head orientation is returned in (azi, elev, roll).
CAVEGetHeadFront(hx, hy, hz)
CAVEGetHeadLeft(hx, hy, hz)
CAVEGetHeadUp(hx, hy, hz)
Macros that return unit vectors pointing in the head tracker's front, left, or up direction.
CAVEHeadOrientation
Macro that expands as the GL transformations necessary to orient an object in the same direction as the head.

5.4. Form of a basic CAVE program

5.4.1. Program code

        #include <cave.h>

        void app_shared_init(), app_compute_init(),
	     app_init_gl(), app_draw(),
	     app_compute();

        main(int argc,char **argv)
        {
	 CAVEConfigure(&argc,argv,NULL);
	 app_shared_init(argc,argv);  
	 CAVEInit();  
	 CAVEInitApplication(app_init_gl,0);  
	 CAVEDisplay(app_draw,0);       
	 app_compute_init(argc,argv);  
	 while (!getbutton(ESCKEY))
                app_compute();
         CAVEExit();
        }

5.4.2. Program flow

CAVEConfigure(): This routine reads the CAVE configuration file, and parses argc/argv for any user-specified configuration options.

app_shared_init(): This initializes anything that will be shared by the computation and rendering processes (allocating shared memory, etc.). Since all of the program's data that is not in shared memory will be duplicated up to four times by the forks in CAVEInit, any large chunks of data that do not need to be shared should be allocated after CAVEInit, to save memory.

CAVEInit(): This routine initializes the CAVE. The primary operation that it performs is to fork several processes. These processes are all identical at this point. One process (the "computation process") returns from CAVEInit and runs the rest of main(). The other processes handle the tracking and rendering. There is one rendering process for each wall. These processes call the application's GL init function once (whenever one is given), and repeatedly call the application's drawing function.

CAVEInitApplication(), app_init_gl(): A pointer to the application's graphics initialization function is passed to the rendering process. Since the rendering is not done by the computation process (the one that returns from CAVEInit), but by a separate rendering process, any GL initialization needed for the rendering cannot be done directly by the computation process. Instead, this function sets a pointer in shared memory to tell the rendering process what function to call.

CAVEDisplay(), app_draw(): A pointer to the application's drawing function is passed to the rendering process. As in the CAVEInitApplication routine, this sets a pointer in shared memory to the function. The rendering process then sees this pointer and calls the function itself.

app_compute_init(): This initializes any non-shared data that will be used by the computation process.

app_compute(): This performs the application's computations. Any results that are used by the drawing function should be stored in shared memory.

CAVEExit(): This causes all CAVE processes to exit and restores the machine to its normal state.

Note: If your program does nothing in the computation process (i.e. app_compute() is empty), you should probably call sginap so that the computation process doesn't waste a lot of CPU time that the other processes could use. CAVEInitApplication does not have to be called if it is not needed. When it is used, it should be called after CAVEInit and before CAVEDisplay.

5.5. Compiling a CAVE program

A program that uses the CAVE should include the file "cave.h" and be linked with the CAVE library, as well as the GL and math libraries (-lcave -lgl_s -lm). To use audio functions, include "vssClient.h" (after "cave.h") and link with the sound library (-lsnd). All CAVE include files and libraries are in /usr/local/CAVE/include and /usr/local/CAVE/lib.

6. CAVE Simulation

6.1 Introduction

The CAVE Library provides options to simulate some or all of the hardware-specific parts of the CAVE environment. This allows application developers to write and test code on ordinary workstations, without requiring constant use of the CAVE hardware.

There are three basic parts of the CAVE which can be simulated - the tracker input, the wand input, and the immersive display. When running a CAVE program, the configuration file (see Section 8) can be used to select the simulator mode for these options (note that the "Simulator y" option is available as a shorthand method of selecting all simulator options at once). The simulated tracking and wand use the keyboard and mouse for controls; the simulated display provides a perspective view, not limited to a single wall, and also an outside-the-CAVE third-person view.

6.2 Simulated tracking

Simulated tracking is selected by the configuration option "TrackerType simulator". The controls for moving the simulated head and wand are given below.

6.2.1 Head Controls

The simulated user's head can be moved and rotated within the CAVE using the arrow keys. Note that the head is restricted to remain within the confines of physical CAVE. The commands to control the head are:
LEFT_ARROW ............ Move left
RIGHT_ARROW ........... Move right
UP_ARROW .............. Move forward
DOWN_ARROW ............ Move backward
SHIFT + UP_ARROW ...... Move up
SHIFT + DOWN_ARROW .... Move down
ALT + LEFT_ARROW ...... Rotate left
ALT + RIGHT_ARROW ..... Rotate right
ALT + UP_ARROW ........ Rotate up
ALT + DOWN_ARROW ...... Rotate down
P .......... Reset head and wand to initial positions

6.2.2 Wand Controls

The wand is controlled using the mouse. Moving the mouse while holding down the appropriate key will move or rotate the wand. As with the head, the wand is restricted to stay inside the CAVE. When the user's head is moved, the wand is moved with it.

The wand movement controls are as follows:

CTRL + mouse movement ....... Move wand left/right/forward/back
SHIFT + mouse movement ...... Move wand left/right/up/down
ALT + mouse movement ........ Rotate wand left/right/up/down
< and > .......... Roll wand (rotate about Z)
HOME ....... Reset wand to be in front of user

6.3 Simulated wand controls

The simulated wand controls (buttons & joystick) are selected by the configuration option "Wand simulator".

Pressing the mouse buttons corresponds to pressing the wand buttons. Holding down the spacebar while moving the mouse controls the joystick values. Note that the joystick controls set the X and Y values based on the current position of the mouse on the screen, rather than the mouse's relative movement (i.e. the top of the screen is Y=1.0, etc.). The joystick is reset to (0,0) when the spacebar is released.

6.4 Simulated display

The simulated display is selected by using the "simulator" wall (or "simulator1" or "simulator2") in the Walls configuration option.

There are three display modes for the simulator wall. In mode 0, it displays what would be rendered on one of the CAVE walls; in mode 1, it displays a normal perspective view of the application's environment from the position of the user's head; and in mode 2, it displays a third-person view showing the user inside the CAVE. The simulator views can also show the position of the user's head and of the wand, the current frame rate, and the outline of the physical CAVE, and can black-out the parts of the scene which would not be visible due the lack of right, back, and ceiling walls.

The keyboard controls for these options are:

0 ...... Switch to "wall-view" mode
1 ...... Switch to user centered Perspective mode
2 ...... Switch to Outside the CAVE mode
T .......... Toggle timing (frame rate) display
W .......... Toggle display of wand
U .......... Toggle display of user (head)
INSERT ..... Toggle display of CAVE outline
DEL ........ Toggle blackout of right, rear, and ceiling walls
H .......... Print help text
When in wall-view mode (mode 0), the following keys select which wall's display is rendered:
F ........ front wall
L ........ left wall
B ........ floor
R ........ right wall
When using the outside-the-CAVE view, you can move the viewpoint around with the following controls:
KEYPAD ARROWS (2,4,6,8) .... Rotate the viewpoint
KEYPAD -/+ ................. Zoom in/out
KEYPAD 5 ................... Reset the viewpoint

7. Supporting software

There are several auxiliary programs which are used either by the CAVE library or for testing the CAVE hardware. These programs can be found in /usr/local/CAVE/bin.
DisplayMode
This is a simple script which parses the output of /usr/gfx/gfxinfo in order to determine the current video mode of the display. The argument is the graphics display number to check; if none is given, display 0 is checked. The output is a string like "1025x768_96s". The script is run by the CAVE library during initialization to determine whether the video mode needs to be changed. If the script fails (which it will on older systems that do not report the video mode in gfxinfo), the non-CAVE display mode given in the configuration file will be used.
mplock
mpunlock
These programs will isolate and unisolate CPUs on the system. The arguments are the numbers of the CPUs which are to be isolated/unisolated. If the CPULock configuration option is set, mplock will be run by the CAVE library on startup, and mpunlock will be run when the CAVE exits. These programs are separate from the CAVE library because CPU isolation requires superuser privileges; hence, the mplock and mpunlock binaries must be owned by root and have their setuid permissions bit set.
vss
The sound server. vss must be running on the audio server machine (whose IP address is given in the configuration file) before a CAVE program starts. See the CAVE Audio Library manual for more information.
test.pattern.96
This program generates test patterns used for aligning the CAVE projectors and matching colors between screens. It is intended to be run when the display is in 1025x768 96hz video mode. The optional argument is the size of the CAVE (in feet); the default is 10'. The different patterns are selected using the number keys (1, 2, 3, and 4). Pattern 1 is a grid of 6"x6" squares, with diagonals and circles, and an "L" in the leftbuffer and "R" in the rightbuffer. There are diagonals and circles for both the full screen and for 90% of the screen height (for the 10'x9' walls). Pattern 2 displays colorbars running both vertically and horizontally. Patterns 3 & 4 are the same as patterns 1 & 2, but rotated 45 degrees; these are intended for the setup of the ARPA Enterprise CAVE's floor.
cavevars
This is a basic confidence test for the CAVE library and hardware. It displays the values of all the CAVE library's global variables on the front wall, left wall, and floor. The values include the tracker data, the eye positions derived from the tracker data, the status of the wand buttons & joystick, and the timing information. The string "Left Eye" is displayed in the left buffer, and "Right Eye" in the right buffer. A set of X/Y/Z axes are displayed at the wand position and at the head position (using the CAVE library tracker vector macros). To exit the program, press Escape.

8. CAVE Configuration File

The CAVE configuration file lists a number of setup options for the CAVE which may change, but which would not normally be set by your program. These include such things as which walls to use, and offsets for the physical location of the tracker devices. When a CAVE program starts, the function CAVEConfigure will read the configuration file and save the information in a global record used by the various CAVE library functions. The default, system wide configuration file /usr/local/CAVE/etc/cave.config is read first. After reading this, CAVEConfigure will look for the file .caverc in your home directory; if it is found, it is read, and any entries there will override the settings from the default configuration. This means that your .caverc file should only contain those values which you wish to change from the default; most of the values, such as those for the trackers, should only be specified in the system configuration file.

The configuration file is a text file with one configuration setting per line. Each setting consists of a keyword followed by one or more values for that configuration variable. Lines beginning with # are comments. The parsing of keywords by CAVEConfigure is case-insensitive. All options which specify linear measurements should have their units given at the end of the line; the units that may be used are inches, feet, centimeters, and meters; if no units are given, feet are assumed. The configuration options that use units are: InterocularDistance, HeadSensorOffset, WandSensorOffset, TransmitterOffset, Origin, CAVEWidth, and CAVEHeight. The possible keywords and their meanings are:

AudioServer <ip-address>
The numerical IP address of the host which is running the audio server. CAVEConfigure will set the environment variable SOUNDSERVER using this value.
BirdsHemisphere <hemisphere>
Specifies which hemisphere to have the Flock-of-Birds tracker to use. hemisphere should be one of "lower", "upper", "front", "aft", "left", or "right".
Calibration y|n
Whether or not to use calibration for the Flock-of-Birds tracker.
CalibrationFile <filename>
The name of the file containing the Flock-of-Birds calibration data.
CAVEWidth <width> <units>
The width of the physical CAVE. This is assumed to be the depth as well.
CAVEHeight <height> <units>
The height of the physical CAVE.
CPULock y|n
Whether to "lock" the CPUs or not. If this is 'y', the rendering processes will be forced to run on CPUs 1-3 (one process per CPU) and the CPUs will be isolated so that no other processes can use them. The tracker process will also be run on an isolated CPU, if there are enough available. CAVEExit will un-isolate the CPUs. This requires the programs mplock and mpunlock (in /usr/local/CAVE/bin). If your CAVE program crashes while using this option, you should run mpunlock manually to un-isolate the processors.
DeskCorners <lower-left x y z> <upper-left x y z> <lower-right x y z> <units>
Defines the physical geometry of the Immersadesk screen, in CAVE coordinates. The values consist of nine numbers, the X, Y, and Z coordinates of the lower left, upper left, and lower right corners of the screen, followed by the units of measure. This is used to compute the perspective projection for the Immersadesk display, and so must be given if "Walls desk" (see below) is selected.
DisplayMode <mode>
DisplayMode setmon <setmon-mode>
Which video mode to use for display. The options for <mode> are "mono", "stereo96", and "stereo120", for monoscopic, 96 hz stereoscopic, and 120 hz stereoscopic. Disabling stereo will destroy much of the immersive effect of the CAVE, but can increase the frame rate, and so may be useful for those users who cannot see stereo. The "setmon" option allows you to directly specify the VOF argument for setmon in <setmon-mode>.
HeadSensorOffset <X-offset> <Y-offset> <Z-offset> <units>
Offset values from the position of the head sensor to the point between the user's eyes. The offset is added to the position reported by the head tracker to yield the position that the CAVE library will report.
HideCursor y|n
Whether or not to hide the cursor when in the CAVE windows.
InterocularDistance <distance> <units>
The distance between the user's eyes. 2.75 inches is a typical value which works well for most users.
NonCAVEDisplayMode <mode>
NonCAVEDisplayMode setmon <setmon-mode>
Which video mode is used when the CAVE is not running. The library will assume that the displays are in this mode before the CAVE is started, and will return the displays to this mode when CAVEExit is called. The options for <mode> are "96hz", "60hz", and "72hz". The "setmon" option allows you to directly specify the VOF argument for setmon in <setmon-mode>.
Origin <left-distance> <floor-distance> <front-distance> <units>
Origin of the CAVE coordinate system. This is specified as the distance from the three walls to the origin. For the 10' CAVE, these values are "5 0 5 feet".
ProjectionCorners <wall-name> <lower-left x y z> <upper-left x y z> <lower-right x y z> <units>
Defines the corners of the projection plane for a given wall. This is currently only used by the ImmersAdesk wall and the "left_eye" and "right_eye" head-tracked walls. For the desk, it is equivalent to "DeskCorners". For the head-tracked walls, the three points define the geometry of that eye's projection plane in eye-space coordinates; eye-space coordinates are defined as having the eye at the origin, with the axes aligned with the user's head - the Z axis points directly back, the Y axis points up, the X axis points right.
SerialTracking y|n
Indicates whether tracking should be done serially or in parallel. If SerialTracking is enabled, one of the rendering processes will read the tracker and wand devices; if it is disabled (the default), a separate process is used to read the devices. In general, serial tracking should only be used with the Simulator tracker, where a separate process can put too much of a load on the X server and slow up the rest of an application.
Simulator y|n
Shorthand option for selecting full simulator mode. Using the configuration "Simulator y" is equivalent to specifying the options "Walls Simulator", "WallDisplay simulator -1 window", "Tracking y", "TrackerType simulator", "Wand simulator", "WandSensorOffset 0 0 0", "WandSensorElevation 0", "UseCalibration n", and "SerialTracking y". NB: Saying "simulator n" will merely set the flag CAVEConfig->Simulator to 0; it will not undo any other effects of a previous "simulator y".
TrackerBaudRate 19200|9600|...
Baud rate for the tracker serial device. 19200 is the default.
TrackerLog <logfile>
Specifies a file into which the tracker data will be written as the CAVE is running. The file will contain a header record (a CAVE_LOG_HEADER_ST struct), followed by one log entry per frame. Each log entry consists of the CAVE time, the head and wand sensor structs, and the states of the wand buttons and joystick. In the simulator, if this option is given, the file will be read rather than written, and the data used for the simulated trackers. This allows you to replay a CAVE session with the simulator. No logging is done if no <logfile> is given.
TrackerPort <portname>
Name of the serial port(s) that the trackers are attached to. <portname> should be something like /dev/ttym2.
TrackerType <name>
Which type of tracking hardware is being used. <name> should be one of "birds", "logitech", "mouse", "simulator", "spaceball", or "none". "mouse" selects a simple mouse-controlled tracking system, which can be useful for testing purposes. "simulator" selects a simulated tracking system that uses keyboard and mouse controls (see Section 6.2). Since both the tracker positions and the wand controls are handled by the tracker process, "TrackerType none" can be used to have this process only check the wand controls.
Tracking y|n
Whether or not to run the tracker process. If tracking is disabled, the head and wand are assigned fixed default positions at the center of the CAVE, and all the buttons and joystick values are set to 0.
TransmitterPosition <X-offset> <Y-offset> <Z-offset> <units>
The position of the transmitter in CAVE coordinates. This is used to adjust the values returned by the trackers.
TransmitterOrientation <X-rotation> <Y-rotation> <Z-rotation>
The orientation of the transmitter in the CAVE space. This is used to adjust the values returned by the trackers.
Units <units>
What units to use for the CAVE coordinates. <units> should be either "feet" or "meters". Tracking data will be reported in the given units, and the graphics projections will be in those units. "feet" is the default.
WallDisplay <wall-name> <pipe#> [<window-geometry>]
Describes where a given wall's window will be displayed on the workstation. <wall-name> can be one of "front", "left", "right", "floor", "back", "ceiling", "arpafloor", "desk", or "simulator".
<pipe#> is the number of the graphics pipe used by the wall. If <pipe#> is -1, the wall will inherit your shell's DISPLAY variable, rather than redefining it. Also, an X display (e.g. "evl:0.0") can be specified in place of a pipe number; this can be used to display the wall on a remote machine in place of the local hardware (note that the CAVE library will be unable to change the remote machine's video display mode when this option is used).
<window-geometry> defines the area on the display for the window; it is given in the format "XDIMxYDIM+XOFFSET+YOFFSET" (e.g. 512x512+300+100). If the string "window" is given instead of a size and offsets, the wall will be displayed in a normal, bordered window which can be moved and resized. If no geometry is given, it will default to the full screen.
Walls <wall> <wall> ...
Lists which walls are active. <wall> can be one of "front", "left", "right", "floor", "back", "ceiling", "arpafloor", "desk", "dual_eye", "left_eye", "right_eye", "simulator", "simulator1", or "simulator2". The display information (see WallDisplay) must be provided for a wall to be used (the system config file should already contain that information for all walls that are physically available).
The "simulator" wall selects the simulator-style display (see Section 6.4. The "simulator1" wall is a simulator display that is always in viewing mode 1; the "simulator2" wall is always in mode 2.
The "desk" wall is intended for the Immersadesk; if it is selected, the DeskCorners configuration data must also be given.
The "arpafloor" wall produces a projection for the floor of the ARPA Enterprise CAVE, which is rotated by 45 degrees about Y. It also masks out the portions of the window which should not be displayed (after the application's display function is called).
The "dual_eye" wall produces side-by-side stereo images, for the BOOM2 display.
The "left_eye" and "right_eye" walls each produce a monoscopic head-coupled view from the position of the given eye. The are for use with an HMD or BOOM3. The "ProjectionCorners" data must also be given for these walls.
Wand <wand-type>
The type of wand being used. <wand-type> should be one of "mouse", "PC", "logitech", "simulator", or "none". This determines what controls are read (buttons/joystick). The "simulator" option will use the mouse and spacebar to simulate the PC wand's buttons and joystick. The "logitech" option should only be used in conjunction with "TrackerType logitech".
WandSensorElevation <angle>
The amount to rotate the wand sensor's data around the X axis, to compensate for the sensor's orientation. <angle> should be in degrees.
WandSensorOffset <X-offset> <Y-offset> <Z-offset> <units>
Offset values for the position of the wand sensor. The offset is added to the position reported by the wand tracker to yield the position that the CAVE library will report.

9. Sample Programs

9.1. CAVE sample program 1

/* simple.c
/*    A trivial CAVE demo program, demonstrating the most basic CAVE library
/*   functions. This program just draws a red triangle in the front of the
/*   CAVE. No interaction (outside of moving around), and nothing changes.
*/

#include <cave.h>

void simple_draw(void);

main(int argc,char **argv)
{
/* Initialize the CAVE */
 CAVEConfigure(&argc,argv,NULL);
 CAVEInit();
/* Give the library a pointer to the drawing function */
 CAVEDisplay(simple_draw,0);
/* Wait for the escape key to be hit */
 while (!getbutton(ESCKEY))
	sginap(10);
/* Clean up & exit */
 CAVEExit();
}


/* simple_draw - the display function. This function is called by the
  CAVE library in the rendering processes' display loop. It draws a red
  triangle 2 feet tall, 4 feet off the floor, and 1 foot in front of the
  front wall (assuming a 10' CAVE). */
void simple_draw(void)
{
 float vert1[3] = { -1, 4, -4},
	vert2[3] = { 1, 4, -4},
	vert3[3] = { 0, 6, -4};
 cpack(0); clear(); zclear();
 cpack(0xff);
 bgnline();
   v3f(vert1);
   v3f(vert2);
   v3f(vert3);
   v3f(vert1);
 endline();
}

9.2. CAVE sample program 2

#include <cave.h>

void init_shared_data(),gl_init_fn(),draw_fn(),draw_ball();

/* Shared data */
void *shared_arena;
float *x,*y,*z;

main(int argc,char **argv)
{
 float vx,vy,vz;
 CAVEConfigure(&argc,argv,NULL);
 init_shared_data();
 vx = (drand48()-0.5)/10.0;
 vy = (drand48()-0.5)/10.0;
 vz = (drand48()-0.5)/10.0;
 CAVEInit();
 CAVEInitApplication(gl_init_fn,0);
 CAVEDisplay(draw_fn,0);
 while (!getbutton(ESCKEY))
	{	/* Bounce the ball around */
	if (*x < -5) vx = fabs(vx);
	else if (*x > 5) vx = -fabs(vx);
	if (*y < 0) vy = fabs(vy);
	else if (*y > 10) vy = -fabs(vy);
	if (*z < -5) vz = fabs(vz);
	else if (*z > 5) vz = -fabs(vz);
	*x += vx;
	*y += vy;
	*z += vz;
	sginap(1);  /* Make this loop run about 100 iterations/second */
	}
 CAVEExit();
}

/* Get a shared arena, amalloc the shared variables, & initialize them */
void init_shared_data()
{
 shared_arena = CAVEUserSharedMemory(1024);
 x = (float *) amalloc(sizeof(float),shared_arena);
 y = (float *) amalloc(sizeof(float),shared_arena);
 z = (float *) amalloc(sizeof(float),shared_arena);
 *x = 0;
 *y = 5;
 *z = 0;
}

/* initialize the graphics - define the lighting data */
void gl_init_fn()
{
 float light_data[] = { LCOLOR, 1.0, 1.0, 1.0,
                               POSITION, -5.0, 10.0, 5.0, 0.0,
                               LMNULL };
 float material_data[]={ DIFFUSE, 0.6,0.5,0.1,
                                AMBIENT, 0.2, 0.2, 0.0,
                                LMNULL };
 lmdef(DEFLMODEL,1,0,NULL);
 lmbind(LMODEL,1);
 lmdef(DEFLIGHT,1,0,light_data);
 lmdef(DEFMATERIAL,1,0,material_data);
}

/* draw the scene - draw a ball at (*x,*y,*z), and, if button 1 is pressed,
 draw a smaller ball in front of the wand */
void draw_fn()
{
 cpack(0xffff80); clear(); zclear();
 lmbind(LIGHT0,1);
 lmbind(MATERIAL,1);
 pushmatrix();
   translate(*x,*y,*z);
   draw_ball();
 popmatrix();
 lmbind(MATERIAL,0);
 if (CAVEBUTTON1)
	{
	float wand_x,wand_y,wand_z, wand_fx,wand_fy,wand_fz;
	CAVEGetWand(wand_x,wand_y,wand_z);
	CAVEGetWandFront(wand_fx,wand_fy,wand_fz);
	cpack(0xff00ff);
	pushmatrix();
	  translate(wand_x+wand_fx*2,wand_y+wand_fy*2,wand_z+wand_fz*2);
	  scale(0.25,0.25,0.25);
	  draw_ball();
	popmatrix();
	}
}

/* draw a unit sphere */
#define STEP (M_PI/6)
void draw_ball()
{
 float lat,lon,v[3];
 bgntmesh();
 for (lat=-M_PI; lat<M_PI; lat+=STEP)
    for (lon=0; lon<=2*M_PI; lon+=STEP)
	{
	v[0] = sin(lon)*cos(lat);
	v[1] = cos(lon)*cos(lat);
	v[2] = sin(lat);
	n3f(v);
	v3f(v);
	v[0] = sin(lon)*cos(lat+STEP);
	v[1] = cos(lon)*cos(lat+STEP);
	v[2] = sin(lat+STEP);
	n3f(v);
	v3f(v);
	}
 endtmesh();
}