Part D - DirectInput

Controller Input

Sample | Programming | Settings | Controller
Context | User Dialog | Design | Camera | Exercises


Game controllers have become an integral part of most digital games.  Controllers include such diverse devices as game pads, joysticks, steering wheels, and pedals, to name a few.  These devices evolve rapidly and require software support of a broad nature.  The DirectInput API of DirectX was designed to provide such versatile support.  DirectInput is not limited to Microsoft based products and supports many different types of controllers. 

A controller hosts a variety of objects through which the user can interface with a game.  For instance, modern joysticks include buttons, axes-based controls, sliders, and point-of-view hats.  While buttons are binary switches, axis-controls capture position or movement, and point-of-view hats capture orientation and are neither binary nor gradual measures.  A joystick is slightly more versatile than a simple hand-held controller.  Motion in the direction of the axes on a controller is normally absolute with handle movement measured with respect to a central, neutral position. 

In this chapter, we incorporate controller input from a joystick device using the DirectInput API. 


Controller Sample

The Controller Sample interrogates the system and enumerates all of the currently attached controllers, giving the user the option of selecting the controller that they wish to use in their game.  The sample also provides the user with a choice of which object on the selected controller to use as the trigger.  The user can also choose between a y-axis that is near-to-far or far-to-near and to activate or deactivate the z-axis. 

controller dialog

The user interface in this sample adds two combo boxes and two check boxes to the dialog box.  The first combo box lists the installed controllers.  The second combo box lists the buttons available on the selected controller to serves as the trigger in the game.  The check boxes set y-axis reversal and enable the z-axis input. 

The framework populates the controller combo box with a list of the controllers that are currently attached to the host system and the trigger combo box with a list of the buttons that are available on the selected controller.

In this sample, the controller moves the current camera, while a controller object rolls the top box. 


Programming Issues

The programming issues that arise with this sample include

  • enumeration of installed devices
  • selecting a data format for a device
  • polling the device
  • sensitivity of the input data

Enumeration

The EnumDevices() method on the IDirectInput8 interface enumerates all of the input devices that meet the requirements set by the 1st and 4th arguments.  The 1st argument specifies the type of devices.  The 4th argument specifies the scope of the enumeration.  The 2nd argument points to the callback function that the system will call for each device found.  The 3rd argument is the address that will be passed to that function.  The argument to this method is the address of the pre-defined format for a generalized joystick device c_dfDIJoystick2

The EnumObjects() method on the IDirectInputDevice8 interface enumerates all of the input and output objects available of a device.  The 1st argument holds the address of the callback function that the system will call every time it encounters a device object.  The 2nd argument is the address that the system will pass to the callback function on each enumeration.  The 3rd argument specifies the type of objects to be enumerated.

Data Format of a Controller

The SetDataFormat() method on the IDirectInputDevice8 interface sets the data format for the controller.  The argument to this method is the address of the pre-defined format for a generalized joystick device c_dfDIJoystick2

The DIJOYSTATE2 structure holds the state information for a generalized joystick device.  The members holds data for displacements. rotations, linear and angular velocities, linear and angular accelerations, slider kinematics, 4 direction controllers, and 128 buttons.

Polling

If a controller devices are polled.  to be added

Sensitivity

to be added


Settings

The framework's configuration file defines the macros that hold

  • the maximum number of controllers admitted
  • the maximum number of controller objects admitted
  • the default controller settings

and defines the enumeration constants for

  • the controller motions
  • the controller objects
  • the controller descriptions
  • the controller object descriptions

 /* Header for Configuring the Framework
  *
  * Configuration.h
  */

 // ...

 #define MAX_C_BUTTONS   128
 #define MAX_CONTROLLERS  10

 typedef enum Integer {
     // ...
     GF_CT_INDX,
     GF_CT_TGRB,
     GF_CT_FLGS,
     GF_CT_CCNT,
     GF_CT_BCNT,
     GF_CT_DSPX,
     GF_CT_DSPY,
     GF_CT_DSPZ,
     GF_CT_ROTZ,
     // ...
 } Integer;

 // ...

 typedef enum BoolArray {
     GF_KB_KEYS, // key states
     GF_MS_BTNS, // mouse button states
     GF_CT_BTNS  // controller button states
 } BoolArray;

 // ...

 typedef enum StrArray {
     // ...
     GF_CTRDESC, // controller descriptions
     GF_CTBDESC, // controller button descriptions
     // ...
 } StrArray;

 // ...

 // Controller Buttons
 //
 typedef enum CntlrButton {
     TRIGGER,
     BUTTON_1,
     BUTTON_2,
     BUTTON_3,
     BUTTON_4,
     BUTTON_5,
     BUTTON_6,
     BUTTON_7,
     BUTTON_8,
     BUTTON_9,
     BUTTON_10,
     NORTH,
     EAST,
     WEST,
     SOUTH
 } CntrlrButton;

 // ...

 // Dialog options
 //
 // default adapter selection
 #define RUN_IN_WINDOW_DESC L"Run in a window"
 #define RESOLUTION_DESC    L""
 // default controller
 #define NO_CONTROLLER_DESC L"No Controller"
 #define TRIGGER_DESC       L""

 // ...

