Overview
This is the summary of the Joystick Conference held at Origin Systems on June 8th and 9th. I try to summarize the topics discussed and propose a plan of action. It became clear at the meeting that the basic plan would be to modify the proposed GameSDK specification to solve a few basic problems. I have modified a few points that were resolved in the meeting after consultation with the GameSDK engineers.
For the purposes of clarity for those not at the meeting, I first cover the basic concepts that were established during the first day of the meeting.
Following the overview, I discuss the proposed changes to the GameSDK and demonstrate sample code. The specific data structure and function names of the implementation are not proposed as final, but are expected to be used a guidelines for the changes. After discussion with Microsoft, it has become clear that it is too late to change the existing GameSDK joystick specification as it has already been included as part of the Win ‘95 final shipment. Thus, these extensions are proposed as additional enhancements for version 2.0. The specifics of how version 2.0 will interact with version 1.0 will have to be left to Microsoft.
Finally, I summarize various agreed upon data such as the family and parametric data. Also, I present a prototype Control Panel Applet.
Conference Attendees
|
Name |
Company |
Title |
|
|
Zachary Simpson (host) |
Origin Systems / Electronic Arts |
Director of Technology |
zsimpson@origin.ea.com |
|
John Landry |
Compaq |
Senior Member/ Technical Staff |
landry@bangate.compaq.com |
|
Steve McGowan |
Forte |
Director of Engineering |
SBM@fortech.com |
|
Joe Rayhawk |
Thrust Master |
Project Engineer |
joe@thrustmaster.com |
|
Rick Salvador |
CH Products |
Technical Representative |
chproducts@aol.com |
|
Barbara Gutierrez |
Logitech |
OEM Account Development Manager |
barbara_gutierrez@ logitech.com |
|
Ken Hall |
Colorado Spectrum |
President |
73233.467@compuserve.com |
|
Beth Marcus |
Exos |
Chief Technical Officer |
exos@exos.com |
|
Kevin English |
Tanisys |
Software Design Engineer |
kevine@tanisys.com |
|
Jennifer Nyland |
Tanisys |
Senior Design Engineer |
jennifer@tanisys.com |
|
Alex St John |
Microsoft |
Games Evangelist |
alexjo@microsoft.com |
|
Reno Suffi |
Suncom Technologies |
Electrical Engineer |
reno@xnet.com |
|
Bernard Geaghan |
MicroTouch |
Vice President, Research and Development |
bgeaghan@mts.mhs. compuserve.com |
|
Louie McCrady |
Dynamix / Sierra |
Software Engineer |
louiem@dynmx.com |
|
Rick Overman |
Dynamix / Sierra |
Software Engineer |
ricko@dynmx.com |
|
Mark Klonower |
Crystal Semiconductor Corporation |
Marketing |
mklonow@crystal.cirrus. com |
|
Paul DiLascia |
Microsoft Systems Journal |
Contributing Editor |
72400,2702@compuserve.com |
|
John Smith |
Advanced Gravis |
UltraSound Project Manager |
john.smith@gravis.com |
|
Ron Haidenger |
Advanced Gravis |
Director Product Managemant |
75300,773@compuserve.com |
|
Tim O’Neal |
Sempro |
Product Development Director |
|
|
Alan Kleymeyer |
Mind Path Technologies |
alankley@aol.com |
|
|
Charles Ader |
Micro Speed |
Senior Member Technical Staff |
cader@netcom.com |
|
David Petchey |
Mindscape |
Staff Scientist |
dpetchey@mindscape. com |
|
Michael Rakieten |
Dell |
Product Marketing Manager Multimedia & I/O Devices |
michael_rakieten@ccmail.us.dell.com |
|
Vernon Weiss |
Dell |
Dimension Product Marketing Manager |
71333,3400@compuserve.com |
|
Gray Chenault |
Dell |
Multimedia Engineer |
gary_chenault@ccmail.us.dell.com |
|
Joseph Pinzarrone |
First Person Gaming |
Vice President of Engineering |
joseph@fpgaming.com |
Basic Concepts
Channels
All input devices can be thought of as consisting of a certain number of discreet channels of information. A channel is one atomic degree of freedom on the device. For example, a button is a channel, a horizontal axis is a channel, etc.
It is sometimes desirable from the point of view of an application to view a logical channel as a conglomerate or disassembly of the physical channels on a device. For example, the top hats on many flight sticks are implemented as a set of 4 discreet switches while on other flight sticks the top hat is implemented as two full axes (a sort of joystick on a joystick). From the point of view of the application, these implementations are the same; it is just that the discreet version has a much more limited resolution. Thus, a top hat would have two logical channels – a horizontal and vertical. The fact that the physical device is implemented as four separate switches or as a single voltage divider is irrelevant.
Mapping
As just discussed, joysticks can be thought of as having many independent channels. Unfortunately, as new joysticks are created there is no intuitive standard which specifies which physical channels should be mapped to which logical channels. There is, however, one exception – "generic" joysticks. The "generic" sticks conform to the old 1980 IBM standard of 2 axes and two buttons. However, even for generic joysticks there still exists a problem in that it is unclear which button is primary and which is secondary. The unspoken standard for "generic" joysticks is that button 0 is primary and button 1 is secondary. The point is, as more buttons and axis are created, the problem of mapping will become increasingly difficult.

