The TwoHand Library

 

Introduction

The TwoHand Library is a library of routines that are built on top of the MR Toolkit. The TwoHand Library provides routines to support one or two 3D position and orientation trackers with added buttons. The purpose of this library is to automatically handle some common functionality that is needed in every desktop VR system that uses 3D trackers. This includes support for automatically setting up the view parameters for a user who is sitting in front of a graphics console, plus the usual geometry management functions that this implies.

The TwoHand Library manages connections to the MR toolkit, and sets up the coordinate systems such that the real position of the 3D display is automatically taken into account. The TwoHand Library also provides facilities for automatic cursor management, grabbing, rotating, and scaling, the application's coordinate system, plus the Sundial menu system, the button events system, the Spotlight selection interaction technique, plus sundry debug and matrix routines. These facilities allow for the management of button events with callbacks and Sundial menu selection events with callbacks. Each interaction technique has its own include file to define function prototypes.

The TwoHand Library defines a few typedefs, such as Point3, Point4, Matrix3f, and Matrix4f, which are explicit data structure definitions for spatial data used by the library. There is also definition for data types RGB and RGBA, which are OpenGL 3-byte and 4-byte color arrays. The main data structure used within the TwoHand Library is the THCursor pointer. This data structure contains all of the spatial data associated with a single 3D position and orientation tracker. This cursor data structure contains redundant data, depicting the cursor's position and orientation in two separate coordinate systems defined in the library. The data is in matrix form, and position + orientation (quaternion). The two coordinate systems are environment and model coordinates.

The Environment coordinate system is the coordinate system defined by the MR Toolkit. In the TwoHand Library, the environment coordinate system is the coordinate system that is related to the position and orientation of the display screen. That is, since the TwoHand program must run on any display screen, the environment coordinate system is determined at run time to encode the display screens location.

The Model coordinate system, on the other hand, is a coordinate system that is used by the application programmer. Since the TwoHand Library provides utilities for grabbing the 3D space and moving it around, the TwoHand Library defines this coordinate system and names it the Model coordinates. When the application starts up, the environment and model coordinate systems are in the same location. As the user manipulates the 3D space, the model system is grabbed, moved, scaled, and so on to suit the user's needs. While the model system is being moved, the environment coordinate system remains static, with the origin placed at the center of projection. The X and Y axes of environment coordinates correspond to the horizontal and vertical axes of the display, respectively. In essence, the environment coordinate system is the position and the orientation of the users virtual eyepoint.

Thus, for each cursor, the TwoHand Library maintains the position and orientation of the cursor in both the environment coordinate system and model coordinate system. The following struct definition shows a subset of the THCursor fields.

 

typedef struct {

 

Matrix4f

Envt ;

/* Cursor transformation in Environment coords */

 

Matrix4f

Model ;

/* Cursor transformation in Model coords */

 

Point3

PE ;

/* Point in Environment */

 

Point3

PM ;

/* Point in Model */

 

Point3

PW ;

/* Point in Window */

 

Point4

QuatE ;

/* Rotation from Environment origin */

 

Point4

QuatM ;

/* Rotation from Model origin */

 

Point3

Grab ;

/* grabbed point in Model coords: constrained */

} THcursor ;

typedef THcursor *THCursor;

 

The left cursor is available using the global variable LeftCursor, and the right is available at RightCursor. The left cursor's position in Model coordinates would thus be accessed by LeftCursor->PM.

 

The main call within the two hand library this THInitialize(), which initializes the MR toolkit, makes connections to the appropriate button and tracker servers, and sets up all of the appropriate cursor data.

A number of cursor related calls exist to perform the grabbing function and to draw the cursors at the appropriate positions. There are also calls to place the cursors at the sweet spot, which is located in front of the users virtual eyepoint at an appropriate position. There are also calls to print debugging data to the debug file, and some supplementary matrix routines that are not found in librot.

 

Sundials

The Sundial menu package is an event-based menu system that uses the TwoHand Library’s cursor information. To use a particular sundial menu, the menu must first be defined by a call to THSundialInit1Menu(). This procedure parses a partially filled data structure defined by the programmer and creates a Sundial menu and all of its sub-menus.

When an application-defined pop-up event arrives, THSundialEcho() must be called once every 3D graphics update to draw and evaluate the Sundial. The programmer is responsible for calling THSundialEcho() because it is assumed that the application programmer will want precise control over drawing order in the visual update loop. THSundialEcho() performs all of the interaction with the given cursor and highlights the menu items as appropriate. When the pop-down event occurs (again defined by the application), then THSundialEvalSelect() must be called. This determines if any menu item was selected, and to activate the callback associated with the selected item.

