DTrack

From HIT Lab Australia

Jump to: navigation, search
Passive and Active markers.

Within the VisionSpace Studio is a tracking system capable of observing the position and orientation of retroreflective passive markers. The markers available at this time are "Spikey" (passive), "Glasses" (passive) and a Flystick2 (active); the latter of which also includes buttons and an analog joystick (though the joystick update rate is not as fast as the marker position update). All of these markers can be successfully tracked simultaneously without any problems. It is not recommended to use the "Glasses" marker for anything other than headtracking as it is prone to being "lost" when moving quickly.

Due to the limitations of only having four infrared tracking cameras positioned above the screen and none behind the user, it is not possible to track any markers when they are obscured from more than 1 camera by a body (each marker must have 3 points visible to 3 cameras at all times). In practical use the only ramifications of this are that a marker would be unlikely to be tracked successfully if the user was looking away from the screen (it would continue to report the position/orientation of the marker when it was lost), but in such a scenario the user would not be able to see the results of their actions either. This means that the full range of orientation is technically not available to passive markers as when the marker is turned to face the user their arm may obscure the retroreflective balls from multiple cameras.

See also: Infrared Tracking System

Contents

Obtaining Passive Marker Data via VRPN

This section contains technical information and may require programming knowledge.

VRPN is an open-source (modified Boost) software interface for communicating across a network with tracking devices used in virtual reality software; VRPNNet is a C# wrapper around VRPN. DTrack fully supports VRPN, with both passive markers and flysticks being available to any VRPN client.

As VRPN considers DTrack to be a single device, despite it comprising multiple independent markers with buttons, each marker is considered to be a single sensor within one set of TrackerRemote, ButtonRemote and AnalogRemote for DTrack. Passive marker position and orientation data is encoded within a TrackerRemote, whilst button and analog stick data for each flystick is encoded in a ButtonRemote and AnalogRemote. Each ButtonRemote will assign 8 buttons (max) and 2 analogs (horizontal/vertical) for each flystick.

There are no identification numbers assigned to incoming data from ButtonRemote and AnalogRemote devices, only TrackerRemote devices, which may lead to minor confusion if both passive markers and Flysticks are being used simultaneously. Each ButtonRemote will have 8 buttons assigned to each Flystick2, with Flystick2 #1 numbered 0-7, Flystick2 #2 8-15 and so on; the same is true of the AnalogRemote but with 2 analogs per Flystick2.


VRPN: Flystick2 DTrack Console Application

This section contains technical information and may require programming knowledge.
Note: You will need prior knowledge of how to use C++ to successfully follow this tutorial. You may find more specific information about how to use the various remotes in VRPN at the Client code page on the VRPN website.

You will need:

Start by creating a new C++ project in your IDE of choice. You'll require three files:

  • Flystick.h
  • Flystick.cpp
  • VRPNFlystickConsole.cpp (main function in this file)

You will need to be able to import the following VRPN header files into your project, as well as have the compiled VRPN libraries available:

  • vrpn_Tracker.h
  • vrpn_Button.h
  • vrpn_Analog.h
  • quat.h (in the quat directory)

We'll begin by writing the Flystick header file. You'll need to include the above headers in the file, along with a few useful definitions:

// ID number of the Flystick's TrackerRemote in DTrack in the VisionSpace
#define FLYSTICK_TRACKER_ID 2
// Flystick2 only possesses six buttons
#define FLYSTICK_BUTTONS 6
// Analog remote data is sent in channels; note which channel is horizontal/vertical
#define ANALOG_REMOTE_HORIZONTAL 0
#define ANALOG_REMOTE_VERTICAL 1

Create a Flystick class definition in the header file. VRPN communicates updates for the flystick via Tracker, Button and Analog remotes, so add one of each as a private member variable to the class in the header:

class Flystick
{
private:
    vrpn_Tracker_Remote *_tremote;
    vrpn_Button_Remote *_bremote;
    vrpn_Analog_Remote *_aremote;
};

