Client side VRPN Devices

Ryan Pavlik edited this page Apr 15, 2015 · 11 revisions

An application uses one or more VRPN devices by creating one or more VRPN application objects. Examples and descriptions of currently-implemented client-side application objects include vrpn_Tracker_Remote, vrpn_Poser_Remote, vrpn_Button_Remote, vrpn_Analog_Remote, vrpn_Analog_Output_Remote, vrpn_Dial_Remote, vrpn_ForceDevice_Remote, vrpn_Imager_Remote, and vrpn_Sound_Remote. Each type of object has its own include file (vrpn_Tracker.h and so on), but all are found in both the client-side and server-side VRPN libraries. General information is provided below, followed by specific information on each object type.

The name of application (client) objects all begin with vrpn_ and all end with _Remote. The base class for the object (from which both client and server objects are derived) lacks the _Remote. Server objects have names specific to the device or class of devices that they serve (for example, _Fastrak for a certain kind of tracker).

Objects of each type are normally created by calling their constructor and passing the name of the device you wish to open. The name includes the local name and the name of the machine on which the device is found. For example, to open the device called Tracker0 on the machine ioceiling.cs.unc.edu, you would do the following:

#include <vrpn_Tracker.h>
vrpn_Tracker_Remote myTracker("Tracker0@ioglab.cs.unc.edu");

For object types vrpn_Tracker_Remote, vrpn_Analog_Remote, vrpn_Dial_Remote, and vrpn_Button_Remote the application defines a callback function to the object. This function is called whenever a new data value is received from the device. For trackers, this callback records new tracker reports. For buttons, it reports when a button is pressed or released. Each update includes (by default) the local time at which the value changed.

For object types vrpn_Analog_Output_Remote, vrpn_Poser_Remote, vrpn_ForceDevice_Remote, and vrpn_Sound_Remote, the application calls member functions to provide commands to the remote device. VRPN handles packaging the requests and sending them to the remote device.

Example programs:

Examples of using each type of remote device listed above can be found on the descriptions below.

John Stone from the Beckman Institute provided several examples of using a Phantom (vrpn_Tracker, vrpn_Button, vrpn_ForceDevice), including hooking these to openGL programs under Irix. These can be found in the client_src/haptic directory of the source code tree.

mainloop() -- important to call this!

For all object types, the code should call the object's mainloop() member function once each time through the program's main loop. It is at this time that the incoming network buffers are checked and application callbacks are called, once for each incoming message. Also, for outgoing network connections, this is when the data are actually put on the wire. (It probably would have been better to have named this function "poll()", based on the way it is used.)

In the future, shared-memory or multi-threaded VRPN connections may be created. For these, the callbacks may be called in the remote system as soon as the member function is called on the local system. For this reason, the semantics of the callback handlers is that they will be called sometime from when the member function creating the message is called until the next time mainloop() finishes on the object.

Text messages

See the vrpn text message page for a description of how to change or turn off the printing of VRPN text messages.

vrpn_Tracker_Remote

Type definitions

Positions from all trackers in VRPN are reported in meters. Velocities are reported in meters/second. Accelerations are reported in meters/second/second. These are all reported in three-element double arrays in the order (X=0, Y=1, Z=2). They are translated into this format from the native format for each device.

Orientations from all trackers in VRPN are reported in quaternions (see note on Quaternions) in four-element double arrays in the order (X=0, Y=1, Z=2, W=3). They are translated into this format from the native format for each device.

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 <stdlib.h>
#include <stdio.h>
#include <vrpn_tracker.h>

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

void    VRPN_CALLBACK 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. The safest way to get transformation matrices or Euler angles is using the built-in quatlib routines. The most helpful may be q_to_ogl_matrix(), which converts the native quaternion from VRPN into an OpenGL matrix. Together with the position (which is a translation), the two can be added into the matrix stack as follows:

void handleSensor2Tracker(void *userdata, const vrpn_TRACKERCB info) {
    double orient[16];
    glTranslated(info.pos[0], info.pos[1], info.pos[2]);
    q_to_ogl_matrix(orient, info.quat);
    glMultMatrixd(orient);
}

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.

vrpn_Poser_Remote

The vrpn_Poser_Remote class specifies a position and orientation to which a motion platform should go. It is the inverse of a vrpn_Tracker_Remote (which tells where a tracked object has moved to). Servers that include tracked objects and can also set the positions of these objects will implement both the Tracker and Poser interfaces. User code will control the object's position using the Poser interface and listen for reports of new positions on the Tracker interface.

Selections from the #include file

//------------------------------------------------------------------------------------
// Client Code

// Open a poser that is on the other end of a connection for sending updates to it.
class vrpn_Poser_Remote: public vrpn_Poser {
 public:
   // The name of the poser to connect to, including connection name,
   // for example "poser@magnesium.cs.unc.edu". If you already
   // have the connection open, you can specify it as the second parameter.
   // This allows both servers and clients in the same thread, for example.
   // If it is not specified, then the connection will be looked up based
   // on the name passed in.
   vrpn_Poser_Remote (const char* name, vrpn_Connection* c = NULL);

   // unregister all of the handlers registered with the connection
   virtual ~vrpn_Poser_Remote (void);

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

   // Routines to set the state of the poser
   int request_pose(struct timeval t,
                    vrpn_float64 position[3],
                    vrpn_float64 quaternion[4]);
   int request_pose_velocity(struct timeval t,
                             vrpn_float64 position[3],
                             vrpn_float64 quaternion[4],
                             vrpn_float64 interval);
};

Using a vrpn_Poser_Remote

A vrpn_Poser_Remote object is used by passing the name of the poser you want to connect to as a parameter to the constructor and then calling its mainloop() message each time through the program's main loop. While active, the poser device is controlled by setting the position or velocity. The position is sent using request_pose(). The poser clips the request to its active region and then moves to this location. The velocity is sent using request_pose_velocity().

vrpn_Button_Remote

Selections from the #include file

// User routine to handle a change in button state.  This is called when
// the button callback is called (when a message from its counterpart
// across the connetion arrives).

typedef struct {
    struct timeval  msg_time;   // Time of button press/release
    int     button;     // Which button (numbered from zero)
    int     state;      // New state (0 = off, 1 = on)
} vrpn_BUTTONCB;
typedef void (*vrpn_BUTTONCHANGEHANDLER)(void *userdata,
                     const vrpn_BUTTONCB info);

// Open a button that is on the other end of a connection
// and handle updates from it.  This is the type of button that user code will
// deal with.

#define BUTTON_MOMENTARY 10
#define BUTTON_TOGGLE_OFF    20
#define BUTTON_TOGGLE_ON    21
#define ALL_ID      -99

class vrpn_Button_Remote: public vrpn_Button {
  public:
    // The name of the button device to connect to
    vrpn_Button_Remote(char *name);

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

    // (un)Register a callback handler to handle a button state change
    virtual int register_change_handler(void *userdata,
        vrpn_BUTTONCHANGEHANDLER handler);
    virtual int unregister_change_handler(void *userdata,
        vrpn_BUTTONCHANGEHANDLER handler);
        //call in the remote device to set the
        //buttons to be momentary or toggle.
        //A momentery button comes on only as long as it is held down.
        //A toggle button comes on when it is pressed and then goes off when
        //it is pressed again. Use ALL_ID to set the mode for all buttons.
        int set_button_mode(int mode, int button_id);
};

