VRPN main page

Obtaining VRPN

VRPN Support

Installing and Testing

Compiling and Modifying

Client code

Server code

Troubleshooting

Connections

Logging and Playback

Shared Objects

Clock Synchronization

Coming attractions & suggestions

UNC-specific information

Table of Contents

Sections in this page:

Selections from the #include file

// User routine to handle a tracker position update.  This is called when
// the tracker callback is called (when a message from its counterpart
// across the connection arrives).

typedef	struct {
	struct timeval	msg_time;	// Time of the report
	long		sensor;		// Which sensor is reporting
	double		pos[3];		// Position of the sensor
	double		quat[4];	// Orientation of the sensor
} vrpn_TRACKERCB;
typedef void (*vrpn_TRACKERCHANGEHANDLER)(void *userdata,
					 const vrpn_TRACKERCB info);

// User routine to handle a tracker velocity update.  This is called when
// the tracker callback is called (when a message from its counterpart
// across the connection arrives).

typedef	struct {
	struct timeval	msg_time;	// Time of the report
	long		sensor;		// Which sensor is reporting
	double		vel[3];		// Velocity of the sensor
	double		vel_quat[4];	// Delta Orientation of the sensor
} vrpn_TRACKERVELCB;
typedef void (*vrpn_TRACKERVELCHANGEHANDLER)(void *userdata,
					     const vrpn_TRACKERVELCB info);

// User routine to handle a tracker acceleration update.  This is called when
// the tracker callback is called (when a message from its counterpart
// across the connection arrives).

typedef	struct {
	struct timeval	msg_time;	// Time of the report
	long		sensor;		// Which sensor is reporting
	double		acc[3];		// Acceleration of the sensor
	double		acc_quat[4];	// Delta Delta Orientation of the sensor
} vrpn_TRACKERACCCB;
typedef void (*vrpn_TRACKERACCCHANGEHANDLER)(void *userdata,
					     const vrpn_TRACKERACCCB info);
//The above routines only deal with the sensor to tracker transform. The routines
//handle the tracker to room transform, unit to sensor transform, and the trackers
// workspace (working volume). These currently come from the vrpn_Tracker.cfg on
// VRPN server or client. Have this set properly on the VRPN servers allows your
// application to run on different trackers without having to change any code or
// recompile!

// tracker to room (currently come from vrpn_Tracker.cfg file)
typedef struct {
        struct timeval  msg_time;       // Time of the report
        double  tracker2room[3];        // position offset
        double  tracker2room_quat[4];   // orientation offset
} vrpn_TRACKERTRACKER2ROOMCB;
typedef void (*vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER)(void *userdata,
                                        const vrpn_TRACKERTRACKER2ROOMCB info);

// unit to sensor (currently comes from vrpn_Tracker.cfg file)
// this xfrom is from the sensor to the object you really care about in your
// virtual work (like user's head or hand, etc.)
typedef struct {
        struct timeval  msg_time;       // Time of the report
        long    sensor;                 // Which sensor this is the xform for
        double  unit2sensor[3];        // position offset
        double  unit2sensor_quat[4];   // orientation offset
} vrpn_TRACKERUNIT2SENSORCB;
typedef void (*vrpn_TRACKERUNIT2SENSORCHANGEHANDLER)(void *userdata,
                                        const vrpn_TRACKERUNIT2SENSORCB info);

// workspace (aka working volume). Currently comes from vrpn_Tracker.cfg file
typedef struct {
        struct timeval  msg_time;       // Time of the report
        double workspace_min[3];        // minimum corner of box (tracker CS)
        double workspace_max[3];        // maximum corner of box (tracker CS)
} vrpn_TRACKERWORKSPACECB;
typedef void (*vrpn_TRACKERWORKSPACECHANGEHANDLER)(void *userdata,
                                        const vrpn_TRACKERWORKSPACECB info);