The above diagram demonstrates the basic layers of mapping. The physical map is the data that is pulled off of the device. With the exception of generic joysticks, this mapping will be completely arbitrary and specific to the device.
The logical map is the channel structure as revealed by the driver to the application. The order of this map is not arbitrary since the application must be able to determine the usage of these channels.
The final layer is the actual application map which is directly associated with the behaviors of the application.
One of the keys to a successful joystick driver model will be to designate how the logical map will be revealed to the application. There are three ways that the logical map is exposed: vendor mapping, family mapping, and parametric mapping. These are not mutually exclusive.
Vendor Mapping
One way for the application to determine the logical-to-application mapping is by storing a unique map for each kind of joystick. The obvious disadvantage of this approach is that it is not guaranteed that the user will have one of the supported devices. On the other hand, this type of mapping does have the benefit of ensuring that for at least some very popular joystick models, all of the proprietary features can be utilized.
Family Mapping
Another approach to logical mapping would be to create standards for common varieties of devices. For example, these families might fall into categories like: "flight controller", "steering wheel", etc. Families should define:
A device can support more than one family. For example, a "flight stick" could certainly be used as a "generic" family also. Each of the supported families of the joystick is a mode, and a joystick can not be in more than one mode at a time per application.
Parametric Mapping
Yet another way to determine the logical mapping is by defining a set of parameters which describe the channels. These descriptions would take on two basic forms: physical parameters and usage parameters.
Physical parameters would describe things such as: "two state button", "horizontal axis", "spring loaded", "on the floor", "hand held", etc. Usage parameters would describe suggested usage. For example, "vertical POV", "rudders", "brakes", "chaff", etc.
Using these parameters, an application can make an attempt to intelligently determine the logical-to-application mapping. Using all three of the approaches just mentioned (family, vendor, and parametric) will enable applications to be very user-friendly about the usage of known and unknown devices.
Because game developers have been "talking" to the joystick ports directly for the last 15 years, there has been no ability to undo the constraints of the original IBM joystick port. (Some very clever hacks have been devised however). Clearly, the advantage of creating a new driver model is to empower digital joysticks by which I mean any joystick which is not bound to the clunky analog-polled input model of old. There already exists a demand for such devices, for example, the serial donggles used for laptops. In addition, USB and other new bus standards are emerging which would seem to be well suited to joysticks.
Several optimizations can be made independent of how joysticks evolve over the next few years to a defined API can enable. Two in particular are worthy of inspection: simultaneous device polling and channel deactivation.
Channel Deactivation
Because it takes time to poll and transfer data from a device (especially with analog polled devices), it would be nice if the application could deactivate unused channels. This would enable "smart devices" to speed up polling as well as transfer times and also decrease power consumption.
Simultaneous Polling
It is often the case that an application wants to poll more than one device at the same time. Thus, it would be nice if the driver could minimize the number of transitions to ring zero. This could be implemented in several ways, but the basic idea is that some kind of data structure internal to the driver is maintained that instructs the VxD to poll more than one device during a call to poll the device.
Proposed Changes to the GameSDK v 1.0
The first draft of the GameSDK Joystick interface defines an extension to the original Windows joystick model. Because very few developers actually ever used this old joystick model due to its inefficiency, there is little incentive to remain backward (or near-backward) compatible. Instead, we should insure that we design a system which will have an extended lifetime.
The old Windows driver model was based on the limitations of the original IBM joystick port. It defines two usable axes and two buttons per stick. The new GameSDK is quite in improvement in that it now defines six axis and many more buttons. However, the mapping problem as described above is still not addressed.
The currently proposed GameSDK specification will be shipping with Win ‘95; this preclude dramatic modification due to the shipping timeline of Win ‘95. However, we can build a more general purpose model and slot it in as a pseudo-replacement for the GameSDK model. The exact way in which this will implemented is left up to Microsoft.
I propose to make several key changes to the GameSDK:
During the conference, much debate centered around whether there should exist a transport independent layer in the driver model. I for one, was swayed both directions during the discussion. We concluded at the time that a transport independent layer should be built into the DDI model. After consideration with Microsoft, I recommend that we do not include this layer. I believe that the complications that will be created by this layer outweigh the benefits.
Homogenious Channels
I propose that we move away from specialized data structures that try to encapsulate the functionality of axis and buttons, and replace it with the concept of a homogeneous channel buffer. In other words, we create all channels equal; buttons and axis will not be differentiated. This fundamental change will increase the flexibility of the system and also make application coding easier by allowing developers to use highly efficient and convenient lookup tables to perform the logical-to-application mapping.
Thus, today we have the GameSDK proposed
JoyGetPosEx(JOYINFOEX *) function which takes the following structure:
struct JOYINFOEX {
DWORD dwSize;
DWORD dwFlags;
DWORD dwXPos;
DWORD dwYPos;
DWORD dwZPos;
DWORD dwRPos;
DWORD dwUPos;
DWORD dwVPos;
DWORD dwButtons;
DWORD dwButtonNumber;
DWORD dwPOV;
DWORD dwReserved1;
DWORD dwReserved2;
};
We would replace this structure with simply and array of:
DWORD channelBuffer [numberOfChannels + 1]
The
+1 is reserved for a DWORD dwSize at the beginning of the structure that is required to determine the length.
Logical Mapping Modes
As soon as we eliminate the structure listed above and replace it with a generic channel buffer, we create the need for a well defined logical mapping. As described above, there are three basic ways to resolve the logical map: vendor, family, and parametric. I suggest that we incorporate all of this data into this system to allow the application designer maximum flexibility. I believe that this can be done in a surprisingly simple data structure as demonstrated below.
Like the channel buffer described above, the existing GameSDK capability data is compacted into one structure called
JOYCAPS. I reorganize this data into more than one structure to allow for the enumeration of the differing modes and channels.
It is important to remember that a device will support more than mode. In fact, it may support many many modes. Modes are cheap from the driver point of view because all they require is data that tells the driver how to convert its physical bits into logical channels. Because similar information is utilized for every mode in a given device, this physical to logical mapping can be directed though a lookup table which is initialized from data in the registry. This lookup table approach avoids large amounts of unused code in the driver space an will keep overhead to a minimum.
Every device will probably want to support some of the following kinds of modes:
Family Modes
A joystick manufacturer will choose to support a family if they believe that their device naturally supports such a family. There will be considerable over-lap among families. For example, a joystick that is built as a "flight stick" will clearly work as a "generic" also. In fact, almost every device will support "generic".
Native Modes
Any manufacturer with new features such as more axes or buttons will certainly want to create a native mode where every button and every axis is included in the logical map. These modes will be utilized by the application engineers in one of two ways; they will either recognize the manufacturer and product ID and use an pre-determined mapping (if the joystick company’s marketing is doing a good job!), or they will evaluate the channel parameters one by one and create an application mapping parametricly.
Compatibilty Modes
The globally unique identifiers used to recognize a particular device are not copyrightable. Thus, it is certainly fair game to write a driver which emulates the native mapping of another device. In this case, the driver should not "lie" about its real manufacturer in the manufacturer ID fields revealed in the capability structures. Instead, the device capability should report the "truth" about the manufacturer and the mode IDs should tell the "lie". This way, customer service representatives from all sides will be able to determine the true configuration of the machine.
Optimizations
The two optimizations described above in the Basic Concepts, will be implemented through two special calls. The simultaneously polling is accomplished by associating more than one device and channel buffer with a primary device which is then used to poll the chain of devices. Channel deactivation is done by allowing the application to set the channel state for each channel after the logical-to-application map has been derived and the usage is known. Note, it is assumed that for all family modes the driver will disable unused physical channels automatically.
Prototype Data Structures
CHANNELBUFFER is a replacement for JOYINFOEX.
typedef struct tagCHANNELBUFFER {
DWORD size;
DWORD channelData [1];
// ... actually 1 .. n channel DWORDs here ...
} CHANNELBUFFER;
JOYCAPS is a subset of the JOYCAPS structure defined in the GameSDK ver 1.0. The primary difference is that the channel information has been moved out. See
JOYCHANNELCAPS below.
typedef struct tagJOYCAPS {
DWORD dwSize;
WORD wIsPrimary;
GUID guidManufactuerID;
GUID guidProductID;
CHAR szManufactersName [MAXNAMELEN];
CHAR szProductName [MAXNAMELEN];
CHAR szRegistrationKey [MAXNAMELEN];
CHAR szOEMVxD [MAXOEMVXDLEN];
} JOYCAPS;
JOYMODECAPS is an extension of the GameSDKs JOYCAPS function. The reason for the distinction is to facilitate the logical mapping as described above. This structure is filled out by the driver for each mode that is supported during the call to
JoyEnumModes.
typedef struct tagJOYMODECAPS {
DWORD dwSize;
WORD wFlags;
GUID guidManufactuerID;
GUID guidProductID;
CHAR szManufactersName [MAXNAMELEN];
CHAR szProductName [MAXNAMELEN];
GUID guidFamily;
WORD wNumChannels;
DWORD reservedForDriverModeIDStorge; // This gets set as a mode id by JoyEnumModes
DWORD reserved;
} JOYMODECAPS;
Note that this structure has a few duplicated fields compared to JOYCAPS. The important distinction is that these fields are used to report supported logical modes, not the actual device. Thus, a joystick manufactured by one company can emulate the logical mapping provided by another company. For example, suppose WackyCorp. decides to emulate BigCorp’s joystick. They would fill in the
JOYCAPS structure as usual, but they would enumerate an extra mode for this compatibility. In this enumeration, they would fill the structure as follows:
joymodecaps.szManufactuerName = "Wacky Corp";
joymodecaps.szProductName = "BigCorp(tm) UltraPro(tm) compatibility mode.";
guidProductID and guidManufactuerID listed in the structure with the ones listed in the JOYCAPS structure.
JOYCAPS and the JOYMODECAPS, but the manufacturer should still list "compatibility mode" in the descriptive product string.
JOYCHANNELCAPS
is also a subset ofJOYCAPS in the GameSDK. This structure is now enumerated on a per channel basis instead of being consolidated into one structure. This allows for homogeneous channels as described earlier.
typedef struct tagJOYCHANNELCAPS {
DWORD dwSize;
DWORD flags;
// bit 0: 1 = active / 0 = disabled
// bit 1-2: 0 = read only, 1 = write only, 2 = read / write
DWORD dwMin;
DWORD dwMax;
DWORD dwCenter;
DWORD dwResolution;
// Resolution is the number of discrete values that can be represented
CHAR szRegisterKey [MAXNAMELEN];
// Contains the key name of the parameteric info.
};
Prototype API
I do not know exactly how these functions will fit into the current GameSDK specification. I suspect that they will be added above or in parallel to the existing specification. Thus, all of these function names are purely descriptive; they are not intended to conflict with or replace any existing functions. Also, no error detection or other house cleaning elements like close are described here. Treat this as purely a descriptive prototype.
int JoyEnum (JOYCAPS *joyCaps, int done)
During each call this function fills out the capability structure for the next device. The done flag is initialized to zero, and when it has completed enumerating all of the devices, it returns zero. This allows for very simple coding as demonstrated in
initJoysticks in the sample code below.
In some cases, joystick manufacturers may choose to integrate more than logical device into the same package or connector. For example, a flight stick might also include a throttle. The family designations a designed to be simple, so there exists a flight stick family and a throttle family. Thus, the device driver for this integrated device would report itself as two different devices in the
JoyEnum function despite the fact that it is really one device.
HANDLE JoyOpenDevice (int joystickNumber, JOYCAPS *joyCaps)
This opens the device specified by the ordinal of the device from the
JoyEnum function or the application can pass in PREFERRED_JOYSTICK to allow the driver to choose (this is usually based on the control panel settings). The joyCaps argument must be passed in from the previous JoyEnum because it contains the ID used by the driver to correlate the appropriate mini-driver. (If this is in fact unnecessary from a DDI implementation, the last argument would be eliminated.)
It returns the handle to the device used in many subsequent calls.
int JoyModesEnum (HANDLE deviceHandle, JOYMODECAPS *joyModeCaps, int done)
Like
JoyEnum, this enumerates the capabilities into the caps structure one at a time. In this case, it enumerates the logical mapping modes supported by the device. The joystick manufacturer will typically enumerate the following kinds of modes:
UNKNOWN_FAMILY
UNKNOWN_FAMILY
void JoySetMode (HANDLE deviceHandle, JOYMODECAPS *joyModeCaps)
This function puts the driver into the mode selected by the
joyModeCaps which was derived from the JoyModesEnum call. Once this has been done, all calls to JoyReadChannels will return the channels as mapped by the definition of the selected mode.
int JoyChannelEnum (
HANDLE deviceHandle,
JOYCHANNELCAPS *joyChannelCaps,
int done
)
Like
JoyEnum and JoyModesEnum, this function enumerates the capabilities into the caps argument one at a time. With this function, the channels of the currently selected mode are enumerated. This gives basic channel descriptions such as min, max, and center, but more importantly, it gives a registry key which can be used to derive parametric information.
void JoyReadChannels (HANDLE deviceHandle, CHANNELBUFFER *channelBuffer)
This is the function that actually retrieves the position and state information for the channels. All the channels are a DWORD in size and their order is dependent on the selected mode.
void JoyWriteChannels (HANDLE deviceHandle, CHANNELBUFFER *channelBuffer)
This function writes the channel data back to the device if the device supports this. This is not demonstrated in the sample code.
void JoySetChannelState (
HANDLE deviceHandle,
int channelNum,
JOYCHANNELCAPS *caps
)
This function will set the state of the enable / disable bit. I chose to pass in the entire caps structure here in case any other data might be useful to set, but this function is primarily intended to disable unused channels. The advantage of using this is that intelligent devices may use this information to turn-off certain parts of the polling on the joystick to save polling time, transfer time, and power. Failing to use this function will not cause harm, it will just potentially result in a less efficient poll. It is assumed that all modes set the unused physical channels automatically. That is, if
JoySetMode is called with FLIGHT_STICK_FAMILY, it is assumed that the device driver is smart enough to turn off all logic on the device unused by the "flight stick" family.
void JoyAddSimultanousDevicePoll (
HANDLE primaryDevice,
HANDLE associatedDevice,
CHANNELBUFFER *associatedChannelBuffer
)
This function is used to avoid ring transitions. It instructs the driver that the associated handle and buffer should be polled at the same time that the primary device is. The basic idea is that if the application is going to poll more than one device then this will usually be done at the same time. By using this function to chain the devices together, the
JoyReadChannels call can get all the work done during one ring transition.
These associations are maintained Internal to the device driver presumably in a linked list. I do not define how this will take place.
This function is not demonstrated in the sample code.
Application Sample Code
This piece of sample code demonstrates both how to initialize the logical mapping and how to poll the joystick. For this example, we imagine that the game has four basic commands:
CHANGE_DIRECTION, ACCELERATE, SHOOT_GUNS, and SHOOT_MISSLES.
This code demonstrates the simplicity of homogeneous channels by the use of a lookup table called
channelMap. This map is used to pull the appropriate commands out of the channel buffer and into the command variables used by the application.
Note that the initialization code here is deliberately over complicated to demonstrate all possible ways to derive the logical mapping mode. In a real application, the programmer would probably stick to either a simple family and vendor map. The parametric information would, most likely, be used only by a very complex application such as one that required or supported sophisticated VR hardware.
//------------------------------------------------------------------
// Application Channels
//------------------------------------------------------------------
#define CHANGE_DIRECTION 0
#define ACCELERATE 1
#define SHOOT_GUNS 2
#define SHOOT_MISSLES 3
#define NUMBER_OF_APPLICATION_CHANNELS 4
//------------------------------------------------------------------
// Global Variables
//------------------------------------------------------------------
HANDLE joystickDeviceHandle = NULL;
int numChannels;
char channelLookupTable [NUMBER_OF_APPLICATION_CHANNELS];
//------------------------------------------------------------------
// initJoysticks
//------------------------------------------------------------------
void initJoysitcks() {
// Count all of the joystick devices that are connected by
// enumerating them into JOYCAPS holders
// In a real application, you might want to use all of them,
// but in this demonstration we will only use the designated primary.
JOYCAPS joyCaps;
int done = 0;
for (
int numDevices=0;
done = JoyEnum (&joyCaps, done);
numDevices++
);
if (numDevices > 1) {
// This is a placeholder for a dialog box...
print ("You have more than one physical joystick connected to your comupter "
"Your current selection is the %s\n"
"Press \"Select\" to open the control panel.",
joyDevices[JoyGetPreferredJoystick()].
);
// Open the joysitck control panel applet if they ask to...
}
// Set the global device handle. This also resets the joyCaps variable
joystickDeviceHandle = JoyOpenDevice (PREFERED_JOYSTICK, &joyCaps);
//------------------------------------------------------------------
// The initialization code uses the following algorithm
// to deterimne the logical mapping.
// 1. Enumerate the modes.
// 2. Choose the appropraite mode based on the following preferences:
// 1. First preference: Vendor/Product is Wacky Ultra Pro™ or compatible
// 2. Second preference: Flight Stick Family
// 3. Thrid prefernce: A good parametric fit
// 4. Fourth preference: Generic Family
// 3. If none of these are avilable, resort to a parametric approach
// 1. Enumerate the channels
// 2. Find a appropriate horizontal, vertical axises as well as two buttons.
// 4. Open the device in the appropraite mode.
//
// Now, enumerate the modes to determine the appropraite logical map
// Use a simple "best match" search algorithm. Keep the generic
// mode as a backup in case we can’t find any other preferable mode.
JOYMODECAPS tmpModeCaps, bestModeCaps, genericModeCaps;
done = 0;
int best = 0;
while (done = JoyModesEnum (device, &tmpModeCaps, done)) {
if (
tmpModeCaps.guidFamily == UNKNOWN_FAMILY &&
tmpModeCaps.guidManufactuerID == WACKY_CORP &&
tmpModeCaps.guidProductID == WACKY_JOYSTICK_ULTRA_PRO_STICK_EXTREME
) {
memcpy (&bestModeCaps, &tmpModeCaps, sizeof(JOYMODECAPS));
best = 2;
}
else if (tmpModeCaps.guidFamily == FLIGHT_STICK_FAMILY && best < 2) {
memcpy (&bestModeCaps, &tmpModeCaps, sizeof(JOYMODECAPS));
best = 1;
}
else if (
tmpModeCaps.guidFamily == UNKNOWN_FAMILY &&
tmpModeCaps.guidManufactuerID == joyCaps.guidManufactuerID &&
tmpModeCaps.guidProductID == joyCaps.guidProductID &&
best < 1
) {
// This is the native mode.
memcpy (&bestModeCaps, &tmpModeCaps, sizeof(JOYMODECAPS));
}
else if (tmpModeCaps.guidFamily == GENERIC_FAMILY) {
// Always keep the generic mode around in case all else fails.
memcpy (&genericModeCaps, &tmpModeCaps, sizeof(JOYMODECAPS));
}
}
// Now set the mode. This causes the joystick driver to return values
// as appropraite for the established mode during calls to JoyReadChannels
JoySetMode (joystickDeviceHandle, &bestModeCaps);
// Set the global numChannels variable
numChannels = bestModeCaps.numChannels;
//------------------------------------------------------------------
// Now set up our application conversion lookup table depending on
// the mode. If we haven’t found a family that we have a pre built
// lookup table for, then we will have to resort to a parametric approach.
// These mappings are all arbitrary examples
static char wackyUltraProChannelMapping[CHANNEL_MAP_SIZE] = { 3, 2, 1, 0 };
static char flightStickChannelMapping[CHANNEL_MAP_SIZE] = { 1, 2, 3, 0 };
static char genericFamilyChannelMapping[CHANNEL_MAP_SIZE] = { 0, 2, 1, 3 };
if (best == 2) {
// Load the WACKY_JOYSTICK_ULTRA_PRO_STICK_EXTREME_TM
// mapping into the lookup table
memcpy (
channelLookupTable,
wackyUltraProChannelMapping,
NUMBER_OF_APPLICATION_CHANNELS
);
solutionFound = 1;
}
else if (best == 1) {
// Load the flight-stick family mapping into the lookup table
memcpy (
channelLookupTable,
flightStickChannelMapping,
NUMBER_OF_APPLICATION_CHANNELS
);
solutionFound = 1;
}
//------------------------------------------------------------------
// Now life starts getting tricky, we haven’t found any of our
// prefered mapping modes so we must resort to the
// channel parameters of the native mode. If we can’t get a good
// fit here, then we will resort to the generic family.
// Note that some application designers may choose to skip this step
if (best == 0) {
// Enumerate all of the channels
JOYCHANNELCAPS *channels = new JOYCHANNELCAPS [numChannels];
done = 0;
int i = 0;
for (
int i=0;
done = JoyChannelEnum (joystickDeviceHandle, &channels[i], done);
i++
);
for (i=0; i<bestModeCaps.numChannels; i++) {
// THE FOLLOWIUNG IS ALL PSUEDO-CODE
// lookup in the registry using the channels[i].registryKey
// and find the parametric info. Set up each channel
// if you find an appropraite channel description
for (i = enumerate the channel parameters) {
if its a primary button then
channelLookupTable[FIRE_BUTTON] = i;
if its a secondary button then
channelLookupTable[MISSLE_BUTTON] = i;
if its a primary horizontal axis then
channelLookupTable[X_AXIS] = i;
if its a primary vertical axis then
channelLookupTable[Y_AXIS] = i;
}
channels[i].flags = channel actually used ? enabled : disabled;
solutionFound = 1;
}
// Now tell the driver which channels are actually active.
// This is an optimization that can make it so that any intelligent
// device can shut off the polling of extraneous channels
if (solutionFound) {
for (i = 0; i < numChannels; i++) {
JoySetChannelState (joystcikDeviceHandle, i, &channels[i]);
}
}
else {
// If we can’t find any other solution than we will resort to generic
memcpy (
channelLookupTable,
genericFamilyChannelMapping,
NUMBER_OF_APPLICATION_CHANNELS
);
}
}
}
//--------------------------------------------------------------------
// Main Funtion
// This demonstrates the intialization and polling, nothing else.
//--------------------------------------------------------------------
void main() {
initJoystick();
// The previous function set the joystickDeviceHandle
// and numChannels global variables.
channelBuffer = new DWORD [numChannels + 1];
// The +1 is for the required dwSize field
channelBuffer[0] = sizeof(DWORD) * (numChannels + 1);
while (the game is still running) {
// Poll the iput
JoyReadChannels (joystickDevicehandle, channelBuffer);
// Execute the commands...
direction = channelBuffer[channelLookupTable[CHANGE_DIRECTION]];
speed += channelBuffer[channelLookupTable[
ACCELERATE]];
if (channelBuffer[channelLookupTable[
SHOOT_GUNS]]) {
launch a bullet
}
if (channelBuffer[channelLookupTable[
SHOOT_MISSILES]]) {
launch a missle
}
update the world
render the world
}
}
Family Synopsis
Families are designed to specify the logical map of common input devices. Note that for each one, there is a designated ordering to both the channels and the scales. In general:
|
Family |
Channel |
Designation |
Notes |
|
Generic / |
0 |
X |
Minimum values on left |
|
Game Pad |
1 |
Y |
Minimum values on top |
|
2 |
Suggested Primary Button |
For all buttons, one means pressed zero mean released. |
|
|
3 |
Suggested Secondary Button |
||
|
4 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
|
Flight Stick |
0 |
X |
Minimum values on left |
|
1 |
Y |
Minimum values on top |
|
|
2 |
Trigger |
Generally on index finger. |
|
|
3 |
Primary Thumb Button |
||
|
4 |
Hat X |
Entered as an axis even if it is actually implemented as a switch. Minimum values on left |
|
|
5 |
Hat Y |
Entered as an axis even if it is actually implemented as a switch. Minimum values on top |
|
|
6 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
|
Yoke |
0 |
Roll |
Minimum values on left |
|
1 |
Pitch |
Minimum values when forward (away from the user). |
|
|
2 |
Left Primary Button |
||
|
3 |
Right Primary Button |
||
|
4 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
Pedals |
0 |
Yaw |
Minimum values on left |
|
1 |
Left Brake |
Minimum values when released |
|
|
2 |
Right Brake |
Minimum values when released |
|
|
3 |
Accelerator |
Minimum values when released |
|
|
4 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
|
Throttle |
0 |
Thrust |
Minimum values when back (towards user) |
|
1 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
|
Steering |
0 |
Steering Axis |
Minimum values on left |
|
Wheel |
1 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
Directional 3d Tracker |
0 |
Roll |
This is relative to the user’s orientation. Minimum values on user’s left. |
|
1 |
Pitch |
This is relative to the user’s orientation. Minimum values when pointing down. |
|
|
2 |
Yaw |
This is relative to the user’s orientation. Minimum values on user’s left. |
|
|
3 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
|
6d Tracker |
0 |
Roll |
This is relative to the user’s orientation. Minimum values on user’s left. |
|
1 |
Pitch |
This is relative to the user’s orientation. Minimum values when pointing down. |
|
|
2 |
Yaw |
This is relative to the user’s orientation. Minimum values on user’s left. |
|
|
3 |
Translational - X |
This is left / right relative to the monitor. Minimum values to left. |
|
|
4 |
Translational - Y |
This is towards / away from the monitor. Minimum values towards the monitor. |
|
|
5 |
Translational - Z |
This is up / down relative to the monitor. Minimum values above the monitor |
|
|
6 .. n |
Auxiliary Buttons |
Generally ordered in easiest to hit to least easy to hit. No defined meaning. |
Control Panel Applets
Joystick manufacturers should create control panel applets which allow the user to configure their device. I have illustrated what one of these might look like below. This is only a prototype.