Using a vrpn_Button_Remote

A vrpn_Button_Remote object is used by passing the name of the button device you want to connect to as a parameter to the constructor, defining a callback handler that with respond to the button state update messages, and then calling its mainloop() message each time through the program's main loop. The callback handler takes care of updated implementing what should happen on a button press or release. Here is a simple example program that just prints the new state of each button as the reports arrive:

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

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

void    VRPN_CALLBACK handle_button(void *userdata, vrpn_BUTTONCB b)
{
    printf("B%ld is %ld\n", b.button, b.state);
}

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

    /* initialize the button */
    btn = new vrpn_Button_Remote("Button0@ioglab");

    // Set up the button callback handler
    btn->register_change_handler(NULL, handle_button);

/*
 * main interactive loop
 */
while ( ! done )
    {
    // Let the  button do its thing
    btn->mainloop();
    }

}   /* main */

Callback function

The vrpn_Button_Remote class provides information on changes in the button states by calling a user callback function. 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 adjust a certain color field based on button presses then a pointer to the color field 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.

vrpn_Analog_Remote

An application can open an analog device, like a joystick or set of sliders, which can send analog values back. After defining the analog device object, analog values in different channels can be read through callback handlers.

Selections from the #include file

//----------------------------------------------------------
//************** Users deal with the following *************

// User routine to handle a change .  This is called when
// the analog callback is called (when a message from its counterpart
// across the connetion arrives).

typedef struct {
    struct timeval  msg_time;   // Time of button press/release
    int     num_channel;        // how many channels
    double      channel[vrpn_CHANNEL_MAX];  // channel diliever analog values
} vrpn_ANALOGCB;

typedef void (*vrpn_ANALOGCHANGEHANDLER)(void *userdata,
                     const vrpn_ANALOGCB info);

// Open a analog device that is on the other end of a connection
// and handle updates from it.  This is the type of analog device
// that user code will  deal with.

class vrpn_Analog_Remote: public vrpn_Analog {
  public:
    // The name of the button device to connect to
    vrpn_Analog_Remote(char *name);

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

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

  protected:
    typedef struct vrpn_RBCS {
        void                *userdata;
        vrpn_ANALOGCHANGEHANDLER    handler;
        struct vrpn_RBCS        *next;
    } vrpn_ANALOGCHANGELIST;
    vrpn_ANALOGCHANGELIST   *change_list;

    static int handle_change_message(void *userdata, vrpn_HANDLERPARAM p);
};

Using vrpn_Analog_Remote

A vrpn_Analog_Remote object is used by passing the name of the analog device you want to connect to as a parameter to the constructor, and then using the analog device interface to read in analog values in different channels. Here is a simple example program that just prints the new values of each channels as the reports arrive:

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

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

void    VRPN_CALLBACK handle_analog(void *userdata, vrpn_ANALOGCB b)
{
  for (int i=0; i< b.num_channel; i++)
   printf("Chan[%d] = %lf\n", i, b.channel[i]);
}

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

    /* initialize the analog */
    ana = new vrpn_Analog_Remote("analog0@ioglab");

    // Set up the analog callback handler
    ana->register_change_handler(NULL, handle_analog);

/*
 * main interactive loop
 */
while ( ! done )
    {
    // Let the  analog device do its thing
    ana->mainloop();
    }

}   /* main */

vrpn_Analog_Output_Remote

The vrpn_Analog_Output_Remote class enables the client program to specify an analog value on a server object. It is the inverse of a vrpn_Analog_Remote (which tells what analog values on a server object). Servers that include analog output objects can also implement the vrpn_Analog interface. User code will control the object's value using the vrpn_Analog_Output_Remote interface and listen for reports of new positions on the vrpn_Analog_Remote interface.

Selections from the #include file

// Open an analog device that is on the other end of a connection
// and handle updates from it.  This is the type of analog device
// that user code will deal with.
class vrpn_Analog_Output_Remote : public vrpn_Analog_Output {
    public:
        // The name of the analog device to connect to
        // Optional argument to be used when the Remote should
        // listen on a connection that is already open.
        vrpn_Analog_Output_Remote(const char* name,
                                  vrpn_Connection* c = NULL);
        virtual ~vrpn_Analog_Output_Remote (void);

        // Call each time through application main loop.
        virtual void mainloop();

        // Request the analog to change its value to the
        // one specified. Returns false on failure.
        virtual bool request_change_channel_value(unsigned int chan,
            vrpn_float64 val,
            vrpn_uint32 class_of_service = vrpn_CONNECTION_RELIABLE);

        // Request the analog to change values all at once.  If
        // more values are given than we have channels, the extra
        // values are discarded. If less values are given than we
        // have channels, the extra channels are set to 0.
        // Returns false on failure.
        virtual bool request_change_channels(int num,
            vrpn_float64* vals,
            vrpn_uint32 class_of_service = vrpn_CONNECTION_RELIABLE);
};

Using a vrpn_Analog_Output_Remote

A vrpn_Analog_Output_Remote object is used by passing the name of the server you want to connect to as a parameter to the constructor and then calling its mainloop() message each time through the program's main loop. While active, the analog output device is controlled by calling request_change_channel_value() or request_change_channels().

vrpn_Dial_Remote

An application can open a dial device, like a knob, which can send differential orientation values back. This class is similar to the vrpn_Analog class, but it deals with dials or other orienters that can turn forever. The same device may be treated as both an analog and a dial by a server program; the client selects which behavior by connecting the appropriate remote class to the server.

Selections from the #include file

//----------------------------------------------------------
// User routine to handle a change in dial values.  This is called when
// the dial callback is called (when a message from its counterpart
// across the connetion arrives).

typedef struct {
    struct timeval  msg_time;   // Timestamp when change happened
    vrpn_int32      dial;       // which dial changed
    vrpn_float64    change;     // Fraction of a revolution it changed
} vrpn_DIALCB;

typedef void (*vrpn_DIALCHANGEHANDLER) (void * userdata, const vrpn_DIALCB info);

// Open a dial device that is on the other end of a connection
// and handle updates from it.  This is the type of device
// that user code will deal with.

class vrpn_Dial_Remote: public vrpn_Dial {
  public:
    // The name of the device to connect to.
    // Optional argument to be used when the Remote MUST listen on
    // a connection that is already open.
    vrpn_Dial_Remote (const char * name, vrpn_Connection * c = NULL);
    ~vrpn_Dial_Remote();

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

    // (un)Register a callback handler to handle dial updates
    virtual int register_change_handler(void *userdata,
        vrpn_DIALCHANGEHANDLER handler);
    virtual int unregister_change_handler(void *userdata,
        vrpn_DIALCHANGEHANDLER handler);
};

Using vrpn_Dial_Remote

A vrpn_Dial_Remote object is used by passing the name of the dial device you want to connect to as a parameter to the constructor, and then using the dial device interface to read in values of different dials. Here is a simple example program that just prints the new value of each dial as the reports arrive:

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

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

void    VRPN_CALLBACK handle_dial (void *, const vrpn_DIALCB d)
{
    printf("Dial %ld spun by %lf\n", d.dial, d.change);
}

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

    /* initialize the dial */
    dial = new vrpn_Dial_Remote("Dial0@ioglab");

    // Set up the dial callback handler
    dial->register_change_handler(NULL, handle_dial);

    /*
     * main interactive loop
     */
    while ( ! done ) {
        // Let the dial device do its thing
        dial->mainloop();
    }
}   /* main */