Model Settings

The Model settings define the controller motion conversion factors

 /* Header for Configuring the Model Branch
  *
  * ModelSettings.h
  */

 // ...

 // controller conversion factors
 #define CTR_SPEED               0.05f
 #define CTR_DISPLACEMENT_FACTOR 0.015f
 #define CTR_ROTATION_FACTOR     0.015f 

 // ...

Window API

The Window API-specific settings defines the symbolic names that are shared between the framework and the dialog resource, the no controller option, and the window and dialog box captions:

 /* Configuration Settings for the Window API Branch
  *
  * WindowSettings.h
  */

 // ...
 #define IDC_CNT 107
 #define IDC_TRG 108
 #define IDC_YRV 109
 #define IDC_ZON 110

 #define NO_CONTROLLER -1

 // Window captions
 //
 #if Z_AXIS == NEAR_TO_FAR
 #define WND_NAME L"Controller (Z Axis Near to Far)" 
 #elif Z_AXIS == FAR_TO_NEAR
 #define WND_NAME L"Controller (Z Axis Far to Near)"
 #endif
 #define DLG_NAME L"Configure fwk4gps - Controller"

 // ...


Controller

The Controller class manages the interrogation of the host system for installed controllers and the connection between the selected controller and the Design object.

Interface

The iController interface exposes seven virtual methods to the framework:

  • interrogate() - interrogates the host system for installed controllers and stores the results in the Context object
  • setup() - sets up the connection to the controller device
  • retrieveInput() - retrieves input from the controller device
  • suspend() - suspends the connection to the controller device
  • restore() - restores the connection to the controller device
  • release() - releases the connection to the controller device
  • Delete() - destroys the controller device's manager
 class iController {
   public:
     virtual bool interrogate(void*)                                = 0;
     virtual bool setup(void*)                                      = 0;
     virtual void retrieveInput()                                   = 0;
     virtual void suspend()                                         = 0;
     virtual bool restore()                                         = 0;
     virtual void release()                                         = 0;
     virtual void Delete() const                                    = 0;
 };

 extern "C"
 iController* CreateController(void*, iContext*);

Class Definition

The instance variables of the Controller class include

  • context - a pointer to the interface to the Context object
  • di - points to an interface to the DirectInput object
  • controller - points to an interface to a DirectInput controller device
  • - a pointer to the array of controllers installed on the system
  • flags identifying
    • axisIsActive - the active axes
    • povIsActive - the points of view status
    • reversey - the y direction
  • pointOfView - the points of view direction
struct Controller {
    GUID    guid;
    wchar_t desc[MAX_DESC + 1];
    static int count;
};

class Controller : public iController {

    iContext*            context;  // points to the Context object
    LPDIRECTINPUT8       di;       // points to the Direct Input object
    LPDIRECTINPUTDEVICE8 controller; // points to the Direct Input controller

    // interrogation
    Controller* attached;          // holds descriptions of the attached
                                   // controllers

    // execution
    bool axisIsActive[4];          // is axis active?
    bool povIsActive;              // point of view is active?
    int  pointOfView[4];           // point of view
    bool reversey;                 // reverse direction of y axis

    Controller(void*, iContext*);
    Controller(const Controller& j);            // prevents copying
    Controller& operator=(const Controller& j); // prevents assignment
    virtual ~Controller();

  public:
    friend iController* CreateController(void*, iContext*);
    bool   interrogate(void*);
    bool   setup(void*);
    bool   restore();
    void   retrieveInput();
    void   suspend();
    void   release();
    void   Delete() const { delete this; }
};

Implementation

The CreateController() function creates the Controller object on dynamic memory:

 int Controller::count = 0;

 iController* CreateController(void* hinst, iContext* c) {
     return new Controller(hinst, c);
 }

Construction

The constructor retrieves an interface to the DirectInput object, initializes the list of installed controllers, the interface to the controller device, and all axes to inactive states. 

 Controller::Controller(void* hinst, iContext* c) : context(c) {

     // acquire an Interface to DirectInput object for this application
     di = NULL;
     if (FAILED(DirectInput8Create((HINSTANCE)hinst, DIRECTINPUT_VERSION,
      IID_IDirectInput8, (void**)&di, NULL))) {
         error(L"Controller::00 Failed to obtain an Interface to Direct "
          L"Input");
      }

     attached = NULL;
     controller = NULL;
     axisIsActive[0] = axisIsActive[1] = axisIsActive[2] = axisIsActive[3] =
      false;
 }