We'll need to store the data provided in each update (position, orientation, analog and button state). Due to the complications of callback methods in C++, we'll make these public static variables:

public:
    // Vector3 - same as q_vec_type from quatlib
    static double _position[3];
    // Quaternion - same as q_type from quatlib
    static double _orientation[4];
    static double _eulerOrientation[3];
    // Alignment of joysticks
    static double _analogHorizontal;
    static double _analogVertical;
    // Buttons
    static bool _buttonState[FLYSTICK_BUTTONS];

Whenever we call the mainloop() function on one of the remotes we created, VRPN will process updated information provided by the server (sent as UDP packets, so infrequent calls to mainloop() could process old data from the buffer). After processing it will call a VRPN_CALLBACK function we write with the updated information. Some templates for the type of function to use are below:

// Callback for a position or orientation change by the tracked device
static void VRPN_CALLBACK handle_tracker(void *userdata, const vrpn_TRACKERCB t)
{
	if (t.sensor != FLYSTICK_TRACKER_ID)
		return;	// ignore others - modify this to track passive markers also
	q_vec_copy(_position, t.pos);
	q_copy(_orientation, t.quat);
	q_to_euler(_eulerOrientation, _orientation);
}
 
// Callback for a button being pressed or released
static void VRPN_CALLBACK handle_button(void *userdata, const vrpn_BUTTONCB b)
{
	_buttonState[b.button] = (bool)b.state;
}
 
// Callback for changes in the analog stick position
static void VRPN_CALLBACK handle_analog(void *userdata, const vrpn_ANALOGCB a)
{
	_analogHorizontal = a.channel[ANALOG_REMOTE_HORIZONTAL];
	_analogVertical = a.channel[ANALOG_REMOTE_VERTICAL];
}

Note: using class member functions as callbacks is a little more involved, which is why this example uses static functions. Finish up the header by adding two more function declarations:

Flystick(char* serverAddress);
void UpdateFlystick();

The implementation of the Flystick class is quite trivial; we only need to initialise the static variables, write a constructor and the update function; the callbacks in the header file deal with updating the state of the Flystick. First we initialise the static variables:

double Flystick::_eulerOrientation[3] = {0, 0, 0};
double Flystick::_orientation[4] = {0, 0, 0, 0};
double Flystick::_position[3] = {0,0,0};
bool Flystick::_buttonState[FLYSTICK_BUTTONS] = {false, false, false, false, false, false};
double Flystick::_analogVertical = 0;
double Flystick::_analogHorizontal = 0;

To create each remote we initialise it using the address of the DTrack VRPN server, register the static callback function and disable warning output from the remote:

Flystick::Flystick(char* serverAddress)
{
	// Tracker Remote
	_tremote = new vrpn_Tracker_Remote(serverAddress);
	_tremote->register_change_handler(NULL, handle_tracker);
	_tremote->shutup = true;
 
	// Button remote
	_bremote = new vrpn_Button_Remote(serverAddress);
	_bremote->register_change_handler(NULL, handle_button);
	_bremote->shutup = true;
 
	// Analog remote
	_aremote = new vrpn_Analog_Remote(serverAddress);
	_aremote->register_change_handler(NULL, handle_analog);
	_aremote->shutup = true;
}

UpdateFlystick() is a function that should be called quite regularly by the owner of the Flystick object, as it processes only the oldest UDP packets in the buffer. If the program takes a long time to call UpdateFlystick this may introduce some lag to the marker; see Using a tracker when the application's main loop is slow for solutions to this problem.

void Flystick::UpdateFlystick()
{
	_tremote->mainloop();
	_bremote->mainloop();
	_aremote->mainloop();
}

To provide the console output we write a very simple main method, below, which will continuously update and print the state of the flystick to the console until terminated. VRPNFlystickConsole.cpp