vrpn_ForceDevice_Remote

When an application opens a force device, such as the PHANToM, it also wants access to the position reports from the device and also any buttons that are on the device. The application gets access to these by opening the device with the same name as a vrpn_Tracker_Remote, a vrpn_Button_Remote, and a vrpn_ForceDevice_Remote. VRPN takes care of mapping all of these devices to use the same connection to communicate over, and the application can treat the three separate functions of the device independently.

The vrpn_ForceDevice_Remote is the force-update part of a force-feedback device. It specifies objects in the space of the force device that the end effector(s) will bump against.

Selections from the #include file

#define MAXPLANE 4   //maximum number of plane in the scene

class vrpn_ForceDevice {
public:
    void setSurfaceKspring(float k)         {SurfaceKspring = k; }
    void setSurfaceKdamping(float d)        {SurfaceKdamping =d;}
    void setSurfaceFstatic(float ks)        {SurfaceFstatic = ks;}
    void setSurfaceFdynamic(float kd)       {SurfaceFdynamic =kd;}
    void setFF_Origin(float x, float y, float z);
    void setFF_Force(float fx, float fy, float fz); // dynes
    void setFF_Jacobian(float dfxdx, float dfxdy, float dfxdz, // dynes/meter
            float dfydx, float dfydy, float dfydz,
            float dfzdx, float dfzdy, float dfzdz);
    void setFF_Radius(float r);
};

typedef     struct {
    struct          timeval msg_time;       // Time of the report
    float           force[3];               // force value
} vrpn_FORCECB;
typedef void (*vrpn_FORCECHANGEHANDLER)(void *userdata,
                                     const vrpn_FORCECB info);

typedef struct {
        struct          timeval msg_time;       // Time of the report
        double          pos[3];                 // position of SCP
        double          quat[4];                // orientation of SCP
} vrpn_FORCESCPCB;
typedef void (*vrpn_FORCESCPHANDLER) (void *userdata,
                                        const vrpn_FORCESCPCB info);

class vrpn_ForceDevice_Remote: public vrpn_ForceDevice {
public:

    // The name of the force device to connect to
    vrpn_ForceDevice_Remote(char *name);
    void set_plane(float *p);
    void set_plane(float *p, float d);
    void set_plane(float a, float b, float c,float d);

    void sendSurface(void);
    void startSurface(void);
    void stopSurface(void);

        // vertNum normNum and triNum start at 0
        void setVertex(int vertNum,float x,float y,float z);
        // NOTE: ghost dosen't take normals,
        //       and normals still aren't implemented for Hcollide
        void setNormal(int normNum,float x,float y,float z);
        void setTriangle(int triNum,int vert0,int vert1,int vert2,
              int norm0=-1,int norm1=-1,int norm2=-1);
        void removeTriangle(int triNum);
        // should be called to incorporate the above changes into the displayed trimesh
        void updateTrimeshChanges();
        // set the trimesh's homogen transform matrix (in row major order)
        void setTrimeshTransform(float homMatrix[16]);
    void clearTrimesh(void);

        // the next time we send a trimesh we will use the following type
        void useHcollide();
        void useGhost();

    void sendConstraint(int enable, float x, float y, float z, float kSpr);

    void sendForceField(float origin[3], float force[3],
        float jacobian[3][3], float radius);
    void sendForceField(void);
    void stopForceField();

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

    // (un)Register a callback handler to handle a force change
    // and plane equation change and trimesh change
    virtual int register_change_handler(void *userdata,
        vrpn_FORCECHANGEHANDLER handler);
    virtual int unregister_change_handler(void *userdata,
        vrpn_FORCECHANGEHANDLER handler);

    virtual int register_scp_change_handler(void *userdata,
                vrpn_FORCESCPHANDLER handler);
        virtual int unregister_scp_change_handler(void *userdata,
                vrpn_FORCESCPHANDLER handler);

    virtual int register_error_handler(void *userdata,
        vrpn_FORCEERRORHANDLER handler);
    virtual int unregister_error_handler(void *userdata,
        vrpn_FORCEERRORHANDLER handler);
};

Using a vrpn_ForceDevice_Remote

A vrpn_ForceDevice_Remote object is used by passing the name of the force device you want to connect to as a parameter to the constructor, defining a callback handler that will respond to the force device state update messages, and then calling its mainloop() message each time through the program's main loop. Often, a callback handler is not required because you usually just want to tell a force device what forces to apply and don't need to hear back from the force device about what forces it is applying. While active, the force device is controlled by setting the current plane, trimesh, or force field.

The plane is sent using sendSurface(). The force device determines what forces need to be applied to simulate the plane. startSurface() and stopSurface() are used to activate or deactivate the force device.

You can send a triangle mesh by calls to setVertex() and setTriangle().Similarly you can modify the mesh with calls to setTriangle(), setVertex(), and removeTriangle(). Once you have finished a round of adjustments and are ready for them to take effect, make a call to updateTrimeshChanges(). clearTrimesh() behaves like stopSurface() for a plane. After a trimesh has been sent you can set its homogenous tranformation matrix with a call to setTrimeshTransform.

Several functions inherited from vrpn_ForceDevice are used to set various parameters used to simulate the plane surface such as the spring constant and friction. If you register a handler using register_scp_change_handler you can receive messages giving the location of the surface contact point (SCP) being used in the force model. While the hand tracker associated with force device may report that the hand position is actually below a virtual surface, the surface contact point will always stay on or above the virtual surface. It may be preferable to use the SCP rather than the hand position for graphical display of the hand position as doing so can have a significant effect on the perception of surface compliance.

A force field may be specified as a local approximation, analogous to a plane for surfaces. You must specify the position at which the approximation is valid, the force at that position and the jacobian matrix giving the derivatives of the force field in each direction. You must also specify a radius beyond which the approximation should not be used (forces will automatically go to zero). This is a safety feature. In general, you will want to use the latest position reported by the tracker for the approximation point. To update the force field you should call sendForceField() and to set the force field to 0 you can call stopForceField().

The following is an example that sets up the force device to simulate a plane defined by the equation, Y = 0.

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

#define PHANTOM_SERVER "Phantom@phantom3"

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

void    handle_force_change(void *userdata, vrpn_FORCECB f)
{
    static vrpn_FORCECB lr;        // last report
    static int first_report_done = 0;

    if ((!first_report_done) ||
        ((f.force[0] != lr.force[0]) || (f.force[1] != lr.force[1])
          || (f.force[2] != lr.force[2])))
       printf("force is (%f,%f,%f)\n", f.force[0], f.force[1], f.force[2]);
    first_report_done = 1;
    lr = f;
}

void    handle_tracker_change(void *userdata, const vrpn_TRACKERCB t)
{
    static vrpn_TRACKERCB lr; // last report
    static float dist_interval_sq = 0.004;

    if ((lr.pos[0] - t.pos[0])*(lr.pos[0] - t.pos[0]) +
        (lr.pos[1] - t.pos[1])*(lr.pos[1] - t.pos[1]) +
        (lr.pos[2] - t.pos[2])*(lr.pos[2] - t.pos[2]) > dist_interval_sq){
            printf("Sensor %d is now at (%g,%g,%g)\n", t.sensor,
                    t.pos[0], t.pos[1], t.pos[2]);
            lr = t;
    }
}