Interrogate

The interrogate() method on the Controller object retrieves the GUIDs and descriptions of the installed controllers, along with the names of their objects and stores this information in the Context object for subsequent user selection. 

 bool Controller::interrogate(void* hwnd) {

     bool rc    = false;
     int  count = 0;

     // find number of attached controllers
     di->EnumDevices(DI8DEVCLASS_GAMECTRL,
      (LPDIENUMDEVICESCALLBACK)countControllers, (void*)&count,
      DIEDFL_ATTACHEDONLY);

     // enumerate controller descriptions
     if (attached)
         delete [] attached;
     attached = new Controller[count];
     attached->count = 0;
     di->EnumDevices(DI8DEVCLASS_GAMECTRL,
      (LPDIENUMDEVICESCALLBACK)enumControllerDesc, (void*)attached,
      DIEDFL_ATTACHEDONLY);
     if (attached->count > MAX_CONTROLLERS) {
         wchar_t str[MAX_DESC + 1];
         sprintf(str, attached->count,
          L" Controllers found - increase MAX_CONTROLLERS");
         error(str);
         attached->count = MAX_CONTROLLERS;
     }

     // set dimensions and allocate context memory
     context->set(GF_CT_CCNT, attached->count);

     // store each controller that can be created successfully
     DIDEVICEOBJECTINSTANCE didoi;   // holds object info for controller
     didoi.dwSize = sizeof didoi;
     for (int ic = 0; ic < attached->count; ic++) {
         // create the device temporarily just to interrogate it
         LPDIRECTINPUTDEVICE8 didInter; // points to the device
         GUID guid = attached[ic].guid;
         if (guid != GUID_NULL &&
          SUCCEEDED(di->CreateDevice(guid, &didInter, NULL)) &&
          SUCCEEDED(didInter->SetDataFormat(&c_dfDIController2))) {
             // examine the controller's objects
             // retrieve information about each button
             for (int b = 0; b < MAX_C_BUTTONS; b++) {
                 if (SUCCEEDED(didInter->GetObjectInfo(&didoi,
                  DIJOFS_BUTTON(b), DIPH_BYOFFSET))) {
                     int i = ic * MAX_C_BUTTONS + b;
                     context->set(GF_CTBDESC, i, didoi.tszName);
                 }
             }
             rc = true;
             didInter->Release();
             didInter = NULL;
         }
         if (rc)
             context->set(GF_CTRDESC, ic, attached[ic].desc);
     }

     return rc || count == 0;
 }

Count Controllers

The countControllers() callback function increments the controller count for every controller that the system finds: 

 BOOL CALLBACK countControllers(LPCDIDEVICEINSTANCE didesc, void* a) {

     (*(int*)a)++;

     return DIENUM_CONTINUE;
 }

Enumerate Controller Descriptions

The enumControllerDesc() callback function stores in the attached controller array the GUID and the description of the controller that the system has currently found:

 BOOL CALLBACK enumControllerDesc(LPCDIDEVICEINSTANCE didesc, void* a) {

     Controller* attached = (Controller*)a;

     int i = attached->count++;
     strcpy(attached[i].desc, didesc->tszInstanceName, MAX_DESC);
     attached[i].guid = didesc->guidInstance;

     return DIENUM_CONTINUE;
 }

Setup

The setup() method on the Controller object connects the selected controller to the framework.  This method retrieves the interface to the selected device, specifies the data format to be used, defines its cooperative level, and sets the device's properties. 