// Your application can call these method on vrpn_Tracker_Remote
class vrpn_Tracker_Remote: public vrpn_Tracker {
  public:
        // The name of the tracker to connect to
        vrpn_Tracker_Remote(char *name);

        // request room from tracker xforms
        int request_t2r_xform(void);
        // request all available sensor from unit xforms
        int request_u2s_xform(void);
        // request workspace bounding box
        int request_workspace(void);

        // This routine calls the mainloop of the connection it's on
        virtual void mainloop(void);

        // **** to register handlers for all sensors: ****

        // (un)Register a callback handler to handle a position change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERCHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERCHANGEHANDLER handler);

        // (un)Register a callback handler to handle a velocity change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERVELCHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERVELCHANGEHANDLER handler);

        // (un)Register a callback handler to handle an acceleration change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERACCCHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERACCCHANGEHANDLER handler);

        // (un)Register a callback handler to handle a tracker2room change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER handler);

        // (un)Register a callback handler to handle a unit2sensor change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler);

        // **** to register handlers for specific sensors: ****

        // (un)Register a callback handler to handle a position change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERCHANGEHANDLER handler, int sensor);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERCHANGEHANDLER handler, int sensor);

        // (un)Register a callback handler to handle a velocity change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERVELCHANGEHANDLER handler, int sensor);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERVELCHANGEHANDLER handler, int sensor);

        // (un)Register a callback handler to handle an acceleration change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERACCCHANGEHANDLER handler, int sensor);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERACCCHANGEHANDLER handler, int sensor);

        // (un)Register a callback handler to handle a unit2sensor change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler, int sensor);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler, int sensor);

        // **** to get workspace information ****
        // (un)Register a callback handler to handle a workspace change
        virtual int register_change_handler(void *userdata,
                vrpn_TRACKERWORKSPACECHANGEHANDLER handler);
        virtual int unregister_change_handler(void *userdata,
                vrpn_TRACKERWORKSPACECHANGEHANDLER handler);

};

Using a vrpn_Tracker_Remote object

A vrpn_Tracker_Remote object is used by passing the name of the tracker you want to connect to as a parameter to the constructor, defining a callback handler that will respond to the tracker's update messages, and then calling its mainloop() message each time through the program's main loop. The callback handler takes care of updating the head/hand position based on the sensor that was updated. If you are using a tracker with multiple sensors then you may want to register a sensor-specific handler to update the position data specific to a particular sensor (e.g. the head sensor or hand sensor). Sensor-specific callbacks make it easier to use the same callback function to update different transformations. In addition to the regular "sensor2tracker" reports that are sent as tracker updates, you may request unit2sensor and tracker2room reports that give the transformation from head/hand space to sensor space in the case of unit2sensor and the transformation from tracker base space to room space however those spaces have been defined. The tracker server reads these values from a vrpn_Tracker.cfg file located in the same directory as the server application. Additionally, you may access local values for these transformations that are read in by the vrpn_Tracker_Remote constructor from a local vrpn_Tracker.cfg file. These local values do not change when remote values are requested. To get remote transformations you register a callback handler and use the request_t2r_xform() and request_u2s_xform() to tell the server to send the tracker2room and unit2sensor transforms. The request_u2s_xform() function will cause the tracker to send reports for all sensors. To access local values call the get_local_t2r() or get_local_u2s() functions passing them the addresses to which you want the values written.

vrpn_Tracker.cfg file:

The vrpn_Tracker.cfg file may also contain the extents of the tracker workspace. This may be retrieved by registering the appropriate callback and calling the request_workspace() function.


format of the vrpn_Tracker.cfg file:


"tracker name"
"room from tracker translation (x,y,z)"
"room from tracker quaternion (x,y,z,w)"
"workspace (xmin,ymin,zmin) (xmax,ymax,zmax)"
"number of sensors, n"
"sensor # = which sensor"
"sensor from unit translation (x,y,z)"
"sensor from unit quaternion (x,y,z,w)"
... (etc. for all n sensors)
... (etc. for as many trackers as you want to specify)

 