void        handle_button_change(void *userdata, vrpn_BUTTONCB b)
{
    static int count=0;
    static int buttonstate = 1;
    int done = 0;

    if (b.state != buttonstate) {
         printf("button #%ld is in state %ld\n", b.button, b.state);
         buttonstate = b.state;
         count++;
    }
    if (count > 4)
            done = 1;
    *(int *)userdata = done;
}

int main(int argc, char *argv[])
{
        int     done = 0;
        vrpn_ForceDevice_Remote *forceDevice;
    vrpn_Tracker_Remote *tracker;
    vrpn_Button_Remote *button;

        /* initialize the force device */
        forceDevice = new vrpn_ForceDevice_Remote(PHANTOM_SERVER);
    forceDevice->register_change_handler(NULL, handle_force_change);

    /* initialize the tracker */
    tracker = new vrpn_Tracker_Remote(PHANTOM_SERVER);
    tracker->register_change_handler(NULL, handle_tracker_change);

    /* initialize the button */
    button = new vrpn_Button_Remote(PHANTOM_SERVER);
    button->register_change_handler(&done, handle_button_change);

        // Set plane and surface parameters
        forceDevice->set_plane(0.0, 1.0, 0.0, 0.0);
        forceDevice->setSurfaceKspring(0.8);        // spring constant - units of
                                            // dynes/cm
        forceDevice->setSurfaceKdamping(0.1);       // damping constant -
                                                // units of dynes*sec/cm
        forceDevice->setSurfaceFstatic(0.7);        // set static friction
        forceDevice->setSurfaceFdynamic(0.3);       // set dynamic friction
        forceDevice->setRecoveryTime(10);   // recovery occurs over 10
                                                // force update cycles
        // enable force device and send first surface
        forceDevice->startSurface();
        // main loop
        while (! done ){
                // Let the forceDevice send its planes to remote force device
                forceDevice->mainloop();
            // Let tracker receive position information from remote tracker
            tracker->mainloop();
            // Let button receive button status from remote button
            button->mainloop();
                // we may call forceDevice->set_plane(...) followed by
                //      forceDevice->sendSurface() here to change the plane
                // for example: using position information from a tracker we can
                //      compute a plane to approximate a complex surface at that
                //      position and send that approximation 15-30 times per
                //      second to simulate the complex surface
        }
    // shut off force device
        forceDevice->stopSurface();
}   /* main */

Here is an example that displays a tetrahedron with a 10 cm radius, manipulates its mesh a little and transforms it:

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

#define PHANTOM_SERVER "Phantom@phantom3"

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

void    handle_force_change(void *userdata,const vrpn_FORCECB f)
{
        static vrpn_FORCECB lr;        // last report
        static int first_report_done = 0;

        if ((!first_report_done) ||
            ((f.force[0] != lr.force[0]) || (f.force[1] != lr.force[1])
              || (f.force[2] != lr.force[2])))
           printf("force is (%f,%f,%f)\n", f.force[0], f.force[1], f.force[2]);
        first_report_done = 1;
        lr = f;
}

void    handle_tracker_change(void *userdata, const vrpn_TRACKERCB t)
{
        static vrpn_TRACKERCB lr; // last report
        static float dist_interval_sq = 0.004;

        if ((lr.pos[0] - t.pos[0])*(lr.pos[0] - t.pos[0]) +
            (lr.pos[1] - t.pos[1])*(lr.pos[1] - t.pos[1]) +
    (lr.pos[2] - t.pos[2])*(lr.pos[2] - t.pos[2]) > dist_interval_sq){
                printf("Sensor %d is now at (%g,%g,%g)\n", t.sensor,
                        t.pos[0], t.pos[1], t.pos[2]);
                lr = t;
        }
}

void    handle_button_change(void *userdata,const vrpn_BUTTONCB b)
{
        static int count=0;
        static int buttonstate = 1;
        int done = 0;

        if (b.state != buttonstate) {
             printf("button #%ld is in state %ld\n", b.button, b.state);
             buttonstate = b.state;
             count++;
        }
        if (count > 4)
                done = 1;
        *(int *)userdata = done;
}

int main(int argc, char *argv[])
{
        int     done = 0;
        vrpn_ForceDevice_Remote *forceDevice;
        vrpn_Tracker_Remote *tracker;
        vrpn_Button_Remote *button;

        /* initialize the force device */
        forceDevice = new vrpn_ForceDevice_Remote(PHANTOM_SERVER);
        forceDevice->register_change_handler(NULL, handle_force_change);

        /* initialize the tracker */
        tracker = new vrpn_Tracker_Remote(PHANTOM_SERVER);
        tracker->register_change_handler(NULL, handle_tracker_change);

        /* initialize the button */
        button = new vrpn_Button_Remote(PHANTOM_SERVER);
        button->register_change_handler(&done, handle_button_change);

    // before we make any triangle mesh setup calls we can choose which library
    // to use, the default is ghost
    //forceDevice->useGhost();
    forceDevice->useHcollide();

    // units are in meters
    forceDevice->setVertex(0,0.0,0.1,0.0);
    forceDevice->setVertex(1,0.1,-0.1,0.1);
    forceDevice->setVertex(2,-0.1,-0.1,0.1);
    forceDevice->setVertex(3,0.0,-0.1,-0.1);

    forceDevice->setTriangle(0,0,2,1);
    forceDevice->setTriangle(1,0,1,3);
    forceDevice->setTriangle(2,0,3,2);
    forceDevice->setTriangle(3,2,3,1);

        forceDevice->setSurfaceKspring(0.8);    // spring constant - units of
                                                // dynes/cm
        forceDevice->setSurfaceKdamping(0.001);   // damping constant -
                                                  // units of dynes*sec/cm
        forceDevice->setSurfaceFstatic(0.7);    // set static friction
        forceDevice->setSurfaceFdynamic(0.3);   // set dynamic friction

    // enable the trimesh and start displaying it
    // (this function should be called to enable each set of changes
    forceDevice->updateTrimeshChanges();

    // ---just for fun lets call some of the mesh manipulation functions
    // ---- (without really changing the tetrahedron much)

    // setting the vertex automatically adjusts all its adjacent triangles
    forceDevice->setVertex(0,0.0,0.11,0.0);

    forceDevice->removeTriangle(3);
    forceDevice->setTriangle(3,2,3,1);
    forceDevice->updateTrimeshChanges();

    // if you just set the triangle,
    // it is effectively the same as removing it and then setting it
    forceDevice->setTriangle(3,2,3,1);
    forceDevice->updateTrimeshChanges();
    // -------------------------------------------------------------------

    // now move the trimesh a centermeter along each axis
    /* setTrimeshTransform takes an array of 16 floates that describes
       a homogenous transformation matrix in row major order,
       NOTE that this resets the transform for the trimesh, instead
       of multiplying it */
    float xFormMatrix[16] = {1.0,0,0,.01,
                 0,1.0,0,.01,
                 0,0,1.0,-.01,
                 0,0,0.0,1.0};
    forceDevice->setTrimeshTransform(xFormMatrix);

        // main loop
        while (! done ){
                // Let the forceDevice send its data to remote force device
                forceDevice->mainloop();
                // Let tracker receive position information from remote tracker
                tracker->mainloop();
                // Let button receive button status from remote button
                button->mainloop();
        }

        // turn off the haptic display of the trimesh
        forceDevice->clearTrimesh();
}   /* main */

----------------------------------------------------------------------------------------