The properties include the controller's

  • range
  • saturation zone
  • dead zone
 bool Controller::setup(void* hwnd) {

     bool rc = false;

     // release the controller object to start from scratch
     release();

     // retrieve the controller context
     int  ic      = context->get(GF_CT_INDX);
     int  button  = context->get(GF_CT_TGRB);
     int  flags   = context->get(GF_CT_FLGS);
     bool none    = flags & 1;
     bool zAxisOn = !!(flags & 2);
     reversey     = !!(flags & 4);
     GUID guid    = none ? GUID_NULL : attached[ic].guid;

     // no controller selected
     if (none)
         rc = true;
     // obtain Interface to the controller
     else if (FAILED(di->CreateDevice(guid, &controller, NULL)))
         error(L"Controller::10 Failed to create an Interface to controller");
     // set the data format for the controller
     else if (FAILED(controller->SetDataFormat(&c_dfDIController2))) {
         release();
         error(L"Controller::11 Failed to set the data format for controller");
     }
     // set the cooperative level
     else if (FAILED(controller->SetCooperativeLevel((HWND)hwnd,
      DISCL_NONEXCLUSIVE | DISCL_FOREGROUND))) {
         release();
         error(L"Controller::12 Failed to set the behavior for controller");
     }
     else {
         // retrieve the axes that are active on this device
         DIDEVICEOBJECTINSTANCE didoi;
         didoi.dwSize = sizeof didoi;
         if (SUCCEEDED(controller->GetObjectInfo(&didoi, DIJOFS_X,
          DIPH_BYOFFSET)))
             axisIsActive[0] = true;
         if (SUCCEEDED(controller->GetObjectInfo(&didoi, DIJOFS_Y,
          DIPH_BYOFFSET)))
             axisIsActive[1] = true;
         if (SUCCEEDED(controller->GetObjectInfo(&didoi, DIJOFS_Z,
          DIPH_BYOFFSET)))
             axisIsActive[2] = true;
         if (SUCCEEDED(controller->GetObjectInfo(&didoi, DIJOFS_RZ,
          DIPH_BYOFFSET)))
             axisIsActive[3] = true;
         // ignore what GetObjectInfo returned if we don't want z axis
         if (!zAxisOn) {
             axisIsActive[2] = false;
             axisIsActive[3] = false;
         }

         // Set the range, deadzone, and saturation for each axis

         DIPROPRANGE range;

         range.diph.dwSize = sizeof range;
         range.diph.dwHeaderSize = sizeof range.diph;
         range.diph.dwObj = DIJOFS_X;
         range.diph.dwHow = DIPH_BYOFFSET;
         range.lMin = -100;
         range.lMax =  100;

         DIPROPDWORD dead,
                     sat;

         dead.diph.dwSize = sizeof dead;
         dead.diph.dwHeaderSize = sizeof dead.diph;
         dead.diph.dwObj = DIJOFS_X;
         dead.diph.dwHow = DIPH_BYOFFSET;
         dead.dwData = 300; // hundredths of a percent [0,10000]

         sat = dead;
         sat.dwData = 9800;

         if (axisIsActive[0]) {
             controller->SetProperty(DIPROP_RANGE, &range.diph);
             controller->SetProperty(DIPROP_DEADZONE, &dead.diph);
             controller->SetProperty(DIPROP_SATURATION, &sat.diph);
         }

         if (axisIsActive[1]) {
             range.diph.dwObj = DIJOFS_Y;
             dead.diph.dwObj  = DIJOFS_Y;
             sat.diph.dwObj   = DIJOFS_Y;
             controller->SetProperty(DIPROP_RANGE, &range.diph);
             controller->SetProperty(DIPROP_DEADZONE, &dead.diph);
             controller->SetProperty(DIPROP_SATURATION, &sat.diph);
         }

         if (axisIsActive[2]) {
             range.diph.dwObj = DIJOFS_Z;
             dead.diph.dwObj  = DIJOFS_Z;
             sat.diph.dwObj   = DIJOFS_Z;
             controller->SetProperty(DIPROP_RANGE, &range.diph);
             controller->SetProperty(DIPROP_DEADZONE, &dead.diph);
             controller->SetProperty(DIPROP_SATURATION, &sat.diph);
         }

         if (axisIsActive[3]) {
             range.diph.dwObj = DIJOFS_RZ;
             dead.diph.dwObj  = DIJOFS_RZ;
             sat.diph.dwObj   = DIJOFS_RZ;
             controller->SetProperty(DIPROP_RANGE, &range.diph);
             controller->SetProperty(DIPROP_DEADZONE, &dead.diph);
             controller->SetProperty(DIPROP_SATURATION, &sat.diph);
         }

         // try to acquire the controller
         controller->Acquire();
         rc = true;
     }

     return rc;
 } 

The CreateDevice() method on the DirectInput object retrieves an interface to the controller device that is identified by the GUID supplied in the first parameter.  This method returns the address of the interface through its parameter list. 

The SetDataFormat() method on the interface to the controller device takes the address of a an instance of a DIDATAFORMAT struct that describes the format of the data packet on the device.  Here, we use DirectInput's pre-defined global variable c_dfDIController2, which is sufficiently broad for most controller types. 

The SetCooperativeLevel() method on the interface to the controller device defines how the device is to be shared with other applications running on the host system.  The DISCL_NONEXCLUSIVE | DISCL_FOREGROUND flag combination works for controllers without force feedback.  This combination specifies that the application has access to the controller's data only when the application is running in the foreground and that other applications may acquire the controller in either exclusive or non-exclusive mode. 

The SetProperties() method on the interface to the controller device specifies the properties of the device itself.  DirectInput uses a DIPROPDWORD structure to specify a single property value and a DIPROPRANGE structure to specify a range of property values.  The DIPROPDWORD structure consists of two members: a DIPROPHEADER structure and a DWORD that holds the property value.  The DIPROPRANGE structure consists of three members: a DIPROPHEADER structure and two LONGs that hold the minimum and maximum values. 