---- example entry for a tracker in the vrpn_Tracker.cfg file:
Phantom@phantom3
0.0 0.0 -1.4	
0.0 0.0 0.0 1.0
-0.2 -0.2 -0.1 0.2 0.2 0.2
1
0
0.0 0.0 0.0
0.0 0.0 0.0 1.0
---- end of example


Comments in the file are allowed. They follow the '#' character.
Abritrary Text(ie. more comments) at the end of the file is also allowed. VRPN only read up to the point where it
find the tracker information it's looking for, then stops.

note: In this example, the user's head is the base origin for graphics so this is
our 'room' origin. To make the virtual hand appear in front of the head we
translate the hand by 1.4 meters (hence the -1.4 Z translation to go from the
tracker coordinate system to the room coordinate system). This is kind of a
confusing application because the user's head is not actually fixed in a room
but the origin (viewpoint) used in the graphics may be fixed as long as the screen
doesn't move.

Simple Example Program

Here is a simple example program that just prints the new position of each sensor as the reports arrive:

/*
My first vrpn client - vrpnHelloWorld

If you want to transform a CAMERA, VIEWPOINT or HMD, instead of an displayed object,
you need to invert the transform, since
vrpn returns the transform sensor to tracker/base/emitter transform.

// NOTE: a vrpn tracker must call user callbacks with tracker data (pos and
//       ori info) which represent the transformation xfSourceFromSensor.
//       This means that the pos info is the position of the origin of
//       the sensor coord sys in the source coord sys space, and the
//       quat represents the orientation of the sensor relative to the
//       source space (ie, its value rotates the source's axes so that
//       they coincide with the sensor's)

*/
#include 
#include 
#include 

/*****************************************************************************
 *
   Callback handler
 *
 *****************************************************************************/

void    handle_tracker(void *userdata, const vrpn_TRACKERCB t)
{
  //this function gets called when the tracker's POSITION xform is updated

  //you can change what this callback function is called for by changing
  //the type of t in the function prototype above.
  //Options are:
  //   vrpn_TRACKERCB              position
  //   vrpn_TRACKERVELCB           velocity
  //   vrpn_TRACKERACCCB           acceleration
  //   vrpn_TRACKERTRACKER2ROOMCB  tracker2room transform 
  //                                 (comes from local or remote
  //                                  vrpn_Tracker.cfg file)
  //   vrpn_TRACKERUNIT2SENSORCB   unit2sensor transform (see above comment)
  //   vrpn_TRACKERWORKSPACECB     workspace bounding box (extent of tracker)

  // userdata is whatever you passed into the register_change_handler function.
  // vrpn sucks it up and spits it back out at you. It's not used by vrpn internally

  printf("handle_tracker\tSensor %d is now at (%g,%g,%g)\n", 
	 t.sensor,
	 t.pos[0], t.pos[1], t.pos[2]);
}


//****************************************************************************
//
//   Main function
//
//****************************************************************************
int main(int argc, char *argv[])
{       int     done = 0;
        vrpn_Tracker_Remote *tkr;

        // Open the tracker
        tkr = new vrpn_Tracker_Remote("Tracker0@ioglab");

        // Set up the tracker callback handler
        tkr->register_change_handler(NULL, handle_tracker);
	// the handle_tracker fucntion will be called whenever the 
	// tracker position for ANY of the tracker's sensors are updated.
	// if you are interested in only specific sensors (this should be
	// the most common case), use this method instead:

	// tkr->register_change_handler(NULL, handle_tracker,2);
	// handle_tracker will be called only when sensor #2 is updated.

        // 
        // main interactive loop
        //
        while ( ! done ) {
                // Let the tracker do it's thing
                // It will call the callback funtions you registered above
		// as needed
		tkr->mainloop();
        }

}   //main

Callback functions

