CAVEExt

Introduction
Initialization and quiting
Locks
win32 compatibility
matrix math

Introduction

CAVEExt is a set of extension to the CAVE library. There were three reasons to write them. Relaxed locks, Win32 compatibility and matrix math.

I required more relaxed locks because the standard CAVE locks annoyed me. When you write an library/API (like the probing library) which internally uses functions which are available to the outside world as well, you run into trouble if these functions use locks. You have unset and set locks all the time. It would be much easier to have locks which allow a process/thread to set a writelock if it already has a writelock, or to set a readlock if it already has a read/writelock. CAVEExtLocks do just that.

I wrote some functions for win32 compatibility so I could use my win32 cave library. The main compatiblity problem was the fact that my cave library is multithreaded, while the IRIX cave library uses multi-processing. I think multithreading is faster, better, and easier to debug. However, since the address space is shared by all threads, you can not easily use variables which are used in one thread only (for instance, the name of a displaylist). I 'invented' something I called process local memory. The application allocates an id and each of the processes/threads of the application can use this id to allocate/set/get/free it's own process local memory.

I used the set of 4x4 homogenous matrix routines included in the CAVEExt library for some time, in several projects, so I decided it would be best to put them in a library. The math routines are not efficient/consistent (and maybe even not correct all the time), but they work quite well.

Initialization and quiting

void CAVEExtInit();
Call this function after calling CAVEConfigure(). It initializes the CAVEExt library.

void CAVEExtQuit();
Call this function when you want to quit the CAVEExt library. It frees resources used by the library. Make sure you have unset all CAVEExtLocks when you call CAVEExtQuit(), or some resources may not be freed, and errors will be reported.

Locks

CAVEExtLocks are more 'relaxed' locks than the standard CAVELocks. Below is a summary of when setting a CAVEExtLock blocks and when not. In the table, the word 'process' means either a process or a thread, depending on whether multiprocessing or multithreading is used. When you (un)set multiple (read-/write-)locks within one process, a counter is incremented/decremented. In the situations listed below as blocking, other processes/threads can not set the lock until the counter is has reached 0. This means that for every time you call CAVEExtSet...Lock(), you must call CAVEExtUnset...Lock(), or other processes/threads might block indefinitely.

Situation

process wants to set a read lock

process wants to set a write lock

no process has a lock set

doesn't block doesn't block

the process setting the lock already has a read lock set

doesn't block doesn't block

the process setting the lock already has a write lock set

doesn't block doesn't block

other processes have read locks set

doesn't block blocks until all other processes have unset their read locks

another process has a write lock set

blocks until the other process has unset the writelock blocks until the other process has unset the writelock

CAVEExtLock CAVEExtNewLock();
CAVEExtNewLock() returns a new CAVEExtLock. You can free it's resources with CAVEExtFreeLock()

void CAVEExtFreeLock(CAVEExtLock Lock);
Frees resources used by Lock. Make sure all read and/or writelocks have been unset before calling CAVEExtFreeLock().

void CAVEExtSetWriteLock(CAVEExtLock Lock);
Sets a writelock on Lock. Blocks until the writelock can be set. For each call to CAVEExtSetWriteLock(), you must call CAVEExtUnsetWriteLock() from within the same process/thread to unset the lock.

void CAVEExtUnsetWriteLock(CAVEExtLock Lock);
Unsets a writelock on Lock.

void CAVEExtSetReadLock(CAVEExtLock Lock);
Sets a readlock on Lock. Blocks until the readlock can be set. For each call to CAVEExtSetReadLock(), you must call CAVEExtUnsetReadLock() from within the same process/thread to unset the lock.

void CAVEExtUnsetReadLock(CAVEExtLock Lock);
Unsets a readlock on Lock.

win32 compatibility

Most functions for win32 compatibility actually have to do with multithreading compatibility. My win32 cave library is multithreaded and not multiprocessing. One of the differences between between multithreading and multiprocessing is that the memory address space is shared between all threads of the program. While this makes shared memory easy (all memory is shared...), it makes thread-local memory difficult. In multiprocessing mode it is easy to have process-local memory, because the memory is not shared with the other processes by default. Shared memory is difficult and special shared memory heaps have to be created.

Why would one want to have process-local or thread-local memory? To store the names of openGL displaylists for example. Because each thread/process has it's own opengl context, names of displayslists will be different for each thread/process. When multiprocessing, this is not a problem. You just declare a global variable DisplayListName and put you displaylist name in there. When multithreading, this global variable is shared between all threads, so the threads overwrite eachothers displaylist names, and thus use incorrect names.

The CAVEExt library solves this problem as follows: At the beginning of your CAVE application, you allocate IDs for process local memory. Each thread/process can then allocate/store/retrieve it's own memory for/at/from those IDs.

For instance, you need to store the names of 3 displaylists. Your program could then call CAVEExtNewID() 3 times, and store those IDs in global variables. Then you program splits up into, say, 4 display processes/threads, one for each wall of the CAVE. Each display process/thread calls CAVEExtAllocProcessLocalMem() 3 times to allocate space to store 3 GLuints (the names of your 3 displaylists). Each display thread/process can then call glGenLists() 3 times and store the values returned it the process local memory. When the time has come to display the displaylists, each thread/process retrieves the names of the 3 displaylists by calling CAVEExtGetProcessLocalMem() and tells openGL to display those displaylists. When your program is about to terminate, each process/thread should call CAVEExtFreeProcessLocalMem() for each ID to free the process local meemory.

int CAVEExtNewID();
Returns a unique ID for process local memory. This ID does not have to be 'freed' or anything. This limits the number of times CAVEExtNewID can successfully be called to 2^31-1.