Update

The update() method on the Controller object retrieves the current state of the device.  This method stores the state in the Context object:

 void Controller::retrieveInput() {

     HRESULT hr;
     DIJOYSTATE2 state;

     if (controller) {
         // make the current state available
         controller->Poll();
         // retrieve the state of the controller
         hr = controller->GetDeviceState(sizeof(DIJOYSTATE2), &state);
         if (DIERR_INPUTLOST == hr && SUCCEEDED(controller->Acquire()))
             hr = controller->GetDeviceState(sizeof(DIJOYSTATE2), &state);
         if (SUCCEEDED(hr)) {
             // current state components
             int x = 0, y = 0, z = 0, r = 0;
             if (axisIsActive[0])
                 x = state.lX;
             if (axisIsActive[1])
                 y = reversey ? -state.lY : state.lY;
             if (axisIsActive[2])
                 z = state.lZ;
             if (axisIsActive[3])
                 r = state.lRz;
             if (povIsActive)
                 for (int i = 0; i < 4; i++)
                     pointOfView[0] = state.rgdwPOV[i];
             // buttons currently pressed
             for (int i = 0; i < MAX_C_BUTTONS; i++)
                 context->set(GF_CT_BTNS, i, (state.rgbButtons[i] & 0x80) != 0);
             context->set(GF_CT_DSPX, x);
             context->set(GF_CT_DSPY, y);
             context->set(GF_CT_DSPZ, z);
             context->set(GF_CT_ROTZ, r);
         }
     }
 }

Suspend

The suspend() method on the Controller object notififies the system that the framework has stopped using the device: 

 void Controller::suspend() {
     if (controller) controller->Unacquire();
 }

Restore

The restore() method on the Controller object informs the system that the framework is ready to accept input from the device: 

 bool Controller::restore() {

     bool rc = true;

     if (controller) {
         HRESULT hr = controller->Acquire();
         if (hr != S_OK && hr != S_FALSE && hr != DIERR_OTHERAPPHASPRIO) {
             release();
              error(L"Controller::70 Failed to re-acquire the controller");
             rc = false;
         }
     }

     return rc;
 }

Release

The release() method on the Controller object disconnects the device from the framework: 

 void Controller::release() {
     suspend();
     if (controller) {
         controller->Release();
         controller = NULL;
     }
 }

It is important to suspend the Controller object before releasing its connection to the device. 

Destructor

The destructor releases the connection, if any, to the controller device, releases the interface to the DirectInput object and deallocates the memory for the controller descriptions:

 Controller::~Controller() {
     release();
     if (di) {
         di->Release();
         di = NULL;
     }
     if (attached)
         delete [] attached;
 }

Context

The Context object stores the installed controller descriptions along with their object descriptions and the current controller position and its objects' states.  This upgrade only affects the implementation of the Context class.

Implementation

Construction

The constructor allocates memory for the controller descriptions and states:

 Context::Context() {

     // ...

     // allocate context memory for controller button states
     alloc(GF_CT_BTNS, MAX_C_BUTTONS);
     set(GF_CT_BCNT, MAX_C_BUTTONS);

     // ...

     // allocate context memory for controller descriptions
     alloc(GF_CTRDESC, MAX_CONTROLLERS, NULL);

     // ...
 }

User Dialog

The UserDialog class presents the user with a list of attached controllers and their objects and stores the user's controller and trigger selections in the Context object. 

Interface

The iUserDialog interface exposes two virtual methods to the framework:

  • populateControllerList() - populates the controller combo box
  • populateControllerObjectList() - populates the trigger combo box
 class iUserDialog {
   public:
     // ...
     virtual void populateControllerList(void*)       = 0;
     virtual void populateControllerObjectList(void*) = 0;
     // ...
 };

 // ...

Class Definition

The instance variables of the UserDialog class now include

  • controller - the description of the selected controller
  • trigger - the description of the selected trigger
  • flags - flags defining the selected state of the controller
 class UserDialog : public iUserDialog {

     // ...

     wchar_t controller[MAX_DESC + 1];
     wchar_t trigger   [MAX_DESC + 1];
     int     flags;

     // ...

   public:
     // ...
     void   populateControllerList(void*);
     void   populateControllerObjectList(void*);
     // ...
 };

Implementation

Dialog Box