Three auxiliary routines are also supplied: THSundialSetFlagsR() accepts publicly available flags to be set in a particular sundial menu. This allows the application programmer to set "SUNDIAL_STATIC" mode, in which a sundial remains fixed on the screen once popped up. Also, when sundial menu items have a "TOGGLEBUTTON", the "RADIOBUTTONS" flag states that all TOGGLEBUTTONs on a given sundial act in the radio-button manner.

THSundialGetFlags() returns flags for the top level menu item that is passed as an argument. THParsePolyData() takes a list of 3D normal + vertex triples and uses these to define simple polygonal objects.

 

Buttons

The Button package allows the application programmer to connect to a button server and receive button events from up to 6 buttons. Most application programmers will use only THButtonAddCallback(). This defines a button, a transition such as button-release, and a callback. When the given button changes in the given way, the callback is called. The event type and button name are defined by symbolic constants. This package can be used to define events that cause sundial menu pop-up.

The selection package provides a few functions that perform spotlight selection. These include functions to draw the selection cone, plus routines to pass an array of vertices through the selection function.

 

Procedure Calls

 

void THInitialize(int argc, char **argv)

This function must be called first in any two hand library program. It should be called after the window has been created, but before any drawing is to occur. This call will initialize connections to the MR Toolkit, including connections to button and tracker servers. It will open the standard MR_dbgfile file, and so on. No call should be made to the MR Toolkit initialization functions. argc and argv are passed unexamined to the MR Toolkit MR_init() call.

 

void THUpdate( void )

This function must be called at the top of every update loop. It collects tracker and button data and dispatches any predeclared button events. Once this function has been called, the cursor data structures can be accessed with the latest position and orientation data.

 

void THUseButtons( int yes )

This procedure is used to indicate whether not buttons are to be used. 0== NO, 1== yes.

 

void THCursorCount( int numcursors )

Call this procedure with the number of cursors you want to use. By default, two cursors are used. If numcursors == 1, only RightCursor is used. No connection is made to the left cursor, and LeftCursor == 0. If numcursors == 0, neither cursor is used, and no connections are made to any servers. RightCursor i== 0 and LeftCursor == 0 in this case.

 

void THCursorStartAdjust( THCursor C, int type )

To grab the model coordinate system and adjust it with respect to the environment coordinate system, call THCursorStartAdjust().

This procedure takes cursor C and the type of adjustment and uses the given cursor to grab the model coordinate system. The two available modes indicated by type allow the modification of position and orientation (TH_ADJUST_POSORIENT) or just position (TH_ADJUST_ORIENT). This procedure turns on a mode which must be turned off with THCursorEndAdjust().

 

void THCursorEndAdjust( void )

This procedure turns off cursor adjustment mode.

 

void THCursorEcho( THCursor C, int ListId )

This procedure displays the cursor C using the display list identifier ListId at C’s current position and orientation. The contents of ListId are drawn.

 

void THCursorEchoNoRot( THCursor C, int ListId )

This procedure displays the cursor C at the current position using the display list identifier ListId. It does not rotate the cursor to the tracker’s current orientation, and so yields a screen-aligned cursor.

 

void THCursorBoresight( THCursor C )

This procedure sets cursor C’s X axis to lie along the line of sight. This is used for aligning the selection cone to point into the screen from the cursor’s current position. The line of sight is the line from the center of projection through the cursor position.

 

void THEnterModelCoord( void )

Allow subsequent drawing in model coordinates. When THUpdate() is called, the current coordinate system is Environment coordinates. THEnterModelCoord() must be called in order to commence drawing in model coordinates.

 

void THCursorOrigin( THCursor Cright, THCursor Cleft )

This procedure is called with the left and right cursor in order to move the cursors to the "sweet spot". The sweet spot is in a comfortable location in front of the screen.

 

void THCursorWindowXYZ( THCursor C )

This procedure can be called of you would like to compute the value of PW (Point in Window) for cursor C. This is computed already by THUpdate(), but if you have multiple viewports or multiple projections, call THCursorWindowXYZ while the desired viewport or projection is active.

 

void THJumpZoom( THCursor C, float factor )

This procedure scales the model coordinate system up or down by the quantity factor centered at cursor C. This does not affect environment coordinates, only the relative size of model coordinates with respect to environment coordinates.

 

void THTranslateModel( Point3 dxyz )

This procedure translates the Model coordinate system by the 3D vector dxyz. This does not affect environment coordinates, only the location of model coordinates with respect to environment coordinates. The purpose of this routine is to tell the TwoHand system about translations that the programmer may want on initialization.

 

void THDrawBall( Point3 p, RGB rgb )

This procedure draws a small sphere at point p in the color rgb. The sphere is in fact an octahedron.

 

void THDrawBallScale( Point3 p, float scl, RGB rgb )

This procedure draws a small sphere at point p in the color rgb, scaled by the factor scl.

 

void dbgp( char *fname, int line, char *frmt, ... )