void *CAVEExtAllocProcessLocalMem(int ID, int Size);
Allocates Size bytes of memory for ID. If some address is already stored at ID for the calling process/thread, the old address is overwritten (and memory leaks could occur). To free the memory, call CAVEExtFreeProcessLocalMem().
The address of the newly allocated memory is returned.

void *CAVEExtReAllocProcessLocalMem(int ID, int NewSize);
Resizes the size of the block of memory stored at ID to NewSize bytes. The contents of the memory block is preserved.
The address of the newly allocated memory is returned.

void CAVEExtFreeProcessLocalMem(int ID);
Frees the memory stored at ID.

void *CAVEExtGetProcessLocalMem(int ID);
Returns the address of the block of memory stored at ID.

void CAVEExtSetProcessLocalMem(int ID, void *Data);
This functions if usefull when you do not want CAVEExtAllocProcessLocalMem to allocate your memory for you. You can allocate, reallocate, free your memory anyway you like (for instance with malloc(), realloc() and free(), or with the C++ new and delete operators), and store the address of that memory at ID. Do not call CAVEExtFreeProcessLocalMem() and CAVEExtReAllocProcessLocalMem() for ID anymore, since the CAVEExt library has no idea how you allocated your memory.

void CAVEExtSetOption(CAVEExtID ID, int value);
Can be used to set option for the CAVEExt library. Currently however, there are no options to be set...

int CAVEExtGetPID();
Returns a unique positive value in each thread/process. In a UNIX environment, this function just calls getpid(), in a win32 environment, this function calls GetCurrentThreadId()

void CAVEExtSleep(int Millis);
Makes the calling process/thread sleep more Millis milliseconds. If Millis is 0, the process/thread releases control to other process/thread.

matrix math

This set of 4x4 homogenous transformation matrix routines is usefull in the CAVE. I use it to transform vertices, create new transformation matrices for scratch, calculate distance between vertices, project vertices on planes, and much more. The names and arguments of the routines mostly explain what they do.

void CAVEExtMatrixMult(float Result[4][4], float M1[4][4], float M2[4][4]);
void CAVEExtMatrixToRot(float Matrix[4][4], float Angles[3]);
Tries to decompsose Matrix into three seperate rotations about the XYZ axes. The angles stored in Angles are in radians. You can use CAVEExtMatrixYXZRotate() to do the opposite, go from angles to a matrix.
void CAVEExtMatrixYXZRotate(float Matrix[4][4], float Angles[3]);
Sets matrix to the transformation matrix specified by Angles. The order of rotation is Y, X, Z (the 'CAVE order').
void CAVEExtMatrixIdentity(float Matrix[4][4]);
void CAVEExtMatrixCopy(float Dest[4][4], float Source[4][4]);
void CAVEExtMatrixRotate(float Matrix[4][4], char Axis, char Side, float Angle);
Rotates Matrix about Axis ('x', 'y' or 'z') at Side ('l' or 'r'), about Angle (in radians).
void CAVEExtMatrixTranslate(float Matrix[4][4], char Side, float Trans[3]);
void CAVEExtMatrixScale(float Matrix[4][4], char Side, float Scale[3]);
void CAVEExtMatrixSetToRotation(float Matrix[4][4], char Axis, float Angle);
void CAVEExtMatrixSetToTranslation(float Matrix[4][4], float Trans[3]);
void CAVEExtMatrixSetToScale(float Matrix[4][4], float Scale[3]);
void CAVEExtMatrixPrint(float Matrix[4][4], char *Text);
void CAVEExtMatrixNormalize(float Matrix[4][4]);
void CAVEExtMatrixglLoad(float Matrix[4][4]);
Loads Matrix onto the current openGL matrix stack.
void CAVEExtMatrixglMult(float Matrix[4][4]);
void CAVEExtMatrixglDraw(float Matrix[4][4]);
Draws Matrix in the current openGL context. The x-axis is red, the y-axis green, the z-axis blue
void CAVEExtMatrixInvert(float Result[4][4], float Matrix[4][4]);
void CAVEExtMatrixVertexMult(float Matrix[4][4], float Vertex[3], float Result[3]);
void CAVEExtMatrixVectorMult(float Matrix[4][4], float Vector[3], float Result[3]);
void CAVEExtMatrixSideMult(float M1[4][4], float M2[4][4], char Side);
float CAVEExtMatrixProjectVertex(float Matrix[4][4], float Vertex[3], float Result[3]);
Projects Vertex onto the plane represented by Matrix. The result is stored in Result. The x- and the y-axis of Matrix represent lie in the plane, the z-axis is the normal to the plane.
void CAVEExtVectorNull(float Vector[3]);
void CAVEExtVectorInvert(float Vector[3]);
void CAVEExtVectorCopy(float Dest[3], float Source[3]);
void CAVEExtVectorPrint(float Vector[3], char *Text);
void CAVEExtVectorScalarMult(float V[3], float Scalar);
float CAVEExtVectorLength(float V[3]);
void CAVEExtVectorNormalize(float V[3]);
void CAVEExtVectorCrossProduct(float Dst[3], float Src1[3], float Src2[3]);
float CAVEExtVectorDotProduct(float Src1[3], float Src2[3]);
void CAVEExtVectorSubstract(float Result[3], float Vector1[3], float Vector2[3]);
void CAVEExtVectorAdd(float Result[3], float Vector1[3], float Vector2[3]);
void CAVEExtVectorSet(float V[3], float x, float y, float z);
float CAVEExtVectorCos(float Vector1[3], float Vector2[3]);
Returns the cosinus of the angle between Vector1 and Vector2
float CAVEExtVectorAngle(float Base[3], float P1[3], float P2[3]);
Returns the the angle between Vector1 and Vector2

End