The dialog resource script describes the additional controls in the dialog box: 

 IDD_DLG DIALOGEX 200, 100, 200, 290
 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION DLG_NAME
 FONT 8, "MS Sans Serif", 0, 0, 0x0
 BEGIN
     // ...
     LTEXT           "Controller", IDC_STATIC, 10, 125, 50, 10
     COMBOBOX        IDC_CNT,10,135,180,50, CBS_DROPDOWNLIST | WS_VSCROLL | \
                     WS_TABSTOP
     LTEXT           "Trigger", IDC_STATIC, 10, 155, 50, 10
     COMBOBOX        IDC_TRG, 10, 165, 180, 50, CBS_DROPDOWNLIST | WS_VSCROLL | \
                     WS_TABSTOP
     CONTROL         "Reverse Y Axis", IDC_YRV, "Button", BS_AUTOCHECKBOX | \
                     WS_TABSTOP, 10, 185, 65, 10
     CONTROL         "Use Z Axis (if available)", IDC_ZON, "Button", \
                     BS_AUTOCHECKBOX | WS_TABSTOP, 85, 185, 100, 10
     // ...
 END

Construction

The constructor

  • stores the address of the Joystick object in a class variable
  • initializes the default controller selection
  • initializes the default trigger selection
  • initializes the selected controller state
 UserDialog::UserDialog(void* h, iContext* c) : hinst(h), context(c) {

     // ...

     strcpy(controller, NO_CONTROLLER_DESC, MAX_DESC);
     strcpy(trigger,    L"", MAX_DESC);
     reversey = false;
     zAxisOn  = false;
     flags    = 0;
     hwnd     = NULL;
 }

Populate User Dialog

The populateUserDialog() method on the UserDialog object populates the controller combo box:

 void UserDialog::populateUserDialog(void* hwnd) {

     // ...
     populateSoundFileList(hwnd);
     // populate the controller combo box
     populateControllerList(hwnd);
 }

Populate Controller List

The populateControllerList() method on the UserDialog object fills the controller combo box with a list of the installed controllers retrieved from the Context object.  The first item in the list is the no controller option: 

 void UserDialog::populateControllerList(void* hwndw) {

     HWND hwnd = (HWND)hwndw; // handle to current window

     // empty the controller combo box
     SendDlgItemMessage(hwnd, IDC_CNT, CB_RESETCONTENT, 0, 0L);
     // start the list with a no controller option
     int ctr = SendDlgItemMessage(hwnd, IDC_CNT, CB_ADDSTRING, 0,
      (LPARAM)NO_CONTROLLER_DESC);
     // the data item for the no controller line item is NO_CONTROLLER
     SendDlgItemMessage(hwnd, IDC_CNT, CB_SETITEMDATA, ctr, NO_CONTROLLER);
     // assume no controller selected - correct this assumption if necessary
     SendDlgItemMessage(hwnd, IDC_CNT, CB_SETCURSEL, ctr, 0L);
     // uncheck the 'reverse y axis' checkbox
     SendDlgItemMessage(hwnd, IDC_YRV, BM_SETCHECK, BST_UNCHECKED, 0L);
     // uncheck the 'use z axis' checkbox
     SendDlgItemMessage(hwnd, IDC_ZON, BM_SETCHECK, BST_UNCHECKED, 0L);
     // disable the trigger and the check boxes
     EnableWindow(GetDlgItem(hwnd, IDC_TRG), FALSE);
     EnableWindow(GetDlgItem(hwnd, IDC_YRV), FALSE);
     EnableWindow(GetDlgItem(hwnd, IDC_ZON), FALSE);

     // populate the controller combo box
     int nc = context->get(GF_CT_CCNT);
     bool atLeastOne = false;
     for (int ic = 0; ic < nc; ic++) {
         if (context->get(GF_CTRDESC, ic)[0]) {
             // store the controller description in the address part
             ctr = SendDlgItemMessage(hwnd, IDC_CNT, CB_ADDSTRING, 0,
              (LPARAM)context->get(GF_CTRDESC, ic));
             // store the controller index in the data part
             SendDlgItemMessage(hwnd, IDC_CNT, CB_SETITEMDATA, ctr,
              (LPARAM)ic);
             atLeastOne = true;
         }
     }
     // if at least one controller is listed, correct the default choices
     if (atLeastOne) {
         // set the visible line item to the previously selected one
         ctr = SendDlgItemMessage(hwnd, IDC_CNT, CB_FINDSTRINGEXACT, -1,
          (LPARAM)controller);
         if (ctr == CB_ERR)
             ctr = 0;
         SendDlgItemMessage(hwnd, IDC_CNT, CB_SETCURSEL, ctr, 0);
         // retrieve the index of the controller for the default line item
         int ic = SendDlgItemMessage(hwnd, IDC_CNT, CB_GETITEMDATA, ctr, 0);
         // if choice is an attached controller
         if (ic != NO_CONTROLLER) {
             // populate the trigger selection list
             populateControllerObjectList(hwnd);
             // retrieve previous checkbox settings
             bool zAOn = !!(flags & 2);
             bool yRev = !!(flags & 4);
             // set the 'reverse y axis' checkbox to the previous setting
             SendDlgItemMessage(hwnd, IDC_YRV, BM_SETCHECK, yRev ?
              BST_CHECKED : BST_UNCHECKED, 0L);
             // set the 'use z axis' checkbox to the previous setting
             SendDlgItemMessage(hwnd, IDC_ZON, BM_SETCHECK, zAOn ?
              BST_CHECKED : BST_UNCHECKED, 0L);
             // enable the trigger combo box and the check boxes
             EnableWindow(GetDlgItem(hwnd, IDC_YRV), TRUE);
             EnableWindow(GetDlgItem(hwnd, IDC_ZON), TRUE);
         }
     }
 }

