When writing a VR Juggler application object, direct access to hardware devices is not allowed. This is a design choice that helps facilitate the portability of VR applications by preventing them from depending on specific input device makes and models. Instead, the applications are granted access to the device through a proxy. A proxy is nothing more than an intermediary who forwards information between two parties. In this case, the two parties are a VR Juggler application and an input device. The application makes requests on the input device through the proxy.
Proxies are acquired through the Gadgeteer Input Manager, but the process is not entirely straightforward. To assist with the use of device proxies, Gadgeteer provides what are called device interfaces. Device interfaces hide the details of proxy acquisition through the Input Manager, but the concept of device interfaces is something that often causes confusion for those new to VR Juggler application programming. Two object-oriented design patterns are combined by device interfaces: smart pointers and proxies. Within this section, we aim to explain Gadgeteer device proxies and device interfaces clearly and simply. We begin with high-level descriptions of both and then move on to their use in VR Juggler application objects.
As noted above, access to input device data is granted through
device proxies allocated by the Gadgeteer Input Manager. For each
type of input device (digital, analog, keyboard/mouse, etc.), there
is a device proxy class. As a programmer of VR Juggler applications,
knowledge of such proxies does not have to be terribly in-depth. The
fact is, most VR Juggler application object programmers will
probably never need to know more about the interface of a
type-specific device proxy than the return type of its data request
method (usually named getData()). We will
see more about the data request method in the explanation of device
interfaces below. Most of the perceived complexity in the
type-specific proxy classes is only important to Gadgeteer's
internal maintenance of the active proxies. The following is the
complete list of proxy classes in Gadgeteer 1.0 (used by VR Juggler
2.0):
gadget::AnalogProxyThe proxy type for analog input devices (those of type
gadget::Analog). It is defined in the header file
gadget/Type/AnalogProxy.h. The return
type of
gadget::AnalogProxy::getData() is
float.
gadget::CommandProxyThe proxy type for command-oriented input devices (those
of type gadget::Command). It is defined in the header file
gadget/Type/CommandProxy.h. The return
type of
gadget::CommandProxy::getData() is
int.
gadget::DigitalProxyThe proxy type for digital (on/off) input devices (those
of type gadget::Digital). It is defined in the header file
gadget/Type/DigitalProxy.h. The return
type of
gadget::DigitalProxy::getData() is
gadget::Digital::State, an enumerated type. This
means that values returned by
gadget::DigitalProxy::getData() can
be treated as integers.
gadget::GloveProxyThe proxy type for glove input devices (those of type
gadget::Glove). It is defined in the header file
gadget/Type/GloveProxy.h. The return type
of gadget::GloveProxy::getData() is
gadget::GloveData.
gadget::KeyboardMouseProxyThe proxy type for keyboard/mouse input handlers (those
of type gadget::KeyboardMouse). It is defined in the header file
gadget/Type/KeyboardMouseProxy.h. There
is no method
gadget::KeyboardMouse::getData().
Rather, the method to use for querying input data is
gadget::KeyboardMouseProxy::getEventQueue(),
the return type of which is
gadget::KeyboardMouse::EventQueue. We explain more about this below in the section called “Using
gadget::KeyboardMouseInterface”.
gadget::PositionProxyThe proxy type for position tracking input devices
(those of type
gadget::Position). It is defined in the header file
gadget/Type/PositionProxy.h. The return
type of
gadget::PositionProxy::getData() is
gmtl::Matrix44f. The method
gadget::PositionProxy::getData()
takes an optional float paramter that indicates
the units to use for the returned transformation matrix. The
value must be a conversion factor from meters to the desired
units.
gadget::StringProxyThe proxy type for string (text- or word-driven) input
devices (those of type
gadget::String). It is defined in the header file
gadget/Type/StringProxy.h. The return
type of
gadget::StringProxy::getData() is
std::string.
In summary, the important thing to know is that a proxy is a pointer to a physical device. Application object programmers should normally use the higher level device interface as the mechanism to acquire a proxy and read data from the proxied device. The device interface encapsulates some type of proxy that in turn points to an input device. That device can be a wand, a keyboard, a light sensor, or a home-brewed device that reads some input and returns it to Gadgeteer in a meaningful way. That is a lot of indirection, but it makes the handling of physical devices by Gadgeteer incredibly powerful.
Device interfaces are designed to act as wrappers around
type-specific device properties. This is implemented through the
(template) class
gadget::DeviceInterface<T>. Applications could use the proxy classes directly,
but as we have already noted, acquiring the desired proxies from the
Gadgeteer Input Manager is not wholly straightforward. The
type-specific instances of
gadget::DeviceInterface<T> (such as
gadget::PositionInterface,
gadget::DigitalInterface, etc.) simplify
acquisition of proxies. Thus, typical VR Juggler application objects
will have one or more device interface member variables and no proxy
member variables.
The class
gadget::DeviceInterface<T> is a
templated class based on the proxy type it wraps. The following is
the complete list of available
gadget::DeviceInterface<T>
instantiations in Gadgeteer 1.0 (used by VR Juggler 2.0):
gadget::AnalogInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::AnalogProxy>.
It wraps the proxy type
gadget::AnalogProxy and is used for reading data from analog
devices. Include the header file
gadget/Type/AnalogInterface.h.
gadget::CommandInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::CommandProxy>.
It wraps the proxy type
gadget::CommandProxy and is used for reading data from
command-driven devices. Include the header file
gadget/Type/CommandInterface.h.
gadget::DigitalInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::DigitalProxy>.
It wraps the proxy type
gadget::DigitalProxy and is used for reading data from digital
(on/off) devices. Include the header file
gadget/Type/DigitalInterface.h.
gadget::GloveInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::GloveProxy>.
It wraps the proxy type
gadget::GloveProxy and is used for reading data from glove
devices. Include the header file
gadget/Type/GloveInterface.h.
gadget::KeyboardMouseInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::KeyboardMouseProxy>.
It wraps the proxy type
gadget::KeyboardMouseProxy and is used for reading data from analog
devices. Include the header file
gadget/Type/KeyboardMouseInterface.h.
gadget::PositionInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::PositionProxy>.
It wraps the proxy type
gadget::PositionProxy and is used for reading data from position
tracking devices. Include the header file
gadget/Type/PositionInterface.h.
gadget::StringInterfaceThis is a typedef for the template instantiation
gadget::DeviceInterface<gadget::StringProxy>.
It wraps the proxy type
gadget::StringProxy and is used for reading data from string (text-
or word-driven) devices. Include the header file
gadget/Type/StringInterface.h.
The typedefs are provided to make the application object code more readable.
In the application object, a device interface member variable is used as a smart pointer to the proxy. In C++, a smart pointer is not usually an actual object pointer. Instead, the class acting as a smart pointer overloads the dereference operator -> so that a special action can be taken when the “pointer” is dereferenced. The dereference operator is just another operator like the addition and subtraction operators, and overloading the deference operator allows some extra work to be done behind the scenes. On the surface, the code looks exactly the same as a normal pointer dereference, and in most cases, people reading and writing the code can think of the smart pointer as a standard pointer. It may also be convenient to think of a smart pointer as a handle.
With that background, we can move on to explain how
gadget::DeviceInterface<T> uses these
concepts. In user code, there will be instances of types such as
gadget::DigitalInterface,
gadget::PositionInterface,
gadget::KeyboardMouseInterface, and the like. Once they are properly initialized,
device interface objects (whatever their types may be) will act as
smart pointers to the actual Gadgeteer device proxy objects that
they encapsulate.
At this point, it is perfectly reasonable to wonder why
Gadgeteer uses a concept that requires all sorts of documentation
and explanation. The extra effort is worth it because it allows
Gadgeteer to hide the actual type of the device being used. There is
no need to know that some specific VR system uses a wireless mouse
connected to a PC reading bytes from a PS/2 port that represent
button presses. All that matters is knowing which buttons are
pressed at a given instant. The class
gadget::DigialInterface gives exactly that
information, and it quietly hides the messiness of dealing with the
mouse, its driver, and its communication protocol.
Before using a device interface, some objects must be
declared. Programmers must choose the type that is appropriate for
the type of devices relevant to a given application. All device
interface objects must be initialized in the application object's
override of the method vrj::App::init()
method. Because we are dealing with a templated type
(gadget::DeviceInterface<T>), every type-specific instantiation has the same
interface. Hence, all type-specific device interfaces are
initialized using the
gadget::DeviceInterface<T>::init()
method. This method takes a single string argument naming the proxy
to which the interface will connect. The name can be the name of a
proxy or a proxy alias, both of which are defined in VR Juggler
configuration files. Example names are “VJHead”,
“Wand”, “VJButton0”, and “Accelerate
Button”. Using meaningful symbolic names makes them easier to
remember, and it also contributes to hiding the details about the
physical device. With this system, no one needs to care how
transformation information from the user's head is generated.
Gadgeteer cares, but there is no need for it to tell anyone else.
All developers care about is the head transformation matrix. An
example of initializing a
gadget::PositionInterface that connects with
the user head proxy is:
gadget::PositionInterface head;
head.init("VJHead");Remember that this has to be done in an application object's
init() method. The actual object used would
be a member variable of the application class. Note that here, the
normal syntax for calling the method of a C++ object is used rather
than using the dereference operator. Until it is initialized, the
device interface object cannot act as a smart pointer.
Once device interface objects are all initialized and ready to
use, it is time to start using them as smart pointers. VR Juggler and Gadgeteer are already working hard in
the background to update device proxies, and the application is free
to access them. It is usually best to acquire data from the device
proxy through the device interface in the
preFrame() method, but this may not
necessarily be true for all proxies. Continuing with our example of
a gadget::PositionInterface to the user head
proxy, the following code shows how to read the transformation
matrix for the user's head (in feet):
gmtl::Matrix44f head_mat = head->getData();
Believe it or not, the code really is that easy. Simply use
the overloaded dereference operator to get access to the position
proxy object hidden in
gadget::PositionInterface to read data from
the proxy. We now move on to explain the use of type-specific device
interfaces.
Analog devices return floating-point data. As noted above,
the return type of
gadget::AnalogProxy::getData() is
float. Behind the scenes, analog devices in Gadgeteer
scale their input so that application objects always receive it in
the range 0.0 to 1.0 inclusive (also denoted [0.0,1.0] in
mathematically oriented descriptions). Hence, application objects
can always expect analog data to be in that range regardless of
the specific type of analog device being used.
Command-oriented devices were introduced in Gadgeteer 1.0
Beta 1. They are an evolving device type geared towards complex
input that can be interpreted at an abstract level. In Gadgeteer
1.0, such input comes in the form of spoken phrases that are
reinterpreted as commands identified by unique integer values. In
future versions of Gadgeteer, this device type will be used for
scalable gesture recognition. The return type of
gadget::CommandProxy::getData() is
int, and it is up to the person configuring the
command-oriented device to set up the command-to-integer
mappings.
Digital devices are those that have distinct on and off
states. The method
gadget::DigitalProxy::getData() returns
the current state of such a device as a value of the enumerated
type gadget::Digital::State. This type is defined to
allow for easy on/off testing, but it also provides state toggling
information. The possible values of
gadget::Digital::State are OFF
(integer value 0), ON (integer value 1),
TOGGLE_ON (integer value 2),
TOGGLE_OFF (integer value 3). In Example 3.1, “Using gadget::DigitalInterface in
an Application Object”, we see some
example uses of the information returned by
gadget::DigitalProxy::getData().
Example 3.1. Using gadget::DigitalInterface in
an Application Object
1 void MyApp::preFrame()
{
if ( mButton0->getData() )
{
5 // Set state for when mButton0 is pressed, has been pressed
// since the last frame, or has been released since the
// last frame ...
}
else
10 {
// Set state for when mButton0 is not pressed ...
}
switch (mButton1->getData()
15 {
case gadget::Digital::OFF:
// Set state for when mButton1 is not pressed ...
break;
case gadget::Digital::ON:
20 // Set state for when mButton1 is pressed ...
break;
case gadget::Digital::TOGGLE_ON:
// Set state for when mButton1 has been pressed since
// the last frame ...
25 break;
case gadget::Digital::TOGGLE_OFF:
// Set state for when mButton1 has been released since
// the last frame ...
break;
30 }
if ( mButton2->getData() == gadget::Digital::TOGGLE_ON )
{
// Set state when mButton2 goes "high" (is toggled on) ...
35 }
else if ( mButton2->getData() == gadget::Digital::TOGGLE_OFF )
{
// Set state when mButton2 goes "low" (is toggled off) ...
}
40 }Input read from a keyboard and a mouse is provided through
gadget::KeyboardMouseProxy, instances of which are acquired through
gadget::KeyboardMouseInterface. Unlike most other device proxy types,
gadget::KeyboardMouseProxy does not have a
getData() method. Rather, it has a method
called getEventQueue() with return type
gadget::KeyboardMouse::EventQueue that is the “event queue.” Keyboard
and mouse input is handled as events, either key press events or
mouse events. Key press events come from the keyboard and are for
both the pressing and releasing of individual keys or keys with
modifiers (CTRL, ALT, and
SHIFT). Mouse events include both the motion of
the mouse in the X & Y axes and the pressing and releasing of
mouse buttons which may or may not be associated with a keyboard
modifier.
The event queue contains all the key press and mouse events
that occurred since the last frame. Each event is contained in an
object of type gadget::EventPtr[1]. The type gadget::EventPtr is
a reference-counted smart pointer for instances of
gadget::Event, which is in turn a base class for
gadget::KeyEvent and
gadget::MouseEvent. Each of these has its own reference-counted smart
pointer, namely
gadget::KeyEventPtr and
gadget::MouseEventPtr. This seems like a lot of types to understand, but
it is simple enough to use by keeping in mind that there are only
two types of events: key press events and mouse events.
Furthermore, user code should only be interested in
gadget::KeyEventPtr and
gadget::MouseEventPtr. The specific event
type is determined through the method
gadget::Event::type().
At this point, observant readers will be wondering how to
downcast instances of gadget::EventPtr to
either gadget::KeyEventPtr or
gadget::MouseEventPtr. All three of the
reference-counted smart pointer types make use of Boost shared pointers
(instantiations of the type
boost::shared_ptr<T>), part of the
Boost
smart pointer library. Boost shared pointers have their
own version of the built-in C++ operation
dynamic_cast<T,U>() called
boost::dynamic_pointer_cast<T,U>(). It
works the same way as
dynamic_cast<T,U>(), but it is designed
specifically for Boost shared pointers. In Example 3.2, “Using
gadget::KeyboardMouseInterface in an
Application Object”, we see
how to put all of this together in order to handle keyboard and
mouse input.
Example 3.2. Using
gadget::KeyboardMouseInterface in an
Application Object
1 #include <boost/shared_ptr.hpp>
#include <gadget/Type/KeyboardMouseInterface.h>
#include <gadget/Type/KeyboardMouse/KeyEvent.h>
#include <gadget/Type/KeyboardMouse/MouseEvent.h>
5 #include <vrj/Draw/OGL/GlApp.h>
// This is here to shorten the use of the function in preFrame()
// boost::dynamic_pointer_cast<T,U>() below.
10 using namespace boost;
class MyApp : public vrj::GlApp
{
public:
15 MyApp() : vrj::GlApp()
{
/* Do nothing. */ ;
}
20 virtual ~MyApp()
{
/* Do nothing. */ ;
}
25 void init()
{
mKeyboard.init("VJKeyboard");
}
30 void preFrame()
{
gadget::KeyboardMouse::EventQueue evt_queue =
mKeyboard->getEventQueue();
gadget::KeyboardMouse::EventQueue::iterator i;
35
// Loop over all the keyboard and mouse events that
// occurred since the last frame.
for ( i = evt_queue.begin(); i != evt_queue.end(); ++i )
{
40 const gadget::EventType type = (*i)->type();
if ( type == gadget::KeyPressEvent ||
type == gadget::KeyReleaseEvent )
{
45 gadget::KeyEventPtr key_evt =
dynamic_pointer_cast<gadget::KeyEvent>(*i);
// Handle the key press event ...
}
else if ( type == gadget::MouseButtonPressEvent ||
50 type == gadget::MouseButtonReleaseEvent ||
type == gadget::MouseMoveEvent )
{
gadget::MouseEventPtr mouse_evt =
dynamic_pointer_cast<gadget::MouseEvent>(*i);
55 // Handle the mouse event ...
}
}
}
60 void draw()
{
// Draw something ...
}
65 private:
gadget::KeyboardMouseInterface mKeyboard;
};Position tracking devices return data to application objects
as 4×4 transformation matrices. The return type of
gadget::PositionProxy::getData() is
gmtl::Matrix44f, which was introduced in
the section called “The gmtl::Matrix44f Helper Class”. All tracking devices
return a full transformation matrix even if the physical tracking
hardware is only capable of returning translation or orientation
data.
When querying a positional device for its data, it is
critical to ask for the data in the units that the application
expects. An easy way to do this is to pass the result of
getDrawScaleFactor() to
gadget::PositionProxy::getData(). By
default,
gadget::PositionProxy::getData()
returns data in feet, and the implementation of
vrj::App::getDrawScaleFactor() returns
gadget::PositionUnitConversion::ConvertToFeet.
This is for backwards compatibility with VR Juggler 1.0
behavior. See Example 3.3, “Requesting Positional Data in Application-Specific
Units”
for an example of this. Refer to the section called “vrj::App::getDrawScaleFactor()” for more information
about getDrawScaleFactor().
Example 3.3. Requesting Positional Data in Application-Specific Units
1 #include <gmtl/Matrix.h>
#include <gadget/Type/Position/PositionUnitConversion.h>
#include <gadget/Type/PositionInterface.h>
5
#include <vrj/Draw/OGL/GlApp.h>
class MyApp : public vrj::GlApp
10 {
public:
MyApp() : vrj::GlApp()
{
/* Do nothing. */ ;
15 }
virtual ~MyApp()
{
/* Do nothing. */ ;
20 }
void init()
{
mWand.init("VJWand");
25 }
// Use meters for the application units.
float getDrawScaleFactor()
{
30 return gadget::PositionUnitConversion::ConvertToMeters;
}
void preFrame()
{
35 // Request the current wand transformation matrix in
// application units (meters).
const float units = getDrawScaleFactor();
gmtl::Matrix44f wand_mat(mWand->getData(units));
// Do something with the wand transformation ...
40 }
void draw()
{
// Draw something ...
45 }
private:
gadget::PositionInterface mWand;
};String (text- or word-driven) devices were introduced in
Gadgeteer 1.0 Beta 1. They are an evolving device type geared
towards textual or spoken input. In Gadgeteer 1.0, such input
comes in the form of spoken phrases that are returned to the
application object as strings matching those in a pre-defined
grammar. In future versions of Gadgeteer, this device type may be
used for additional forms of high-level input. The return type of
gadget::StringProxy::getData() is
std::string, and it is up to the person
configuring the string device to set up the recognized
grammar.
The indirection provided by device proxies facilitates run-time reconfiguration of hardware devices. If a hardware device breaks down while an application is running, the device can be replaced without shutting down and restarting the application. To support this capability, proxies can become “stupefied,” which means that they are not connected to a device and cannot return new data.
In general, application programmers do not need to worry about stupefied proxies. Data will be returned by the proxy whether it is stupefied or not, but if the proxy is stupefied, it cannot return new data. Stupefied proxies can occur as a result of an error in the VR Juggler configuration or because the hardware device pointed at by the proxy failed to start up correctly.
To determine whether a proxy is stupefied, the method
gadget::Proxy::isStupefied() can be used.
The stupefied state cannot be changed programatically by user code,
however. Only the Input Manager is capable of reconfiguring a proxy
to point at a new valid device. Hence, changing the stupefication
state of a proxy from an application object will have no effect and
may cause the application to crash.
In all versions of VR Juggler prior to 2.0 Beta 3, the word
“stupefied” was misspelled as
“stupified.” The method
gadget::Proxy::isStupified() is retained
in Gadgeteer 1.0 for backwards compatibility, but it will be
removed in Gadgeteer 1.2 (which will ship as part of VR Juggler
2.2).
What is truly amazing about Gadgeteer device interfaces is, despite their seeming complexity, there is really nothing to them. Trying to trace through the source code is a little tricky, but conceptually, it is all about pointers. Keep in mind that all this documentation was written using nothing more than the Gadgeteer header files as a reference.
As mentioned, the class
gadget::DeviceInterface<T> provides the method interface for all the
type-specific instantiations, including the overloaded dereference
operator. The base class of
gadget::DeviceInterface<T>,
gadget::BaseDeviceInterface, maintains the name of the proxy and the proxy
reference itself, and it provides the all-important
init() method.
The beauty of it all is that the proxy object being pointed to by the device interface can be changed without affecting the execution of the user application. In other words, the proxies can be changed at run time to point to different physical devices. All the while, the user code is still using the smart pointer interface and getting data of some sort. This flexibility is one of the most important features of Gadgeteer, and it is important to understand.
[1] Behind the scenes,
gadget::KeyboardMouse::EventQueue is a
typedef for
std::vector<gadget::EventPtr>.