// VRPNFlystickConsole.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include "Flystick.h"
#include <cstdlib>
 
 
int _tmain(int argc, _TCHAR* argv[])
{
	Flystick *flystick = new Flystick("DTrack@144.6.42.63");
 
	// Endless update loop
	while (true)
	{
		// Update the flystick remotes
		flystick->UpdateFlystick();
 
		// Clear console
		system("cls");
 
		// Output updated data
		printf("Position:\nX: %d\nY: %d\nZ: %d", flystick->_position[0], flystick->_position[1], flystick->_position[2]);
		printf("Orientation:\nYaw: %d\nPitch: %d\nRoll: %d", flystick->_eulerOrientation[0], flystick->_eulerOrientation[1], flystick->_eulerOrientation[2]);
		printf("Buttons:\nButton 0 Pressed? %i\nButton 1 Pressed? %i\nButton 2 Pressed? %i\nButton 3 Pressed? %i\nButton 4 Pressed? %i\nButton 5 Pressed? %i", 
			flystick->_buttonState[0], flystick->_buttonState[1], flystick->_buttonState[2], flystick->_buttonState[3], flystick->_buttonState[4], flystick->_buttonState[5]);
		printf("Analog stick:\nHorizontal: %d\nVertical: %d", flystick->_analogHorizontal, flystick->_analogVertical);
	}
 
	return 0;
}

Flystick.h

// Generic C++ Flystick Interface using VRPN
 
// For Flystick components
#include <vrpn_Tracker.h>
#include <vrpn_Button.h>
#include <vrpn_Analog.h>
// For quaternion operations
#include <quat.h>
 
// ID number of the Flystick's TrackerRemote in DTrack in the VisionSpace
#define FLYSTICK_TRACKER_ID 2
// Flystick2 only possesses six buttons
#define FLYSTICK_BUTTONS 6
// Analog remote data is sent in channels; note which channel is horizontal/vertical
#define ANALOG_REMOTE_HORIZONTAL 0
#define ANALOG_REMOTE_VERTICAL 1
 
class Flystick
{
private:
	vrpn_Tracker_Remote *_tremote;
	vrpn_Button_Remote *_bremote;
	vrpn_Analog_Remote *_aremote;
 
	// Callback for a position or orientation change by the tracked device
	static void VRPN_CALLBACK handle_tracker(void *userdata, const vrpn_TRACKERCB t)
	{
		if (t.sensor != FLYSTICK_TRACKER_ID)
			return;	// ignore others - modify this to track passive markers also
		q_vec_copy(_position, t.pos);
		q_copy(_orientation, t.quat);
		q_to_euler(_eulerOrientation, _orientation);
	}
 
	// Callback for a button being pressed or released
	static void VRPN_CALLBACK handle_button(void *userdata, const vrpn_BUTTONCB b)
	{
		_buttonState[b.button] = (bool)b.state;
	}
 
	// Callback for changes in the analog stick position
	static void VRPN_CALLBACK handle_analog(void *userdata, const vrpn_ANALOGCB a)
	{
		_analogHorizontal = a.channel[ANALOG_REMOTE_HORIZONTAL];
		_analogVertical = a.channel[ANALOG_REMOTE_VERTICAL];
	}
 
public:
	// Vector3 - same as q_vec_type from quatlib
	static double _position[3];
	// Quaternion - same as q_type from quatlib
	static double _orientation[4];
	static double _eulerOrientation[3];
	// Alignment of joysticks
	static double _analogHorizontal;
	static double _analogVertical;
	// Buttons
	static bool _buttonState[FLYSTICK_BUTTONS];
 
 
	Flystick(char* serverAddress);
	void UpdateFlystick();
};

Flystick.cpp

#include "stdafx.h"
#include "Flystick.h"
 
double Flystick::_eulerOrientation[3] = {0, 0, 0};
double Flystick::_orientation[4] = {0, 0, 0, 0};
double Flystick::_position[3] = {0,0,0};
bool Flystick::_buttonState[FLYSTICK_BUTTONS] = {false, false, false, false, false, false};
double Flystick::_analogVertical = 0;
double Flystick::_analogHorizontal = 0;
 