Here is an example that makes a nice 3D sinusoidal force field:
(note: As a safety feature, forces are only enabled (by the client) when
the button is pressed)

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

#define PHANTOM_SERVER "Phantom@phantom3"

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

void    handle_force_change(void *userdata, const vrpn_FORCECB f)
{
  static vrpn_FORCECB lr;        // last report
  static int first_report_done = 0;

  if ((!first_report_done) ||
    ((f.force[0] != lr.force[0]) || (f.force[1] != lr.force[1])
      || (f.force[2] != lr.force[2]))) {
    //printf("force is (%f,%f,%f)\n", f.force[0], f.force[1], f.force[2]);
  }

  first_report_done = 1;
  lr = f;
}

void    handle_tracker_change(void *userdata, const vrpn_TRACKERCB t)
{
  static vrpn_TRACKERCB lr; // last report
  static float dist_interval_sq = 0.004;
  float *pos = (float *)userdata;

  if ((lr.pos[0] - t.pos[0])*(lr.pos[0] - t.pos[0]) +
    (lr.pos[1] - t.pos[1])*(lr.pos[1] - t.pos[1]) +
    (lr.pos[2] - t.pos[2])*(lr.pos[2] - t.pos[2])  dist_interval_sq){
    //printf("Sensor %d is now at (%g,%g,%g)\n", t.sensor,
     //       t.pos[0], t.pos[1], t.pos[2]);
    lr = t;
  }
  pos[0] = t.pos[0];
  pos[1] = t.pos[1];
  pos[2] = t.pos[2];
}

void    handle_button_change(void *userdata, const vrpn_BUTTONCB b)
{
  static int buttonstate = 1;

  if (b.state != buttonstate) {
    printf("button #%ld is in state %ld\n", b.button, b.state);
    buttonstate = b.state;
  }

  *(int *)userdata = buttonstate;
}

int main(int argc, char *argv[])
{

  int     done = 0;
  float pos[3];
  int     forceInEffect = 0;
  int     forceEnabled = 0;
  vrpn_ForceDevice_Remote *forceDevice;
  vrpn_Tracker_Remote *tracker;
  vrpn_Button_Remote *button;

  /* initialize the force device */
  forceDevice = new vrpn_ForceDevice_Remote(PHANTOM_SERVER);
  forceDevice-register_change_handler(NULL, handle_force_change);

  /* initialize the tracker */
  tracker = new vrpn_Tracker_Remote(PHANTOM_SERVER);
  tracker-register_change_handler((void *)pos, handle_tracker_change);

  /* initialize the button */
  button = new vrpn_Button_Remote(PHANTOM_SERVER);
  button-register_change_handler((void *)&forceEnabled, handle_button_change);

  // main loop
  while (! done )
  {
    // Let the forceDevice send its messages to remote force device
    forceDevice-mainloop();

    // Let tracker receive position information from remote tracker
    tracker-mainloop();

    // Let button receive button status from remote button
    button-mainloop();

    if (forceEnabled) {
       forceDevice-setFF_Origin(pos[0],pos[1],pos[2]);
       // units = dynes
       forceDevice-setFF_Force(cos(pos[0]*20.0*M_PI),
                        cos(pos[1]*20.0*M_PI),cos(pos[2]*20.0*M_PI));
       // set derivatives of force field:
       // units = dynes/meter
       forceDevice-setFF_Jacobian(-20.0*M_PI*sin(pos[0]*20.0*M_PI), 0, 0,
                                    0, -20.0*M_PI*sin(pos[1]*20.0*M_PI), 0,
                                    0, 0, -20.0*M_PI*sin(pos[2]*20.0*M_PI));
       forceDevice-setFF_Radius(0.02); // 2cm radius of validity
       forceDevice-sendForceField();
       forceInEffect = 1;
    }
    else if (forceInEffect){
       forceDevice-stopForceField();
       forceInEffect = 0;
    }
  }

}   /* main */

Callback function

The vrpn_ForceDevice_Remote class provides information on changes in the force it is applying by calling a user callback function. 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. 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. For example, the force applied might be mapped to a vector displayed on the screen. In this case we would want to use the userdata pointer to pass the data that needs to be changed in order to change the appearance of the vector.

Member functions

There is an overloaded member function of the vrpn_ForceDevice_Remote class that is used to set the coefficients of the plane equation describing the plane (Ax + By + Cz + D = 0); the function is set_plane().

There are three member functions from the base vrpn_ForceDevice class that are also used to set surface parameters; they set the plane & trimesh: stiffness setSurfaceKspring(), damping setSurfaceKdamping(), static friction setSurfaceFstatic() and kinetic friction setSurfaceFdynamic().

There are also functions in the base class for setting the force field parameters; they set the approximation point setFF_Origin(), the force at that point setFF_Force(), the jacobian of the force field setFF_Jacobian() and the bounding radius setFF_Radius().

vrpn_Sound_Remote

vrpn_Sound is the base class for vrpn_Sound_Client and vrpn_Sound_Server. This document is broken into two main sections: client, server. This manual describes the underlying structure of the sound client and server. The descriptions of specific structures are only one interpretation of them. Users of these classes can use the structures to transport other information than which they were meant. For example, it is up to the programmer to always use radians or always use degrees. Although this document might mention one or the other, it is ultimately up to the programmer as to how the structures are interpreted. Be consistent.

vrpn_Sound

vrpn_Sound is used to provide some of the base VRPN functionality to classes that inherit from it. It is suggested that users start at a higher level than vrpn_Sound (at least vrpn_Sound_Client or vrpn_Sound_Server). vrpn_Sound is not discussed in this manual.

vrpn_Sound_Client

The sound client is derived publicly from vrpn_Sound and vrpn_Text_Receiver. vrpn_Sound provides the base VRPN functionality for the client. vrpn_Text_Receiver provides a mechanism for the client to receive messages from the vrpn_Sound_Server. This is useful for reporting warning and errors that might occur with the sound card or server in general.

There are two possibly ways to use this class: instantiated or inherited. Inheriting from vrpn_Sound_Client makes it possible to override the receiveTextMessage function. The function simply prints out messages it gets from the server. All messages, whether text, warnings, or errors are treated in the same manner. By overriding this function it is possible to treat the messages differently, define error levels, etc.

As with all VRPN code it is necessary to call the mainloop function periodically (maybe after each function call) to ensure that messages are passed around the network.

Constructor/ destructor:

vrpn_Sound_Client(const char * name, vrpn_Connection * c);
~vrpn_Sound_Client();

vrpn_Sound_Client takes a device name and a pointer to a vrpn_Connection. The device name is the name assigned in the vrpn.cfg file. Them following code returns a pointer to a sound client which is attached to the device "sound" on a connection to "oxygen-cs.cs.unc.edu" (note that we do a little busy waiting until the connection is set up):

vrpn_Connection * connection;
vrpn_Sound_Client * client;
connection = new vrpn_Connection("oxygen-cs.cs.unc.edu");
mymanager = new vrpn_Sound_Client("sound", connection);
while (!connection->connected()) {
 connection->mainloop();
 printf("waiting on sound connection.. \n ");
 Sleep(1);
}

The destructor currently does nothing.

Loading an audio scene:

vrpn_int32 LoadModel_local(const char *filename);
vrpn_int32 LoadModel_remote(const char *data);
vrpn_int32 LoadPolyQuad(const vrpn_QuadDef quad);
vrpn_int32 LoadPolyTri(const vrpn_TriDef tri);
vrpn_int32 LoadMaterial(const vrpn_int32 id, const vrpn_MaterialDef material);