Parametric info
Parametric information is used by an application to determine a logical-to-application mapping in the case that the device does not conform well to any of the pre-determined families. It may also be used when the application is very sophisticated and requires complete control over a large number of differing devices such as might be the case with VR equipment.
This information is stored entirely in the registry. As described in the Prototype Data Structures above, the
CAPS structures contain a key into this registry. This key is used by developers to access the parameter information on a per channel basis.
A large number of these parameters have been enumerated by Steve McGowan at Forte in his Access Bus specifications. I suggest these are used a the basis for creating the data. However, in his specification, he uses a string to describe the data due to the nature of the Access Bus. While strings have the advantage of being flexible, I believe that it is more appropriate to avoid flexibility in this case; this avoidance will preclude inconsistencies arising between application which would otherwise render the data useless. Also, strings have the disadvantage that they are difficult to parse as compared to a structure. Thus, I suggest that we take Steve’s info and make it into a structure to be saved into the registry.
The parameteric information falls into two basic categories: application-descriptive and physical- descriptive.
Application-Descriptive
Parameters which relate to a specific usage or are suggestions for a potential use are said to be "application-descriptive". For example, "chaff", "guns", "thrust", "steering wheel", etc. Note that many of the applications described in Steve’s document fall into recognizable categories such as "flight controllers", etc. When these categories are recognized, a "family" has been created to simplify matters as documented above.
Physical-Descriptive
Parameters which are descriptions of the physical or mechanical attributes of a device are said to be "physical-descriptive". These are such things as: "does it return to center?", "is it primary?", "is it rotational", etc. Steve has also defined these in his Access Bus specification. I make only a few suggestions in addition:
Summary and Plan of Action
Create a GameSDK Joystick Interface Version 2.0 which will include:
Homogeneous Channels
Enumeration of Logical Mapping Modes including:
Family
Manufacturer
Parametric Information
Create a "standardized" Control Panel Applet.
Define the parametric fields in sub-committee (i.e. Steve McGowan and Microsoft)
Create a WWW page to summarize these changes either at Microsoft or Origin.