The vrpn_Tracker_Remote class provides information on changes in the tracker sensors' position and orientation by calling a user callback function. It is also possible to register handlers for velocity and acceleration information by passing different callback pointer types to the overloaded member function that registers change handlers. The application gives the function that should handle the data, and also a pointer to a block of user data that the callback routine needs in order to handle the data. For example, if the callback function was supposed to fill in a certain transformation matrix based on the callback information then a pointer to the tranformation matrix might be passed as user data. If the callback handler was going to affect some object, a pointer to the object would be passed. In this case, the handler function would typecast the 'void *userdata' pointer to the correct type for the user data. If more than one data item is to be passed, they should be placed into a structure and a pointer to the structure passed as user data.

Warning! You should not normally place your rendering loop (or any other heavy computation) inside the tracker callback. If you do, there will likely be another tracker message by the time you have finished the computation. This will cause the program to enter an infinite loop and never return from the mainloop() call to the tracker. This is because there is a new report from the tracker after each call to the handler for the message.

Using a tracker when the application's main loop is slow

Trackers use unreliable (UDP as of 2/24/98) transmission for their updates. They report these updates at some frequency, perhaps 60-100 times per second. If the application does not call mainloop() on its vrpn_Tracker_Remote object frequently enough, the incoming buffer fills up and some of these messages are not delivered. Unfortunately, UDP discards the later packets and keeps the oldest ones. This has the effect of introducing latency equal to the application's main loop time when that time is much slower than the tracker report time. To get around this, the application should make sure that it gets a new report each time through its main loop. It can do this by purging all of the old reports (through a call to mainloop()) and then reading until a new report comes in. A code fragment showing how this might be done is given here:

#include <stdlib.h>
#include <stdio.h>
#include <vrpn_Tracker.h>

static  int     got_report;     // Tells when a new report has come in

/*****************************************************************************
 *
 Callback handler
 *
 *****************************************************************************/

void    handle_tracker(void *userdata, const vrpn_TRACKERCB t)
{
        static  int     count = 0;
        // XXX Fill in a data structure to be used by the program here

        got_report = 1; // Tell the main loop that we got another report
}

int main(int argc, char *argv[])
{       int     done = 0;
        vrpn_Tracker_Remote *tkr;

        // Open the tracker
        tkr = new vrpn_Tracker_Remote("Tracker0@ioglab");

        // Set up the tracker callback handler
        tkr->register_change_handler(NULL, handle_tracker);

        /*
        * main interactive loop
        */
        while ( ! done ) {
                // Purge all of the old reports
                tkr->mainloop();

                // Make sure that we get a new report
                got_report = 0;
                while (!got_report) { tkr->mainloop(); }

                // XXX The application uses the new report here

                sleep(5);       // Slow main loop
        }

} /* main */

A note on quaternions:

The quaternion angle representation stores the orientation as a four-vector, where the first three values specify the axis around which the rotation occurs and the fourth specifies the amount of rotation. The whole vector is normalized. This matches UNC's quatlib format. Quatlib source code is included in recent VRPN releases; it shows up in a quat directory, alongside the vrpn and vrpn_html directories. An excerpt from the README follows:

This directory contains the source and test files for the quaternion 
library, libquat.a. It is based on Warren Robinett's adapted version of Ken
Shoemake's code, as seen in Shoemake's 1985 SIGGRAPH paper.

For those not familiar with quaternions, they are a
concise and efficient way of describing rotations and orientations of
three-dimensional objects. (Two good references for this subject are
Ken Shoemake's 1985 SIGGRAPH papers "Quaternion Calculus for Animation"
and "Animating Rotation with Quaternion Curves" in the '85
proceedings.)

The library is built on Ken Shoemake's code (as presented in the appendix of
the first paper listed above) and implements the most common quaternion
functions, as well as some more obscure ones. In particular, there are
conversion routines between vectors, matrices and quaternions, as well as
various vector and matrix operations.

Permission to use this code is granted to non-commercial organizations
provided that acknowledgement of the authors and funding agencies is given.

Documentation on individual routines is in quat.h.

See header of quat.c and quat.h for more info.