Flystick::Flystick(char* serverAddress)
{
	// Tracker Remote
	_tremote = new vrpn_Tracker_Remote(serverAddress);
	_tremote->register_change_handler(NULL, handle_tracker);
	_tremote->shutup = true;
 
	// Button remote
	_bremote = new vrpn_Button_Remote(serverAddress);
	_bremote->register_change_handler(NULL, handle_button);
	_bremote->shutup = true;
 
	// Analog remote
	_aremote = new vrpn_Analog_Remote(serverAddress);
	_aremote->register_change_handler(NULL, handle_analog);
	_aremote->shutup = true;
}
 
 
void Flystick::UpdateFlystick()
{
	_tremote->mainloop();
	_bremote->mainloop();
	_aremote->mainloop();
}

These classes are bare-bones for functionality and could be expanded to support passive markers or multiple flysticks. This is left as an exercise for the user.

VrpnNet: Flystick2 DTrack Console Application

This section contains technical information and may require programming knowledge.
Note: You will need prior knowledge of how to use C# to successfully follow this tutorial.

You will need:

Utilising the .NET port of VRPN makes the process of acquiring data from a Flystick a fairly trivial affair.

Start by creating a new C# console application:

Different versions of Visual Studio may not match the above image.

Modify the default application to add a new class, Flystick.cs, and add a reference to VrpnNet.dll:

Right click the project, add a new item and select Class.

Now that the project is set up correctly, it's time to write the Flystick.cs class. Begin by adding the Vrpn namespace to your project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vrpn;

The Flystick sends data to a ButtonRemote, AnalogRemote and TrackerRemote, so we add local variables for those:

class Flystick
{
    // Vrpn Remotes for Flystick data
    private TrackerRemote _tremote;
    private ButtonRemote _bremote;
    private AnalogRemote _aremote;
}

Each of the remotes will send updates via events, which means we also require some additional local variables to track the information during and after each update:

class Flystick
{
    // ID number of the Flystick's TrackerRemote in DTrack in the VisionSpace
    private const int FLYSTICK_TRACKER_ID = 2;
    // Flystick2 only possesses six buttons
    private const int FLYSTICK_BUTTONS = 6;
    // Analog remote data is sent in channels; note which channel is horizontal/vertical
    private const int ANALOG_REMOTE_HORIZONTAL = 0;
    private const int ANALOG_REMOTE_VERTICAL = 1;
 
    // Vrpn Remotes for Flystick data
    private TrackerRemote _tremote;
    private ButtonRemote _bremote;
    private AnalogRemote _aremote;
 
    // Storage for flystick data after updates
    private Vector3 _position;
    private Quaternion _orientation;
    private bool[] _buttonState;
    private double _analogHorizontal;
    private double _analogVertical;
}

Set up some properties to allow proper encapsulation of the Flystick data:

// Storage for flystick data after updates
private Vector3 _position;
public double X { get { return _position.X; } }
public double Y { get { return _position.Y; } }
public double Z { get { return _position.Z; } }
private Quaternion _orientation;
public Quaternion Orientation { get { return new Quaternion(_orientation.X, _orientation.Y, _orientation.Z, _orientation.W); } }
private bool[] _buttonState;
public bool B0 { get { return _buttonState[0]; } }
public bool B1 { get { return _buttonState[1]; } }
public bool B2 { get { return _buttonState[2]; } }
public bool B3 { get { return _buttonState[3]; } }
public bool B4 { get { return _buttonState[4]; } }
public bool B5 { get { return _buttonState[5]; } }
private double _analogHorizontal;
public double AnalogHorizontal { get { return _analogHorizontal; } }
private double _analogVertical;
public double AnalogVertical { get { return _analogVertical; } }

Each remote requires a delegate method to handle the update event. Data from VRPN is passed to the method as EventArgs; each remote follows a different format for EventArgs. The methods should be constructed similarly to the below:

private void DTrackTrackerChanged(object sender, TrackerChangeEventArgs e)
{
    // The program could be modified to handle other passive markers by removing this if statement and creating storage for them.
    if (e.Sensor != FLYSTICK_TRACKER_ID)
        return;
    // Update with a new position and orientation for the Flystick
    _position = e.Position;
    _orientation = e.Orientation;
}
 
private void DTrackButtonChanged(object sender, ButtonChangeEventArgs e)
{
    // Button values are passed as booleans; code to call events for pressing down or up should be included here if desired
    _buttonState[e.Button] = e.IsPressed;
}
 
private void DTrackAnalogChanged(object sender, AnalogChangeEventArgs e)
{
    // Horizontal and vertical position data for the analog stick is provided via channels
    _analogHorizontal = e.Channels[ANALOG_REMOTE_HORIZONTAL];
    _analogVertical = e.Channels[ANALOG_REMOTE_VERTICAL];
}

There should be no default constructor for this class, as there is every chance that the IP address or name of the VRPN server we are connecting to will change. Setting up the remotes and attaching the delegate methods to the remotes' change events is trivial:

Flystick(string serverAddress)
{
    // Initialise variables
    _position = new Vector3(0,0,0);
    _orientation = new Quaternion(0,0,0,0);
    _buttonState = new bool[FLYSTICK_BUTTONS];
 
    // Tracker remote
    _tremote = new TrackerRemote(serverAddress);
    _tremote.PositionChanged += new TrackerChangeEventHandler(DTrackTrackerChanged);
    _tremote.MuteWarnings = true;
 
    // Button remote
    _bremote = new ButtonRemote(serverAddress);
    _bremote.ButtonChanged += new ButtonChangeEventHandler(DTrackButtonChanged);
    _bremote.MuteWarnings = true;
 
    // Analog remote
    _aremote = new AnalogRemote(serverAddress);
    _aremote.AnalogChanged += new AnalogChangeEventHandler(DTrackAnalogChanged);
    _aremote.MuteWarnings = true;
}

The last method to create in the Flystick class is the update method. In order to update the Flystick data it is important to regularly call the Update() method of each remote being used. This method would need to be called by the class using the Flystick:

public void UpdateFlystick()
{
    _tremote.Update();
    _bremote.Update();
    _aremote.Update();
}

Back in the Program.cs file, create a Flystick object and initialise it with the address of the DTrack VRPN server:

class Program
{
    private const string DTRACK_SERVER_ADDRESS = "DTrack@144.6.42.63";
 
    static void Main(string[] args)
    {
        Flystick flystick = new Flystick(DTRACK_SERVER_ADDRESS);
    }
}

Now for displaying the Flystick data in the console. We'll write an endless loop for this application, but for GUI-based applications it would be worthwhile investigating the use of a Miller Loop to update the Flystick.

// Endless update loop
while (true)
{
    // Update the flystick remotes
    flystick.UpdateFlystick();
 
    // Clear the screen from the last update
    Console.Clear();
 
    // Display the data
    Console.WriteLine("Position:\nX: {0:00:00}\nY: {1:00:00}\nZ: {2:00:00}", flystick.X, flystick.Y, flystick.Z);
    // Quaternion is likely difficult to understand; converting to a human-readable format would be wise
    Console.WriteLine("Orientation:\nX: {0:00:00}\nY: {1:00:00}\nZ: {2:00:00}\nW: {3:00:00}", flystick.Orientation.X, flystick.Orientation.Y, flystick.Orientation.Z, flystick.Orientation.W);
    Console.WriteLine("Buttons:\nButton 0 Pressed? {0}\nButton 1 Pressed? {1}\nButton 2 Pressed? {2}\nButton 3 Pressed? {3}\nButton 4 Pressed? {4}\nButton 5 Pressed? {5}", flystick.B0, flystick.B1, flystick.B2, flystick.B3, flystick.B4, flystick.B5);
    Console.WriteLine("Analog Stick:\nHorizontal: {0:00:00}\nVertical: {1:00:00}", flystick.AnalogHorizontal, flystick.AnalogVertical);
}

