Cubby VR Part II
Volume Number: 16
Issue Number: 11
Column Tag: QuickDraw 3D Tricks
Cubby: Multiscreen Desktop VR Part II
by By Maarten Gribnau and Tom Djajadiningrat
How to create an Input Sprocket driver for a 3D
input device
Summary
In this second part of our 'Cubby: Multiscreen Desktop VR' trilogy, we will introduce
you to the art of creating a driver to read an Origin Instruments Dynasight input
device. With the Dynasight, the position of the head of the user is established so that
Cubby can display the correct images on its screens. The driver is created with
InputSprocket, which is part of Apple's Game Sprockets API.
In our previous articles about Desktop VR (Djajadiningrat & Gribnau, 1998; Gribnau
& Djajadiningrat, 1998), we used the Pointing Device Manager (PDM) of QuickDraw
3D to serve our input needs. We promoted the PDM because it was intended to support
3D input devices. The PDM and InputSprocket have in common that they both separate
the device dependent code from the application. They relieve application programmers
from dealing with devices directly and provide an abstraction layer for input
programming. The difference between the two is that InputSprocket is actually
supported by device manufacturers. There are no QuickDraw 3D drivers available to
our knowledge. For InputSprocket on the other hand, lots of drivers are available. In
addition, InputSprocket has configuration management built in. A standard interface is
provided for connecting devices to applications. For these reasons InputSprocket
provides a better solution for Cubby's input needs than the PDM.
In this episode we cover programming with InputSprocket to create a device driver. In
the next episode the application side will be covered. Together, the two articles cover
the whole of InputSprocket. The driver we describe is for the Dynasight device. If you
do not own one or plan to buy one, you might still be interested because the techniques
explained here can be used for other input devices as well. Documentation on writing
InputSprocket drivers is scarce, so you might pick up some facts here. To compile our
code, you will probably need to download the latest version of InputSprocket from the
Apple web site (see References section), which is currently version 1.7.3.
If you are familiar with programming drivers for InputSprocket, you can safely skip
the next section and proceed with the following section about creating an InputSprocket
driver for the Dynasight. If InputSprocket drivers are new to you, you can read the
next section and be introduced to the basic concepts. If you have worked with
InputSprocket before but not with drivers, you can also read the next section to learn
how drivers and applications communicate.
Introduction to InputSprocket
The goal of InputSprocket is to make life easier for game programmers who deal with
input devices. There are an immense number of devices on the market with different
features. Trying to have a game support all these devices optimally can lead to severe
headaches. Most games support input devices through emulation of the keyboard and
mouse. But emulation does not suffice when supporting complex devices that have
directional pads, levers, wheels, etc. For one, the game cannot take advantage of the
extra input controls of complex devices and secondly, configuring the devices and
connecting them to game controls is device specific. That means that game
programmers should provide configuration capabilities for each device that they need
to support.
This is where InputSprocket comes in. It solves these problems through an input
device architecture that allows games developers to create games that can use a wide
variety of input devices. Input device developers can use InputSprocket to build device
drivers that provide a description of input device controls that the game can use to
automatically configure its control options. The device driver can also provide a user
interface that allows the user to change default control options. Therefore, game
programming is made easier because a lot of device dependent code is moved from the
application to the device driver.
The communication between InputSprocket drivers and games is based on elements. The
element is a building block used to describe the capabilities of a device. Each control of
a device is described with an element, so every device can be described with a set of
elements. For example, a one-button mouse can be described with an element for the
x-axis, an element for the y-axis and a button element. More complex devices may
require more elements but are handled in the same way. An element is described by the
following three pieces of data.
A human readable identifier is a string that the game can display to identify the
element for the user during configuration. Examples of such identifiers are "trigger",
"roll" or "move forward".
The element kind is a four-character sequence that indicates the type of data the
element produces. For example, button elements will typically produce two-state
values while axis elements produce continuous data. InputSprocket currently provides
five basic element kinds:
• Button elements produce two-state data.
• Directional pad elements are nine state elements with an idle position and
eight states corresponding to the eight directions on a typical directional game
pad.
• Axis elements produce continuous data, either with or without a
meaningful center. A symmetrical axis has a meaningful center, like the axis
of a joystick. Axis elements such as a gas pedal or a brake do not have a
meaningful center.
• Delta elements are like axis elements but instead of an absolute position
or orientation, they produce Delta data, which indicates the distance moved
relative to the previous position (e.g. mouse axes).
• Movement elements produce movement data that is given both as x-y axis
data and directional pad data, allowing the game to use whichever is suitable.
Note that in general you should use axis data instead.
There is a sixth element kind, the virtual element, which we will encounter later.
The label of an element gives the suggested use for an element. For example, a button
element may be intended as the start button or the firing button. During configuration,
a game uses labels to find the elements it requires for play.
As was mentioned, elements are the way applications communicate with drivers. There
are two interfaces to pass data from drivers to applications using elements: the
low-level and the high-level interface. In principle, driver programmers can decide
which interface to implement. It is tempting to implement the low-level interface
only, since it is the easiest to implement. However, drivers should provide both the
low and the high-level interface, so that game application programmers can decide
which interface they want to use.
Low-level Interface
The low-level interface is the simplest to implement when building drivers. Figure 1
shows a diagram of both the low and the high-level interfaces. The lower parts of the
Dynasight Driver and the InputSprocket Extension represent the low-level interface.
Cubby uses only the high-level interface and therefore connects the cameras to the
high-level axis elements (Head X, Head Y and Head Z) exclusively. When the driver is
loaded, it creates elements for every control it has. The driver is responsible for
reading the data from the device and, as soon as it is loaded, should update its elements
every time the device reports new data. Applications can connect via InputSprocket
directly to the elements of the device and update the game state accordingly.
Figure 1. Diagram of the low- and high-level interfaces.
High-level Interface
In Figure 1, the upper parts of the Dynasight Driver and the InputSprocket Extension
represent the high-level interface. The high-level interface is somewhat more
complicated than the low-level interface but it has all the advantages that
InputSprocket was meant to offer. Instead of connecting to the device's elements
directly, games read data from the devices through virtual elements. These virtual
elements are created by InputSprocket from the needs that the game has. Drivers are
responsible for the mapping of needs to virtual elements. The high-level interface is
only valid between the driver's Init and Stop callbacks that InputSprocket calls. Only
the driver knows exactly how it has been configured, and when it is active, it is
responsible for pushing data to those virtual elements for each need for which it is
configured.
Configuration
The majority of driver callbacks are related to the Configure dialog. These function
calls will only happen while the high-level is valid (i.e. ISpInit has been called
without a subsequent ISpStop). When the application calls ISpConfigure, a dialog is
presented with a scrolling list of devices with one device selected. Figure 3 shows this
dialog with our Dynasight device selected. The popup menus show the current
connection of the Dynasight x- y- and z-axis to a game's roll, pitch and yaw controls.
The selected device is responsible for the primary pane of the dialog, handling all
events and drawing related to that area. The GetSize function is called to determine the
preferred and minimum sizes of the pane area used by the device. If the pane area will
not fit on any available display device, then that device is removed from the high-level
list. The BeginConfiguration function is then called for each device. The configure
dialog is resized and shown. The GetIcon function is called for each device to determine
the icon to be shown in the scrolling list. Then the Show function is called for the
selected device. This is the time to call AppendDITL for the resources to be displayed in
the primary pane. All events returned in the dialog filter are passed to the HandleEvent
function to give the device a chance to handle them. If update events are not handled
(the recommended option to avoid extra flicker), InputSprocket will make the device
Draw function call from within a BeginUpdate/EndUpdate pair. Unhandled mouse clicks
will be passed to the Click function. If the device called AppendDITL to add items to the
dialog and those items are returned by InputSprocket's ModalDialog call, then it will
call the DialogItemHit function.
The device should maintain a 'dirty' variable that is set whenever any configuration
information is changed, and returned and cleared when the Dirty call is made. When a
different device is selected in the list, the old device receives a Hide function call and
the new device a Show function call. When the dialog is closed, every device receives an
EndConfiguration call.
Figure 2. InputSprockets's configuration dialog box with the Dynasight device
selected.
Procedure
Figure 3 illustrates the sequence of events when a game is using InputSprocket. The
initiating calls are shown on the left and the driver's responses are found on the right.
There are four major activities. Low-level initialization is entered when an
application calls ISpStartUp. InputSprocket will then load the driver and call its
major entry point. In response, the driver should look for devices present on the
system and create InputSprocket devices for each active device. If a device was created,
InputSprocket will call the driver's Meta handler to find out where the driver's entry
points are. If no InputSprocket device was created, the driver is unloaded. High-level
initialization is entered when the application calls ISpInit. InputSprocket passes the
needs and virtual elements to the driver. In response, the driver stores them and does
an auto-configuration. This means that the driver will try to find an optimal match
between the needs of the application and the elements the device has. When the
high-level interface is valid, the application can call ISpConfigure. This starts the
configuration activities explained in the previous section.
There are several guidelines that should be followed during auto-configuration. If a
previous device fulfilled a need and the kISpNeedFlag_NoMultiConfig bit is set in the
need structure for that need, the device should not attempt to auto-configure to the
need. The driver should indicate that it is fulfilling a need so that devices queried later
know that the need is taken. More guidelines can be found in Apple's documentation of
InputSprocket (see References section). After auto-configuring, the device should
immediately push initial values to the corresponding virtual elements and from that
point push data to those virtual elements whenever data is pushed to the elements to
which they are configured.
When the application calls ISpStop, the validity of the high-level interface ends. The
drivers should stop pushing data to the virtual elements and dispose the needs and the
virtual elements. Calling ISpShutdown within the application causes InputSprocket to
call the driver's termination routine. In response, the driver should stop pushing
low-level data and dispose the InputSprocket device.
Figure 3. The sequence of events in communication between driver and game.
Creating an InputSprocket Driver for the Dynasight
Now that we have a basic understanding of how InputSprocket works, we will dive into
the coding of a driver and illustrate how we can put this knowledge to use. We take the
Dynasight device as example and start with the real driver functionality: the
initialization and transfer of data. The configuration of the driver will be covered in
the next section. The Dynasight device is an infrared tracking device that is used in
Cubby to track the head position of users. The 3D position data is made available on the
serial port. In the following discussion, we will not go into the details of reading the
position data from the serial port. If you are interested in that part of the driver, you
can read our previous article about Desktop VR (Gribnau & Djajadiningrat, 1998)
and/or you can have a look at the code accompanying this article.
Before we begin, we should make some general remarks about developing
InputSprocket drivers. An InputSprocket driver is a shared library with a specific
file type and creator ('shlb' and 'insp'). All drivers must be located in the same folder
as the InputSprocket extension, which must be the Extensions folder. Being a shared
library, it takes some extra effort to debug a driver. First of all, you should set the