The audio scene can be drawn by either loading a sound model file on the sound server machine, or by loading individual geometry. The provision to load a model from the sound client side is currently unavailable.

LoadModel_local takes a filename which identifies the sound model file to be loaded. The format of this file is defined by the server code. See documentation on the individual servers for this information. The filename provided should be an absolute path to the file. The following line of code loads the model file found at C:\Aureal_Sound_Server\cube_model.dat on the sound server machine.

Load_Model_local("C:\Aureal_Sound_Server\cube_model.dat");

Drawing geometry by using the LoadPoly\* functions is tedious work, although it is about the same as writing model files from scratch by hand. These functions are used in conjunction with the LoadMaterial function. These functions use structures define in vrpn_Sound.h (vrpn_MaterialDef, vrpn_TriDef, vrpn_QuadDef).

typedef struct _vrpn_MaterialDef {
 char material_name[MAX_MATERIAL_NAME_LENGTH];
 vrpn_float64 transmittance_gain;
 vrpn_float64 transmittance_highfreq;
 vrpn\_float64 reflectance_gain;
 vrpn_float64 reflectance_highfreq;
} vrpn_MaterialDef;

Material names should be descriptive of the material being modeled. The maximum length for a descriptive name is MAX_MATERIAL_NAME_LENGTH currently defined as 128 characters. The transmittance values describe how sounds move through the material while the reflectance values describe how sounds are reflected off of surfaces made of this material. Both of these have a gain and a high frequency value associated with them. The gain value determines how much the entire signal is attenuated, while the highfreq value determines how much the higher frequencies are attenuated. Some sample definitions might be (from Aureal's A3D 3.0 API manual):


name: trans.gain trans.highfreq refl.gain refl.highfreq CARPET 0.95 0.60 0.40 0.20 BRICK 0.20 0.50 0.90 0.80


typedef struct _vrpn_QuadDef {
 vrpn_int32 subQuad; // really a bool
 vrpn_float64 openingFactor;
 vrpn_int32 tag;
 vrpn_float64 vertices[4][3];
 char material_name[MAX_MATERIAL_NAME_LENGTH];
} vrpn\_QuadDef;

This structure is for defining quadrangles in the audio scene. The subQuad parameter acts as a boolean as to whether this quadrangle is acting as a subquad or not. Subquads are useful for defining windows or other openings in walls. The openingFactor parameter describes how "transparent" this quadrangle is. If the subQuad parameter is 0 then openingFactor is ignored. An open window would have an opening factor of 1.0, while shuttered windows might have an opening factor of 0.6. The tag parameter is a unique identifier for the surface. The material_name should be assigned to a previously defined material.

typedef struct _vrpn_TriDef {
 vrpn_int32 subTri;
 vrpn_float64 openingFactor;
 vrpn_int32 tag;
 vrpn_float64 vertices[3][3];
 char material_name[MAX_MATERIAL_NAME_LENGTH];
} vrpn_TriDef;

This structure is similar to the QuadDef except it handles triangles instead of quadrangles. The following code defines a brick wall with a triangular shuttered window in the sound client (previously defined):

vrpn_MatrialDef brickDef;
vrpn_QuadDef wallDef;
vrpn_TriDef windowDef;
strcpy(brickDef.material_name, "BRICK");
brickDef.transmittance_gain = 0.95;
brickDef.transmittance_highfreq = 0.60;
brickDef.reflectance_gain = 0.40;
brickDef.reflectance_highfreq = 0.20;
// in this case the wall is parallel with the floor..that's ok
strcpy(wallDef.material\_name,"BRICK");
wallDef.subQuad = 0;
wallDef.openingFactor = 0.0; // doesn't matter but set it anyway
wallDef.tag = 1;
wallDef.vertices[0][0] = 10.0;
wallDef.vertices[0][1] = 10.0;
wallDef.vertices[0][2] = 0.0;
wallDef.vertices[1][0] = 10.0;
wallDef.vertices[1][1] = -10.0;
wallDef.vertices[1][2] = 0.0;
wallDef.vertices[2][0] = -10.0;
wallDef.vertices[2][1] = -10.0;
wallDef.vertices[2][2] = 0.0;
wallDef.vertices[3][0] = -10.0;
wallDef.vertices[3][1] = 10.0;
wallDef.vertices[3][2] = 0.0;
strcpy(windowDef.material_name,"NONE");
windowDef.subQuad = 1;
windowDef.openingFactor = 0.6;
windowDef.tag = 1;
windowDef.vertices[0][0] = 5.0;
windowDef.vertices[0][1] = 0.0;
windowDef.vertices[0][2] = 0.0;
windowDef.vertices[1][0] = 0.0;
windowDef.vertices[1][1] = -5.0;
windowDef.vertices[1][2] = 0.0;
windowDef.vertices[2][0] = 0.0;
windowDef.vertices[2][1] = 5.0;
windowDef.vertices[2][2] = 0.0;
client->LoadMaterial(brickDef);
client->LoadPolyQuad(wallDef);
client->LoadPolyTri(windowDef);
client->mainloop();

There are several functions defined that should allow previously defined materials and polygons to be altered (moved, made more transparent, materials decaying etc). At this time they are not supported and thus not described in this document.

Sounds in the audio scene:

vrpn_SoundID loadSound(const char* sound,
  const vrpn_SoundID id,
  const vrpn_SoundDef soundDef);
vrpn_int32 unloadSound(const vrpn_SoundID id);
vrpn_int32 playSound(const vrpn_SoundID id, vrpn_int32 repeat);
vrpn_int32 stopSound(const vrpn_SoundID id);
vrpn_int32 setSoundVolume(const vrpn_SoundID id, const vrpn_float64 volume);
vrpn_int32 setSoundPose(const vrpn_SoundID id,
  vrpn_float64 position[3],
  vrpn_float64 orientation[4]);
  vrpn_int32 setSoundVelocity(const vrpn_SoundID id, const vrpn_float64 velocity[4]);
vrpn_int32 setSoundDistances(const vrpn_SoundID id,
  const vrpn_float64 max_front_dist,
  const vrpn_float64 min_front_dist,
  const vrpn_float64 max_back_dist,
  const vrpn_float64 min_back_dist);
vrpn_int32 setSoundConeInfo(const vrpn_SoundID id,
  const vrpn_float64 inner_angle,
  const vrpn_float64 outer_angle, const vrpn_float64 gain);
vrpn_int32 setSoundDopScale(const vrpn_SoundID id, vrpn_float64 dopfactor);
vrpn_int32 setSoundEqValue(const vrpn_SoundID id, vrpn_float64 eq_value);
vrpn_int32 setSoundPitch(const vrpn_SoundID id, vrpn_float64 pitch);

There are a number of functions for adding and manipulating sounds in the audio scene. Once sounds have been added to the scene, a specific sounds' parameters can be changed. The unique sound identifier provided in the loadSound function is used to specify a particular sound in later functions.

loadSound takes a filename for the sound, the unique identifier and a vrpn_SoundDef. vrpn_SoundDef is a structure that holds all information for a particular sound. The vrpn_PoseDef structure holds location information.

typedef struct _vrpn_PoseDef {
  vrpn_float64 position[3];
  vrpn_float64 orientation[4];
} vrpn_PoseDef;

position is a vector in XYZ order. Specification regarding the coordinate system is left up to the end users (client and server). orientation is a quaternion in XYZW order. quatlib is used to specify quaternion definitions. This pose structure is used for positioning sounds as well as listeners. The main point here is that the coordinates should be world coordinates! That is, where is the position relative to 0,0,0 in world coordinates and what is the orientation relative to no rotation in world coordinates.

typedef struct _vrpn_SoundDef { vrpn_PoseDef pose; vrpn_float64 velocity[4]; vrpn_float64 max_front_dist; vrpn_float64 min_front_dist; vrpn_float64 max_back_dist; vrpn_float64 min_back_dist; vrpn_float64 cone_inner_angle; vrpn_float64 cone_outer_angle; vrpn_float64 cone_gain; vrpn_float64 dopler_scale; vrpn_float64 equalization_val; vrpn_float64 pitch; vrpn_float32 volume; } vrpn_SoundDef;

vrpn_SoundDef used vrpn_PoseDef to hold the position information. The velocity parameter is a vector of length four (which doesn't make sense to me.. since we are simply defining velocity in the X, Y and Z directions [one idea is to maybe just set the first three values and ignore the fourth value]). The distance information is one of the more confusing aspects of the sound structure.

The distance information describes how far away from the source the noise becomes inaudible (max_front_dist) and how close to source the maximum volume level is heard (min_front_dist). For example, setting max_front_dist to 15 and min_front_dist to five, means that further than 15 units from the source the sound can not be heard and anywhere closer than 5 units from the source the volume level is volume. min/max_back_dist is for when the listener has his/her back to the source. Usually this is the same as the front distances.

The sound cone information specifies the sound cone. (The following is from the A3D 3.0 manual and is specific to the sound server based on the A3D libraries, but gives an idea how these parameters can be used.) "The two angles, [inner_angle] and [outer_angle] define the size of the cone. Between 0 [radians] and [inner_angle], the source will be at the level specified by volume. Between [inner_angle] and [outer_angle] the source gain is multiplied by the cone gain calculatyed by interpolating between 1.0 and [gain] according to the bearing of the listener from the source. From [outer_angle] to [PI radians], the source gain is multiplied by [gain]. Enabling a sound cone for a source, results in a small performance overhead as some extra calculatings need to be performed on the source. Setting either [outer_angle] to 0 or [gain] to 1 disabled cone processing and the sourc eis treated as omnidirectional." Make sure the client and the server use radians or degrees!

The dopler_scale parameter allows the dopler shift on a source to be exagerated or reduced in the following manner: a value of 1.0 is the default setting, a value of 2.0 would double the effect while 0.5 would halve the effect. The equalization_val parameter is similar to a treble control on a stereo. The pitch parameter changes the playback rate of a sample. The volume parameter is actually the gain setting, which specifies the loudness of the source.

The following code adds a source to the environment and begins playing the source:

vrpn\_SoundDef soundDef;
for (int i=0; i<3;i++) soundDef[0].pose.position[i] = 0.0;
for (i=0;i<3;i++) soundDef[0].pose.orientation[i] = 0.0;
soundDef[0].pose.orientation[3] = 1.0;
for (i=0;i<4;i++) soundDef[0].velocity[i] = 0.0;
soundDef[0].max_front_dist = 5.0;
soundDef[0].min_front_dist = 0.1;
soundDef[0].max_back_dist = 0.1;
soundDef[0].min_back_dist = 0.1;
soundDef[0].cone_inner_angle = 0;
soundDef[0].cone_outer_angle = 0;
soundDef[0].cone_gain = 1;
soundDef[0].volume = .8;
soundDef[0].dopler_scale =1.0;
soundDef[0].equalization_val = 1.0;
soundDef[0].pitch = 1.0;
client->loadSound("c:\\dance.wav", 0 , soundDef);
client->mainloop();

In the above example we have loaded a sound into the audio scene. This sound can be manipulated with the setSound* functions. The identifier expected by these functions is the unique identifier supplied in the loadSound functions. These functions are fairly straight-forward and will not be described in detail other than to say that the values can be set while the source is playing or not.

The playSound function takes a unique identifier and a repeat parameter. This parameter specifies the number of times the source should be played. A value of 0 indicates that the source should be played continously. It is wise to set the listener's position before beginning to play sounds. Neglecting to do this could cause the source to appear to be playing from arbitrary locations. The stopSound and unloadSound functions are straight-forward.

The Listener

vrpn_int32 setListenerPose(const vrpn_float64 position[3], const vrpn_float64 orientation[4]);
vrpn_int32 setListenerVelocity(const vrpn_float64 velocity[4]);

The listener's location is specified using the setListenerPose function. Curiously, it splits the pose into a position and orientation in the parameter list. This allows one or the other to be changed with out manipulating any underlying structures. Again, the orientation is a quaternion. The listener's velocity can be set using the setListenerVelocity function. Why this function takes a vector of length four is a mystery.

Other things:

void mainloop(const struct timeval * timeout=NULL);
virtual void receiveTextMessage(const char * message, vrpn_uint32 type, vrpn_uint32 level, struct timeval msg_time);

As mentioned above the client's mainloop function should be called to envoke VRPN message passing. It is recommended that the mainloop function be called after every vrpn_Sound_Client call if only so the programmer does not come to wonder why nothing is happening in the code to realize that the mainloop function was never called.

The receiveTextMessage function is used to capture messages sent from the server back to the client. This is extremely helpful for transmitting error and warning messages. The vrpn_Sound_Client class is inherited from vrpn_TextReceiver which allows for this capability. The function is virtual and can be overriden to perform filtering or other functions. The default function simply prints out all messages from the server directly to stdout.

vrpn_Sound_Server

vrpn_Sound_Server is derived from the vrpn_Sound class (and vrpn_Text_Sender class) and must be inheritted from to be used. It provides an end point opposite the client code for sound. Classes built on top of this class will most likely be specific to a particular sound library. The rest of this document describes how to go about constructing the sound server.

As VRPN messages are sent across the network to the sound server they are received and interpreted by vrpn_Sound_Client. Specific callbacks are called in response to these messages; it is the responsibility of the programmer to define the body of these callbacks. Below the functions are listed with a short description of what is being sent over and what should be done with that data.

void playSound(vrpn_SoundID id, vrpn_int32 repeat, vrpn_SoundDef soundDef);

When the client sends a play message to the server this function is called. The identifier should be a unique number that was specified in a loadSound\* function. The repeat parameter specifies how many times the sample will play. A value of 0 indicates that the sample should play continuously. soundDef is actually not filled in, so any value within it should be ignored!

void loadSoundLocal(char* filename, vrpn\_SoundID id, vrpn\_SoundDef soundDef);

When the client sends a request to load a sound this function is called. The filename should be an absolute path to the sound file. There is no guarantee that the path or filename are correct, however. The identifier should be unique, so it is acceptable to overwrite any pre-existing sounds with this identifier. The soundDef should have all the parameters for the sound source.

The following information is from the vrpn_Sound_Client portion of this document. The programmer can assume that is what will be sent over by the client, and this is what is meant be the values in the structure.

typedef struct _vrpn_PoseDef {
  vrpn_float64 position[3];
  vrpn_float64 orientation[4];
} vrpn_PoseDef;

position is a vector in XYZ order. Specification regarding the coordinate system is left up to the end users (client and server). orientation is a quaternion in XYZW order. quatlib is used to specify quaternion definitions. This pose structure is used for positioning sounds as well as listeners. The main point here is that the coordinates should be world coordinates! That is, where is the position relative to 0,0,0 in world coordinates and what is the orientation relative to no rotation in world coordinates.

typedef struct _vrpn_SoundDef {
  vrpn_PoseDef pose;
  vrpn_float64 velocity[4];
  vrpn_float64 max_front_dist;
  vrpn_float64 min_front_dist;
  vrpn_float64 max_back_dist;
  vrpn_float64 min_back_dist;
  vrpn_float64 cone_inner_angle;
  vrpn_float64 cone_outer_angle;
  vrpn_float64 cone_gain;
  vrpn_float64 dopler_scale;
  vrpn_float64 equalization_val;
  vrpn_float64 pitch;
  vrpn_float32 volume;
} vrpn_SoundDef;

vrpn_SoundDef used vrpn_PoseDef to hold the position information. The velocity parameter is a vector of length four (which doesn't make sense to me.. since we are simply defining velocity in X, Y and Z). The distance information is one of the more confusing aspects of the sound structure.

The distance information describes how far away from the source the noise disappears (max_front_dist) and how close to source the maximum volume level is heard (min_front_dist). For example, setting max_front_dist to 15 and min_front_dist to five, means that further than 15 units from the source the sound can not be heard and anywhere closer than 5 units from the source the volume level is volume. min/max_back_dist is for when the listener has his/her back to the source. Usually this is the same as the front distances.

The sound cone information specifies the sound cone. (The following is from the A3D 3.0 manual and is specific to the sound server based on the A3D libraries, but gives an idea how these parameters can be used.) "The two angles, [inner_angle] and [outer_angle] define the size of the cone. Between 0 [radians] and [inner_angle], the source will be at the level specified by volume. Between [inner_angle] and [outer_angle] the source gain is multiplied by the cone gain calculatyed by interpolating between 1.0 and [gain] according to the bearing of the listener from the source. From [outer_angle] to [PI radians], the source gain is multiplied by [gain]. Enabling a sound cone for a source, results in a small performance overhead as some extra calculatings need to be performed on the source. Setting either [outer_angle] to 0 or [gain] to 1 disabled cone processing and the sourc eis treated as omnidirectional." Make sure the client and the server use radians or degrees!

The dopler_scale parameter allows the dopler shift on a source to be exagerated or reduced in the following manner: a value of 1.0 is the default setting, a value of 2.0 would double the effect while 0.5 would halve the effect. The equalization_val parameter is similar to a treble control on a stereo. The pitch parameter changes the playback rate of a sample. The volume parameter is actually the gain setting, which specifies the loudness of the source.

void loadSoundRemote(char* file, vrpn_SoundID id, vrpn_SoundDef soundDef);

This function is currently not supported and should not be called. One idea for when this does get implemented is that the actual sound file could be streamed over the network.

void stopSound(vrpn_SoundID id);

Stop the sound with this identifier. If the sound with this identifier does not exist then maybe a warning message should be sent back to the client. If the sound is not playing then maybe nothing should be done.

void unloadSound(vrpn_SoundID id);

Unload the sound with this identifier. Hopefully this sound has been loaded, if not a warning message should be sent back to the client. Once unloaded then the all traces of this sound source should be gone. It should be possible to reuse this identifier after this function has been called. Any memory taken up by this source should be flushed.

void changeSoundStatus(vrpn_SoundID id, vrpn_SoundDef soundDef);

If more than one of this sound's parameters needs to change then this function is useful. See the information on vrpn_SoundDef in this section above.

void setListenerPose(vrpn_PoseDef pose);

See the information on vrpn_PoseDef above. The main thing to note is that this information is in world coordinates. Right now we only support one listener so there is no need to specify one.

void setListenerVelocity(vrpn_float64 *velocity);

A vector of length four is sent over. Why four? Not sure. The suggestion using the first three numbers as velocity in the X/Y/Z directions (respectively), and ignoring the fourth number.

void setSoundPose(vrpn_SoundID id, vrpn_PoseDef pose);

Note that the pose information should be in world coordinates.

void setSoundVelocity(vrpn_SoundID id, vrpn_float64 \*velocity);

A vector of length four is sent over. Why four? Not sure. The suggestion using the first three numbers as velocity in the X/Y/Z directions (respectively), and ignoring the fourth number.

void setSoundDistInfo(vrpn_SoundID id, vrpn_float64 *distinfo);

This is a vector of length four. The distance information is in the following order inside this vector: min_back, max_back, min_front, max_front. See the description above within the vrpn_SoundDef discussion for details on what these mean.

void setSoundConeInfo(vrpn_SoundID id, vrpn_float64 *coneinfo);

This is a vector of length three. The cone information is in the following order inside this vector: inner_angle, outer_angle, gain. The vrpn_SoundDef description above has one interpretation of these numbers.

void setSoundDoplerFactor(vrpn_SoundID id, vrpn_float64 doplerfactor);
void setSoundEqValue(vrpn_SoundID id, vrpn_float64 eqvalue);
void setSoundPitch(vrpn_SoundID id, vrpn_float64 pitch);
void setSoundVolume(vrpn_SoundID id, vrpn_float64 volume);

See the descriptions in the discussion on vrpn_SoundDef above to find some details on how these values can be interpreted. Note that volume is really gain (or the loudness of a source).

void loadModelLocal(const char *filename);

This function is called with a model file name. This file may or may not exist, the function should be able to handle both situations. The model file format is up to the programmer.

void loadModelRemote();

This function is not supported. I can think of two ways to implement this function (the function of sending model data from the client side over to the server): encode the textual description of the model from the client-side and interpret it on the server-side, or read the model file on the client-side and call LoadMaterial and LoadPoly* functions from there.

void loadPolyQuad(vrpn_QuadDef *quad);

We receive a pointer to a vrpn_QuadDef. See the description in vrpn_Sound_Client about this structure. One question is what to do if the material specified has not been defined previously.

void loadPolyTri(vrpn_TriDef * tri);

We receive a pointer to a vrpn_QuadDef. See the description in vrpn_Sound_Client about this structure. One question is what to do if the material specified has not been defined previously.

void loadMaterial(vrpn_MaterialDef *material, vrpn_int32 id);

We get a unique identifier and a pointer to a vrpn_MaterialDef. This identifier should be unique, if not a warning message should be sent back to the client.

void setPolyQuadVertices(vrpn_float64 vertices[4][3], const vrpn_int32 id);

This function is not supported. The idea is to be able to move the vertices (dynamic objects?) once a polygon has been loaded. The implementation of this is non-trivial.

void setPolyTriVertices(vrpn_float64 vertices[3][3], const vrpn_int32 id);

This function is not supported. The idea is to be able to move the vertices (dynamic objects?) once a polygon has been loaded. The implementation of this is non-trivial.

void setPolyOF(vrpn_float64 OF, vrpn_int32 tag);

This function is not supported. The idea is to be able to change the opening factor once a polygon has been loaded. The implementation of this is non-trivial.

void setPolyMaterial(const char * material, vrpn_int32 tag);

This function is not supported. The idea is to change the material of an object once a polygon has been loaded. The implementation of this is non-trivial.

vrpn_Imager

There is a presentation on the vrpn_Imager class, and the vrpn_Imager_Poser class, available at http://vrpn.github.io/assets/vrpn_Imager_talk_june_2008.ppt