That's it, you should now have a class that is capable of obtaining Flystick data from DTrack and some ideas on how to expand its usefulness to incorporate passive markers or writing a GUI program. The full class files are below:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace VrpnNetConsoleApp
{
    class Program
    {
        private const string DTRACK_SERVER_ADDRESS = "DTrack@144.6.42.63";
 
        static void Main(string[] args)
        {
            Flystick flystick = new Flystick(DTRACK_SERVER_ADDRESS);
            // Endless update loop
            while (true)
            {
                // Update the flystick remotes
                flystick.UpdateFlystick();
 
                // Clear the screen from the last update
                Console.Clear();
 
                // Display the data
                Console.WriteLine("Position:\nX: {0:00:00}\nY: {1:00:00}\nZ: {2:00:00}", flystick.X, flystick.Y, flystick.Z);
                // Quaternion is likely difficult to understand; converting to a human-readable format would be wise
                Console.WriteLine("Orientation:\nX: {0:00:00}\nY: {1:00:00}\nZ: {2:00:00}\nW: {3:00:00}", flystick.Orientation.X, flystick.Orientation.Y, flystick.Orientation.Z, flystick.Orientation.W);
                Console.WriteLine("Buttons:\nButton 0 Pressed? {0}\nButton 1 Pressed? {1}\nButton 2 Pressed? {2}\nButton 3 Pressed? {3}\nButton 4 Pressed? {4}\nButton 5 Pressed? {5}", flystick.B0, flystick.B1, flystick.B2, flystick.B3, flystick.B4, flystick.B5);
                Console.WriteLine("Analog Stick:\nHorizontal: {0:00:00}\nVertical: {1:00:00}", flystick.AnalogHorizontal, flystick.AnalogVertical);
            }
        }
    }
}

Flystick.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vrpn;
 
namespace VrpnNetConsoleApp
{
    class Flystick
    {
        // ID number of the Flystick's TrackerRemote in DTrack in the VisionSpace
        private const int FLYSTICK_TRACKER_ID = 2;
		// Flystick2 only possesses six buttons
		private const int FLYSTICK_BUTTONS = 6;
        // Analog remote data is sent in channels; note which channel is horizontal/vertical
        private const int ANALOG_REMOTE_HORIZONTAL = 0;
        private const int ANALOG_REMOTE_VERTICAL = 1;
 
        // Vrpn Remotes for Flystick data
        private TrackerRemote _tremote;
        private ButtonRemote _bremote;
        private AnalogRemote _aremote;
 
        // Storage for flystick data after updates
        private Vector3 _position;
		public double X { get { return _position.X; } }
        public double Y { get { return _position.Y; } }
        public double Z { get { return _position.Z; } }
        private Quaternion _orientation;
		public Quaternion Orientation { get { return new Quaternion(_orientation.X, _orientation.Y, _orientation.Z, _orientation.W); } }
        private bool[] _buttonState;
		public bool B0 { get { return _buttonState[0]; } }
		public bool B1 { get { return _buttonState[1]; } }
		public bool B2 { get { return _buttonState[2]; } }
		public bool B3 { get { return _buttonState[3]; } }
		public bool B4 { get { return _buttonState[4]; } }
		public bool B5 { get { return _buttonState[5]; } }
        private double _analogHorizontal;
		public double AnalogHorizontal { get { return _analogHorizontal; } }
        private double _analogVertical;
		public double AnalogVertical { get { return _analogVertical; } }
 
 
        public Flystick(string serverAddress)
        {
            // Initialise variables
            _position = new Vector3(0, 0, 0);
            _orientation = new Quaternion(0, 0, 0, 0);
            _buttonState = new bool[FLYSTICK_BUTTONS];
 
            // Tracker remote
            _tremote = new TrackerRemote(serverAddress);
            _tremote.PositionChanged += new TrackerChangeEventHandler(DTrackTrackerChanged);
            _tremote.MuteWarnings = true;
 
            // Button remote
            _bremote = new ButtonRemote(serverAddress);
            _bremote.ButtonChanged += new ButtonChangeEventHandler(DTrackButtonChanged);
            _bremote.MuteWarnings = true;
 
            // Analog remote
            _aremote = new AnalogRemote(serverAddress);
            _aremote.AnalogChanged += new AnalogChangeEventHandler(DTrackAnalogChanged);
            _aremote.MuteWarnings = true;
        }
 
        public void UpdateFlystick()
        {
            _tremote.Update();
            _bremote.Update();
            _aremote.Update();
        }
 
        private void DTrackTrackerChanged(object sender, TrackerChangeEventArgs e)
        {
            // The program could be modified to handle other passive markers by removing this if statement and creating storage for them.
            if (e.Sensor != FLYSTICK_TRACKER_ID)
                return;
            // Update with a new position and orientation for the Flystick
            _position = e.Position;
            _orientation = e.Orientation;
        }
 
        private void DTrackButtonChanged(object sender, ButtonChangeEventArgs e)
        {
            // Button values are passed as booleans; code to call events for pressing down or up should be included here if desired
            _buttonState[e.Button] = e.IsPressed;
        }
 
        private void DTrackAnalogChanged(object sender, AnalogChangeEventArgs e)
        {
            // Horizontal and vertical position data for the analog stick is provided via channels
            _analogHorizontal = e.Channels[ANALOG_REMOTE_HORIZONTAL];
            _analogVertical = e.Channels[ANALOG_REMOTE_VERTICAL];
        }
    }
}


Obtaining Passive Marker Data via UIVA

This section contains technical information and may require programming knowledge.

UIVA is an open-source (modified Boost) client/server system which manages a connection to VRPN and simplifies the process of obtaining VRPN tracking information in Unity3D (and other C# applications), with the drawback of requiring a server instance to be running (thus complicating application launch); the version of UIVA distributed within HIT Lab AU has DTrack support written into it.

To utilise UIVA to obtain tracking data from DTrack in C#:

  1. Initialise an instance of the compiled UIVA_Server, including in its configuration file the following: DEV_DTRACK DTrack@localhost, replacing localhost with the IP address of the DTrack VRPN server.
  2. Create an instance of UIVA_Client, passing in the address of the UIVA_Server.
  3. Call the client functions GetDTrackPositionData and GetDTrackFlystickData to obtain position, orientation, button and analog data for each remote.

Unity3D: Passive Markers

This section contains technical information and may require programming knowledge.
Note: This requires a version of UIVA that supports DTrack2. The official distribution may not.

The following script, when attached to an object, will make it move in precisely the same manner as a passive marker. It requires access to uiva.dll to function: DTrackPassiveMovable.cs

using UnityEngine;
using System.Collections;
using System;
using UIVA; // must have access to uiva.dll somewhere in the Assets folder of the Unity project.
// Requires a rigidbody to react via physics.  Will disable gravity on it.
[RequireComponent(typeof(Rigidbody))]
public class DTrackPassiveMovable : MonoBehaviour
{
    private UIVA_Client client;
    public int MarkerID = 0;
 
    void Start()
    {
        // Requires a UIVA server running on the local machine
        client = new UIVA_Client("localhost");
 
        // Disable gravity to prevent the object falling through the world when not tracked
        this.rigidbody.useGravity = false;
    }
 
    // Move the object with the marker
    void FixedUpdate()
    {
        // Temp variables - UIVA requires passing doubles, but Unity prefers floats
        double[] position = new double [3];
        double[] orientation = new double [4];
        DateTime dtLastUpdate;    // Discard immediately; we don't really need it.
 
        // Acquire updated position information
        client.GetDTrackPositionData (
            1,    // This is the ID number of the DTrack we're connected to, but we only have one
            (int)MarkerID,
            out position[0],    // X
            out position[1],    // Y (Vector3 Position)
            out position[2],    // Z
            out orientation[0],    // X
            out orientation[1],    // Y
            out orientation[2],    // Z
            out orientation[3],    // W (Quaternion Orientation)
            out dtLastUpdate
        );
 
        this.rigidbody.MovePosition( new Vector3 (
            (float)position[0],
            (float)position[1],
            (float)position[2]
        )*10);    // Multiply by ten to make the movement more noticable - it's reported in meters.
        this.rigidbody.MoveRotation( new Quaternion (
            (float)orientation[0],
            (float)orientation[1],
            (float)orientation[2],
            (float)orientation[3]
        ));
    }
}

Unity3D: Flystick

This section contains technical information and may require programming knowledge.
Note: This example only supports a single flystick as it is not currently possible to match arbitrary numbers of passive markers and flysticks to the correct buttons.
Note: This requires a version of UIVA that supports DTrack2. The official distribution may not.

The following script is a modification of the passive marker script above and includes debugging output indicating the change in button or analog stick state:

using UnityEngine;
using System.Collections;
using System;
using UIVA; // must have access to uiva.dll somewhere in the Assets folder of the Unity project.
// Requires a rigidbody to react via physics.  Will disable gravity on it.
[RequireComponent(typeof(Rigidbody))]
public class DTrackFlystickMovable : MonoBehaviour
{
    public const int DTRACK_BUTTONS_PER_FLYSTICK = 8;
    public const int DTRACK_ANALOGS_PER_FLYSTICK = 2;
 
    private UIVA_Client client;
    public int MarkerID = 2;
 
    // Flystick data
    private bool[] flystickButtons;
    private double analogHorizontal;
    private double analogVertical;
 
    void Start()
    {
        // Requires a UIVA server running on the local machine
        client = new UIVA_Client("localhost");
 
		flystickButtons = new bool[DTRACK_BUTTONS_PER_FLYSTICK];
 
        // Disable gravity to prevent the object falling through the world when not tracked
        this.rigidbody.useGravity = false;
    }
 
    // Move the object with the marker
    void FixedUpdate()
    {
        // Temp variables - UIVA requires passing doubles, but Unity prefers floats
        double[] position = new double [3];
        double[] orientation = new double [4];
        DateTime dtLastUpdate;    // Discard immediately; we don't really need it.
        // Flystick
        string buttons;
        double horizontal, vertical;
 
        // Acquire updated position information
        client.GetDTrackPositionData (
            1,    // This is the ID number of the DTrack we're connected to, but we only have one
            (int)MarkerID,
            out position[0],    // X
            out position[1],    // Y (Vector3 Position)
            out position[2],    // Z
            out orientation[0],    // X
            out orientation[1],    // Y
            out orientation[2],    // Z
            out orientation[3],    // W (Quaternion Orientation)
            out dtLastUpdate
        );
 
        this.rigidbody.MovePosition( new Vector3 (
            (float)position[0],
            (float)position[1],
            (float)position[2]
        )*10);    // Multiply by ten to make the movement more noticable - it's reported in meters.
        this.rigidbody.MoveRotation( new Quaternion (
            (float)orientation[0],
            (float)orientation[1],
            (float)orientation[2],
            (float)orientation[3]
        ));
 
        // Flystick
        client.GetDTrackFlystickData (
            1,
            (int)MarkerID,
            out horizontal,    // Vertical joystick value
            out vertical,    // Horizontal joystick value
            out buttons,    // Buttons (string of 0/1)
            out dtLastUpdate
        );
 
        if (horizontal != analogHorizontal)
        {
            analogHorizontal = horizontal;
            Debug.Log("Analog horizontal changed to: "+analogHorizontal);
        }
        if (vertical != analogVertical)
        {
            analogVertical = vertical;
            Debug.Log("Analog vertical changed to "+analogVertical);
        }
 
        // Buttons with a value of 1 are active
        for (int i=0; i<buttons.Length; i++)
        {
            if (flystickButtons[i] != (buttons[i].Equals("1") ? true : false))
            {
                flystickButtons[i] = buttons[i].Equals("1");
                Debug.Log("Button "+i+" changed to " + ((flystickButtons[i]) ? "up." : "down."));
            }
        }
    }
}