This procedure prints debug output onto the MRToolkit debug file MR_dbgfile. It is a wrapper around fprintf() that prepends the standard printf formatting string and arguments with the name of the source file and the line number where dbgp() is called. The macro _FL (defined in TwoHand.h) can be used to succinctly represent the filename and line number.

 

void dbgpN( char *fname, int line, int level, char *frmt, ... )

Like dbgp(), but only prints if level is greater than the global Debug value. Debug is a global int defined by the TwoHand library that lies in the range [0,10]. Typically, if Debug == 0, then no dbgpN() calls will print anything.

 

void dbgBallStr( Point3 pnt, int color, char *str )

This procedure draws a ball at the given point pnt in the given color and puts the string str in screen aligned text beside the ball.

 

void getXvec( Point3 v, Matrix4f M )

void getYvec( Point3 v, Matrix4f M )

void getZvec( Point3 v, Matrix4f M )

This procedure returns the X, Y or Z vector of the 4x4 matrix M in the variable v.

 

void getTranslation( Point3 v, Matrix4f M )

This returns the translation component of the matrix M in the vector v. This routine accounts for any homogeneous scaling that may exist in M.

 

void putTranslation( Point3 v, Matrix4f M )

This sets the translation component of the matrix M using the vector v. This routine accounts for any homogeneous scaling that may exist in M.

 

void rotCopy( Matrix4f src, Matrix4f dest )

Copies the rotation from the source matrix src to destination matrix dest. This does not adjust for any scaling that may have occurred in src.

 

void rotCopy43( Matrix4f src, Matrix3f dest )

Copies the rotation from the 4x4 source matrix src to 3x3 destination matrix dest. This does not adjust for any scaling that may have occurred in src.

 

void plane_eq( int k, Point3 *v, int *vi, Point4 e )

This calculates the plane equation for a 3D planar polygon given as a list of k 3D points, in v, and returns the equation in e. vi is a k-element array that is used to index into the array v. That is, vi[0] is the index of the first vertex, vi[1] is the index of vertex 2, etc.

If the plane equation is Ax + By + Cz + D = 0, then, A, B, C is the area of projection of the polygon onto the YOZ, ZOX, and XOY planes, respectively.

 

float line_plane( float *v0, float *v1, float *p, float *w )

This procedure intersects the parameterized ray with a plane, and returns the parameter at the intersection. The ray parameter is w. v0 is the ray starting point (w = 0), v1 is the point on the ray where w = 1, and p is the plane equation.

 

int pierce( float *base, float *tip, float *v0, float *v1, float *v2, float *p )

Test whether a vector pierces through a triangle determined by v0, v1, v2. If yes, return the intersection point in P and return TRUE, else, return FALSE. base and tip define the vector.

 

float p2ldist( Point3 p, Point3 q, Point3 r )

Compute the distance between point p and line defined by the points q, r.

 

void THDrawStem( Point3 p1, Point3 p2, scalar radius )

Draw a long thin cylinder "stem" from p1 to p2 at radius r.

 

void THDrawArrow( Point3 p1, Point3 p2, scalar radius )

Draw a long thin cylinder from p1 to p2 at radius r, with an arrowhead at p1.

 

float timediff( struct timeval *prev, struct timeval *cur )

Return the time in seconds that has elapsed from prev until cur. If prev is in fact the earlier time, then timediff() will return a positive value.

 

Buttons

 

ButtonEvent *THButtonAddCallback(int button_id, int event, void (*callback)())

This defines a callback to be called when the event event occurs on button button_id. The callback gets called during THUpdate().

Symbolic constants for button_id are as follows:

 

TH_RIGHT_BUTTON_0

Right nose button

 

TH_RIGHT_BUTTON_1

Right dorsal button

 

TH_RIGHT_BUTTON_2

Right tail button

 

TH_LEFT_BUTTON_0

Left nose button

 

TH_LEFT_BUTTON_1

Left dorsal button

 

TH_LEFT_BUTTON_2

Left tail button

Symbolic constants for event are as follows:

 

TH_BUTTON_PRESS

 

TH_BUTTON_RELEASE

 

TH_BUTTON_CLICK

 

TH_DOUBLE_CLICK

 

TH_TRIPLE_CLICK

 

TH_QUADRUPLE_CLICK

 

TH_QUINTUPLE_CLICK

 

 

Selection.

The selection routines allow the application to draw the spotlight selection cone, and to test which in a list of selected items is closest to the selection cone function. The data structure that is passed to the selection testing function contains 4 32-bit quantities; three floats indicating position, and one int indicating user-defined tag information. Currently, the tag information is only used in the selection draw routine.

 

void THSelectConeDraw( THCursor C, SelectInfo *point )