Populate Controller Object List

The populateControllerObjectList() helper method on the UserDialog object fills the trigger combo box with a list of the objects on the selected controller: 

 void UserDialog::populateControllerObjectList(void* hwndw) {

     int ctr; // line item of selected controller
     HWND hwnd = (HWND)hwndw; // handle to current window

     // empty the controller objects combo box
     SendDlgItemMessage(hwnd, IDC_TRG, CB_RESETCONTENT, 0, 0L);
     // assume no trigger selection possible - correct if necessary
     EnableWindow(GetDlgItem(hwnd, IDC_TRG), FALSE);
     // retrieve the line item of the selected controller
     ctr = SendDlgItemMessage(hwnd, IDC_CNT, CB_GETCURSEL, 0, 0L);
     if (ctr != CB_ERR) {
         // retrieve the index of the selected controller
         int ic = SendDlgItemMessage(hwnd, IDC_CNT, CB_GETITEMDATA, ctr, 0);
         if (ic != NO_CONTROLLER) {
             // examine each button on the controller
             bool found = false;
             int nb = context->get(GF_CT_BCNT);
             for (int b = 0; b < nb; b++) {
                 if (context->get(GF_CTBDESC, b)[0]) {
                     // add the button's name to the trigger combo list
                     int trg = SendDlgItemMessage(hwnd, IDC_TRG, CB_ADDSTRING,
                      0, (LPARAM)context->get(GF_CTBDESC, b));
                     // save the button number in the data area of line item
                     SendDlgItemMessage(hwnd, IDC_TRG, CB_SETITEMDATA, trg, b);
                     // found at least one button
                     found = true;
                 }
             }
             // if buttons exist, set the default button to previously
             // selected one
             if (found) {
                 int trg = 0;
                 // determine the default trigger button
                 if (!wcscmp(context->get(GF_CTRDESC, ic), controller)) {
                     // set the default to the previously selected trigger
                     trg = SendDlgItemMessage(hwnd, IDC_TRG, CB_FINDSTRINGEXACT,
                      -1, (LPARAM)trigger);
                     if (trg == CB_ERR) trg = 0;
                 }
                 SendDlgItemMessage(hwnd, IDC_TRG, CB_SETCURSEL, trg, 0L);
                 // enable the trigger selection and check boxes
                 EnableWindow(GetDlgItem(hwnd, IDC_TRG), TRUE);
             }
             EnableWindow(GetDlgItem(hwnd, IDC_YRV), TRUE);
             EnableWindow(GetDlgItem(hwnd, IDC_ZON), TRUE);
         }
     }
 }

Save User Choices

The saveUserChoices() method on the UserDialog object stores the user's selections for the controller, its trigger, and its settings in the Context object and saves this information in the instance variables as the default values for the next re-configuration, if any: 

 bool UserDialog::saveUserChoices(void* hwndw) {

     bool rcd = false, rcc = false;
     HWND hwnd = (HWND)hwndw; // handle to current window

     // ...

     //----- controller properties ---------------------------------------------

     // retrieve the user's selections
     int ctr = SendDlgItemMessage(hwnd, IDC_CNT, CB_GETCURSEL, 0, 0L);
     if (ctr == CB_ERR)
         error(L"UserDialog::54 Controller selection failed");
     else {
         int  trg, b = 0;
         bool y, z, none;
         // retrieve the index of the selected controller
         int ic = SendDlgItemMessage(hwnd, IDC_CNT, CB_GETITEMDATA, ctr, 0L);
         none = ic == NO_CONTROLLER;
         if (!none) {
             // retrieve the selected trigger button
             trg = SendDlgItemMessage(hwnd, IDC_TRG, CB_GETCURSEL, 0, 0L);
             if (trg == CB_ERR) {
                 error(L"UserDialog::55 Trigger selection failed");
                 trg = 0;
                 b = 0;
             }
             else
                 b = SendDlgItemMessage(hwnd, IDC_TRG, CB_GETITEMDATA, trg, 0);
             // retrieve the selected y axis direction
             y = SendDlgItemMessage(hwnd, IDC_YRV, BM_GETCHECK, 0, 0)
              == BST_CHECKED;
             // retrieve the selected z axis usage
             z = SendDlgItemMessage(hwnd, IDC_ZON, BM_GETCHECK, 0, 0)
              == BST_CHECKED;
         }
         else {
             b  = trg = 0;
             y  = z   = false;
             ic = 0;
         }

         // (re-)define the controller aspect of the context
         flags = ((y ? 1 : 0) << 2) | ((z ? 1 : 0) << 1) | (none ? 1 : 0);
         context->set(GF_CT_INDX, ic);
         context->set(GF_CT_TGRB, b);
         context->set(GF_CT_FLGS, flags);
         // store descriptions for future initialization
         SendDlgItemMessage(hwnd, IDC_CNT, CB_GETLBTEXT, ctr, (LPARAM)controller);
         SendDlgItemMessage(hwnd, IDC_TRG, CB_GETLBTEXT, trg, (LPARAM)trigger);

         rcc = true;
     }

     return rcd && rcc;
 }