This procedure takes a cursor C and a single point With tag information ( a pointer to SelectInfo) and draws the translucent cone and an arrow to the selected object, and some tag information.

 

int THSelectTest( SelectInfo *array, int num_items, THCursor C )

This function takes an array of selectable objects (SelectInfo), the number of elements in the array (num_items), and a cursor C. The spotlight selection function is tested against all of the elements in the SelectInfo array, and returns the array index of the item with the minimum selection function value.

 

Sundial Menus

The sundial functions allow the user to define and interact with sundial menus. Sundials must be defined by defining an array of SundialItems, then calling THSundialInit1Menu(). The last array element MUST equal 0.

The format of the array is as follows:

 

struct sundial_list { /* properties for a sundial menu item */

 

char

*label ;

/* text to print (may be 0) */

 

void

(*callback) (Sundial *, SundialItem *);

 

 

 

 

/* Called when item selected */

 

int

callbackdata ;

/* Data to pass to callback */

 

int

ListID ;

/* OpenGL display list ID */

 

SundialItem *

childitems ;

/* Items of child menu */

 

int

flags ;

/* Item control flags */

} ;

Here is a short example. None of the items has callbackdata, children, or special flags, and no item draws an OpenGL object.

SundialItem LeftButton2CtrlItems[] = {

 

{ "Scale Up",

&ScaleUpSCB,

0, 0, 0, 0 },

 

{ "Re-Origin",

&OriginSCB,

0, 0, 0, 0 },

 

{ "Scale Down",

&ScaleDownSCB,

0, 0, 0, 0 },

 

{ "Spot Align",

&BoresightSCB,

0, 0, 0, 0 },

 

0

 

 

} ;

 

The label field is used as the text label on a sundial menu item. The callback field is the address of a procedure that takes a pointer to a Sundial and a pointer to this SundialItem entry. The callbackdata field may be used by the callback to identify the calling menu item, thus allowing many menu items to call a single callback with different callbackdata values. The ListID field is an optional OpenGL display list ID that may be used to display a geometric object near the outside of the sundial sector. The childitems field points to a previously-defined array of SundialItems which contain a child menu.

 

Sundial *THSundialInit1Menu(SundialItem *items, char *name )

This routine takes a SundialItem array with a name string and defines the menu and all its children. This routine returns a pointer to a complete Sundial that must be used in subsequent sundial calls.

 

int THSundialEcho( Sundial *menu, THCursor C )

This procedure is called by the application when the menu is to be drawn at the cursor C. The application must call this procedure in order for the sundial to be drawn. Event management must be held by the application, for example, by calls to the button package.

 

int THSundialSetFlagsR(Sundial *menu, int flags )

This routine accepts publicly available flags to be set in the given sundial menu. This allows the application programmer to set "SUNDIAL_STATIC" mode, in which a sundial remains fixed on the screen once popped up. Also, when sundial menu items have a "TOGGLEBUTTON", the "RADIOBUTTONS" flag states that all TOGGLEBUTTONs on a given sundial act in the radio-button manner.

 

The flags are as follows:

Menu Flags:

 

SUNDIAL_STATIC

/* sundial is fixed in screen space */

 

RADIOBUTTONS

/* Toggle buttons in Menu have radio behavior */

 

 

 

Item Flags:

 

 

 

TOGGLEBUTTON

/* Menu item is a toggle button */

 

TOGGLE_ON

/* Toggle button is on */

 

int THSundialGetFlags(Sundial *menu )

This function returns flags for the top level menu.

 

void THParsePolyData( Point3 *list )

This procedure takes an array of 3D normal + vertex triples and uses these to define simple polygonal objects. The following example defines two squares that lie on the XY plane and XZ plane, respectively. The ENDPOLY and ENDLIST symbolic constants define the end of a polygon and the end of a list.

 

Point3 Squares[] = {

 

/* Normal

 

 

Vertex */

 

{ 0.0,

0.0, 1.0 },

{ 1.0, 1.0, 0.0 },

{ 0.0,

0.0, 1.0 },

{ 0.0, 1.0, 0.0 },

 

{ 0.0,

0.0, 1.0 },

{ 0.0, 0.0, 0.0 },

 

{ 0.0,

0.0, 1.0 },

{ 1.0, 0.0, 0.0 },

 

{ ENDPOLY,

0.0, 0.0 },

 

 

{ 0.0,

1.0, 0.0 },

{ 1.0, 0.0, 1.0 },

{ 0.0,

1.0, 0.0 },

{ 0.0, 0.0, 1.0 },

 

{ 0.0,

1.0, 0.0 },

{ 0.0, 0.0, 0.0 },

 

{ 0.0,

1.0, 0.0 },

{ 1.0, 0.0, 0.0 },

 

{ ENDPOLY,

0.0, 0.0 },

 

 

{ ENDLIST,

0.0, 0.0 },

 

};