Dialog Procedure

The dlgProc() callback function traps the case where the user has changed their selection from the controller combo box.  In this case, this function calls the populateControllerObjectList() method on the UserDialog object to fill the trigger combo box with the names of that particular controller's objects:

 BOOL CALLBACK dlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {

     BOOL         rc         = FALSE;
     static bool  firsttime  = true;
     iEngine*     engine     = EngineAddress();
     iUserDialog* userDialog = UserDialogAddress();

     // Process message msg
     switch (msg) {
       // ...

       case WM_COMMAND:          // user accessed a dialog box control
         switch (LOWORD(wp)) {   // which control?
           // ...

           case IDC_CNT:  // user accessed the controller combo box
             if (HIWORD(wp) == CBN_SELCHANGE)
                 // populate the objects combo box for the selected controller
                 userDialog->populateControllerObjectList(hwnd);
             break;

           // ...
         }
         break;
     }

     return rc;
 }

Design

The Design class creates the Controller object, polls the Context object for controller input, and destroys the Controller object on destruction.  This upgrade affects the class definition and the class implementation. 

Class Definition

The instance variables of the Design class now include

  • controller - points to the Controller object
 class Design : public iDesign {

     // ...

     iController*  controller;  // points to the Controller object

     // ...

   public:
     // ...
 };

Implementation

Construction

The constructor creates the Controller object:

 Design::Design(void* hinst, iContext* context) {

     // ...

     controller = CreateController(hinst, context);
     // ...

 }

Update

The update() method on the Design object responds to a pressing of the controller's trigger and 4th button.  A trigger press plays the discrete sound.  A press of the 4th button rolls the top box about its z axis:

 void Design::update(int now) {

     // ...

     if (context->pressed(AUD_BKGRD))
         background->toggle(now);
     if (context->pressed(AUD_IMPLS) || context->pressed())
         discrete->toggle(now);

     // ...

     // add changes introduced by keyboard input
     if (context->pressed(MDL_ROLL_BOXES))
         dr += delta;
     if (context->pressed(BUTTON_4))
         dt += delta;

     // ...
 }

Suspend

The suspend() method on the Design object notifies the system that the application is no longer processing input from the controller:

 void Design::suspend() {
     coordinator->suspend();
     display->suspend();
     controller->suspend();
     mouse->suspend();
     keyboard->suspend();
 }

Restore

The restore() method on the Design object informs the system that the application is ready to receive input from the controller:

 void Design::restore(int now) {
     keyboard->restore();
     mouse->restore();
     controller->restore();
     display->restore();
     coordinator->restore(now);
     lastUpdate = time();
 }

Destructor

The destructor destroys the Controller object:

 Design::~Design() {
     coordinator->Delete();
     display->Delete();
     soundCard->Delete();
     controller->Delete();
     mouse->Delete();
     keyboard->Delete();
 }

Camera

The Camera class polls the Context object for controller input.  This upgrade only affects the Camera class. 

Implementation

Update

The update() method on the Camera object reacts to two motions of the controller's handle.  Movement in the controller's selected y direction translates the current camera.  Twisting of the handle about its own axis yaws the camera:

 void Camera::update(int delta) {

     // ...

     // joystick input
     int jy = context->get(GF_CT_DSPY);
     int jr = context->get(GF_CT_ROTZ);
     if (jy)
         dx = (int)(jy * CTR_DISPLACEMENT_FACTOR);
     if (jr)
         dy = -(int)(jr * CTR_ROTATION_FACTOR);

     // ...
 }

The displacement and rotation factors are defined in the ModelSettings.h file.


Exercises




Previous Reading  Previous: Keyboard and Mouse Input Next: OpenGL Conventions   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo