OpenGL SuperBible: Comprehensive Tutorial and Reference

Recommended OpenGL Book by Fallout Software

OpenGL SuperBible is an excellent resource for tutorials and reference for OpenGL developers.

At approximately $45, it is a little expensive, but it is hands down the best OpenGL book for beginners. Without it I would find writing my free OpenGL tutorials much more difficult.

Subjects covered:

 

  • Basic OpenGL Concepts
  • Basic Rendering
  • Vector/Matrix Transformations
  • Basic Texturing
  • Thinking Outside the Box: Non-stock Shaders
  • Advanced Texturing Methods
  • Advanced Geometry Management
  • OpenGL on OSX and Linux
  • OpenGL ES on Mobile Devices
  • Click Here to "Look Inside" OpenGL Superbible

    Be Friendly
    Search This Site for Tutorials About...

    Creating a DirectInput Application (Adding Keyboard & Mouse Controls)

    Just for fun: How to Make French Toast

    Initialization of DirectX-based components is usually a pretty technical task. That means there won't be much theory in this tutorial. I will show you how to initialize and use a Direct Input interface in this tutorial. Both keyboard and mouse input are covered. The source code combines files: dierr.h, dierr.cpp, directinput.h and directinput.cpp:

    
    dierr.h         ; DirectInput error codes header file
    dierr.cpp       ; DirectInput error codes implementation
    directinput.h   ; DirectInput definitions
    directinput.cpp ; DirectInput implementation
    
    

    I always think it's more convenient to look at the header file of a particular code block first and only then read the contents of the actual code (the c, cpp files). So here's directinput.h, it contains all of the main Direct Input function definitions and some structures:

    
    #define MOUSEL	0
    #define MOUSER	1
    #define MOUSEM	2
    
    // mouse_t; defines the mouse data struct
    typedef struct tagMouse
    {
        int x, y,
        pinstate,
        pin;
        DIMOUSESTATE state;
    } mouse_t;
    
    // keyboard_t; defines the keyboard data struct
    typedef struct tagKeyboard
    {
        byte state[256];
    } keyboard_t;
    
    extern mouse_t mse;
    extern keyboard_t key;
    
    extern LPDIRECTINPUT       lpdi;             // DI interface
    extern LPDIRECTINPUTDEVICE lpKeyboard;       // keyboard device
    extern LPDIRECTINPUTDEVICE lpMouse;          // mouse device
    
    void I_Init (HWND hwnd, HINSTANCE instance); // initializes DI interface
    void I_Shutdown (void);                      // shuts down DI interface
    void I_DeviceInit (void);                    // resets keyboard and mouse structs to 0
    void I_Get (void);                           // gets mouse and keyboard input
    void I_GetMouse (void);                      // gets mouse input only
    void I_GetKeyboard (void);                   // gets keyboard input only
    void I_ReaquireKeyboard (void);              // reaquire the keyboard device
    void I_ReaquireMouse (void);                 // reaquire the mouse device
    
    

    The #defines MOUSEL, MOUSER and MOUSEM define the constant values for the left, right and middle mouse button controls. The mouse_t structure defines all variables needed to utilize the mouse controls. The keyboard_t struct defines the 256-key array that will be used to identify a key stroke. The functions defined by I_name are commented and should be self-explanatory. We will look at the contents of each in a while. For now, lets look at lpdi, lpKeyboard and lpMouse. lpdi is the main Direct Input interface, it should be first initialized (which is done within I_Init) and destroyed at the end of your program (this is accomplished with a call to I_Shutdown). lpKeyboard is a Direct Input device. A DI device is simply an interface for defining an input system. There could be keyboard, mouse and joystick DI input devices. In this tutorial we're only concerned with keyboard and mouse input. The DI input device that describes the mouse controls is lpMouse. Each input device must be created. Also, you have to set cooperative level for each input device, set its data format, and finally acquire it. These four steps for each of the two input devices (mouse, keyboard) are accomplished within I_Init. If, during the execution of your code, an input device is interrupted (this could happen when switching between windowed and full-screen modes or when your application conflicts with another one which also uses Direct Input), it needs to be reaquired. And that's exactly what I_ReaquireKeyboard(void) and I_ReaquireMouse(void) functions are for.

    After an input device is created and acquired, you can start getting input from it. For the described in this tutorial mouse and keyboard input interfaces, this is accomplished by calling I_GetMouse(void) and I_GetKeyboard(void). To get both simultaneously, call the I_Get(void) command. I_DeviceInit(void) is called to reset all parameters of the used mouse and keyboard structs (mouse_t mse; and keyboard_t key;) to 0, to make sure that all mouse buttons and all keys on the keyboard are set to unpressed state.

    Before we step right into the Direct Input code, I want to mention how Direct Input is handled in case of an error during initialization or during a call to a Direct Input command. The files dierr.h and dierr.cpp take care of this functionality. You see, when a call to a Direct Input command is made, in case of an error a specific function writes the error code out into the system log file describing the error encountered as that command was executing. The reason for this is that the code would start to look extremely unreadable if we included all error-code variations within the I_Init(void) function itself. dierr.h and dierr.cpp makes it all look a lot cleaner. dierr.cpp is rather lengthy, as it contains functions to output all possible Direct Input error codes for almost every Direct Input function we will be using. However, we'll take a look at dierr.h:

    
    extern char dierr_msg[128];
    extern dword err;
    
    // direct input errors                          // Use with these functions:
    extern char *dierrCreate (dword err);		// DirectInputCreate
    extern char *dierrCreateDevice (dword err);	// CreateDevice
    extern char *dierrCooperate (dword err);	// SetCooperativeLevel
    extern char *dierrSetDataFormat (dword err);	// SetDataFormat
    extern char *dierrAcquire (dword err);		// Acquire
    extern char *dierrGetDeviceState (dword err);	// GetDeviceState
    
    

    The Direct Input interface provides the programmer with some generic functions. These functions manage the DI interface. For example the DirectInputCreate function creates the DI interface. The function CreateDevice is called to create a DI device, and so on. Each of these functions usually returns DI_OK. But when they don't, that means there is an error. In that case, dierr.h and dierr.cpp come to help. When a DI function fails, execution falls back to one of the functions specified in these modules. These functions define the "error-code" versions of each generic DI command. So for example, if the function DirectInputCreate fails with a return value that doesn't match DI_OK, we will use the dierrCreate function to find out what the error code actually was. The return value of dierrCreate is a C, NULL-terminated string. This string is then passed to the LogErr(...); function which logs it to the system log file(the LogErr(...) function is explained in tutorial c & misc. - 1). The program then exits and you are able to look up what the problem was in the system log file and then try to fix the situation. But that's all in case of an error, which shouldn't be the case(perhaps, unless DirectX isn't installed on the machine you're running this code on).

    Every separate DirectX component must be first initialized. Here, we're dealing with Direct Input, so let's see what we need to do in order to initialize Direct Input. Direct Input initialization consists of creating the DI interface by calling the generic function DirectInputCreate. The parameters of this function are the windows instance handle (hinstance), the version of DI you are initializing (DIRECTINPUT_VERSION; could be anywhere from 0x0300 to 0x0700, in this case 0x0700) and the address of the DI object you are initializing (and that is the object already described above, defined as LPDIRECTINPUT lpdi; (remember this is the main DI interface object throughout the code). After the DI interface object is successfully created, we have to use it to create the keyboard and mouse input devices. As already mentioned for each input device to be operational, it must be first created, then you have to set its cooperative level, then you have to set its data format and finally acquire it. And this is exactly what we're doing in the remaining part of I_Init. Take a look:

    
    void I_Init (HWND hwnd, HINSTANCE instance)
    {
        Log("; I_Init(); initialize DirectInput\n");
    
        I_DeviceInit();
    
        // create the direct input object
        if ((err = DirectInputCreate(instance, DIRECTINPUT_VERSION, &lpdi, NULL)) != DI_OK)
            LogErr(dierrCreate(err));
    
        // create keyboard
        if ((err = lpdi->CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL)) != DI_OK)
            LogErr(dierrCreateDevice(err));
        if ((err = lpKeyboard->SetCooperativeLevel(hwnd, DISCL_BACKGROUND|DISCL_NONEXCLUSIVE)) != DI_OK)
            LogErr(dierrCooperate(err));
        if ((err = lpKeyboard->SetDataFormat(&c_dfDIKeyboard)) != DI_OK)
            LogErr(dierrSetDataFormat(err));
        if ((err = lpKeyboard->Acquire()) != DI_OK)
            LogErr(dierrAcquire(err));
    
        // create mouse
        if ((err = lpdi->CreateDevice(GUID_SysMouse, &lpMouse, NULL)) != DI_OK)
            LogErr(dierrCreateDevice(err));
        if ((err = lpMouse->SetCooperativeLevel(hwnd, DISCL_BACKGROUND|DISCL_NONEXCLUSIVE)) != DI_OK)
            LogErr(dierrCooperate(err));
        if ((err = lpMouse->SetDataFormat(&c_dfDIMouse)) != DI_OK)
            LogErr(dierrSetDataFormat(err));
        if ((err = lpMouse->Acquire()) != DI_OK)
            LogErr(dierrAcquire(err));
    }
    
    

    Creating the Input Device. Two interface devices are created after the main DI object (lpdi) is initialized. Note, to create an input device, the CreateDevice function is used which is a member of the initialized DI object. To create a keyboard device, pass GUID_SysKeyboard to CreateDevice(...) as the first parameter. To create a mouse device, GUID_SysMouse is used as the first parameter. These GUIDs are already defined by DI. The second parameter is the address of the device that will be used to obtain input. As previously noted, the keyboard device is defined as lpKeyboard. The mouse device is defined as lpMouse. The third parameter of CreateDevice is not used, pass NULL to it.

    Setting Cooperative Level. Setting cooperative level of a DI input device is accomplished with the SetCooperativeLevel member function of the device object. The window handle(HWND hwnd) is passed as the first parameter, but the key points here are the flags used to set cooperative levels of both input devices (keyboard and mouse). DISCL_BACKGROUND and DISCL_NONEXCLUSIVE are OR-ed together. As DI documentation states, DISCL_BACKGROUND is used when the application requires "background access". If background access is granted, the device may be acquired at any time, even when the associated window is not the active window. What this basically means is that input can be aquired by your program even if its not currently active. Your program could be running in background but input would still be taken from the device you're setting this flag to. DISCL_NONEXCLUSIVE means that the application requires non-exclusive access. Access to the device will not interfere with other applications that are accessing the same device. You see, we would have used DISCL_FOREGROUND (as opposed to DISCL_BACKGROUND) but if we do, each time our OpenGL window becomes inactive, the input devices are automatically unacquired (and lost). Just for the purpose of these tutorials we're making things simple. However, if you're using this code for your own purposes, such as a game you're developing, you could specify the DISCL_FOREGROUND flag here. But then you will have to take care of reacquiring the input device interfaces, of course. Note also, from DI documentation: Applications must specify either DISCL_FOREGROUND or DISCL_BACKGROUND; it is an error to specify both or neither. Similarly, applications must specify either DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE. The final two steps in creation of both input devices are setting data format and acquiring the device. These steps should be easy to understand. Look up more information about these processes in the MSDN documentation if you need to. Once the device is acquired, it's ready to be used by your program. How to get keyboard and mouse input from the two acquired devices is explained below.

    GETTING KEYBOARD AND MOUSE INPUT

    So you've initialized Direct Input and acquired both the mouse and keyboard input devices. How exactly do we get input from these devices? First, I will explain keyboard input. The functions that manage input from the keyboard are I_GetKeyboard and I_ReacquireKeyboard. Let's take a look at the code:

    
    void I_ReacquireKeyboard (void)
    {
        if ((err = lpKeyboard->Acquire()) != DI_OK)
            Log("di_ok\n");
        else
            LogErr(dierrAcquire(err));
    }
    
    void I_GetKeyboard (void)
    {
        if ((err = lpKeyboard->GetDeviceState( ((sizeof(unsigned char)) << 8), (void*)key.state)) != DI_OK)
            if ((err & DIERR_INPUTLOST) && (err & DIERR_NOTACQUIRED))
    	{
                // try to (re-)acquire the keyboard interface
                I_ReacquireKeyboard();
            } else // ...some other error
                LogErr(dierrGetDeviceState(err));
    }
    
    

    To get input from keyboard, in your main loop, you would simply call I_GetKeyboard. What it does is it calls the GetDeviceState member function of the keyboard device and if the device happened to be unacquired it tries to reacquire it. Note that I pass the keyboard array(containing 256 entries) key.state to GetDeviceState as the last parameter. If Direct Input identifies a key stroke, it places a value other than 0 into one of the keyboard array entries(this array is defined by the keyboard_t key struct). For each of the 256 keyboard array entries DI defines a keyboard device constant. For example to specify the Escape key, DI defines it as DIK_ESCAPE. To specify the enter key, DIK_RETURN is defined. So then, to see if the Escape key has been pressed, you would code something like this:

    
    if (key.state[DIK_ESCAPE])
    {
        // Escape is down, do something...
    }
    
    

    There is a keyboard device constant for each key on the keyboard, defined in Dinput.h:

    
    Constant          Note
    
    DIK_ESCAPE
    DIK_1             On main keyboard
    DIK_2             On main keyboard
    DIK_3             On main keyboard
    DIK_4             On main keyboard
    DIK_5             On main keyboard
    DIK_6             On main keyboard
    DIK_7             On main keyboard
    DIK_8             On main keyboard
    DIK_9             On main keyboard
    DIK_0             On main keyboard
    DIK_MINUS         On main keyboard
    DIK_EQUALS        On main keyboard
    DIK_BACK          The backspace key
    DIK_TAB
    DIK_Q
    DIK_W
    DIK_E
    DIK_R
    DIK_T
    DIK_Y
    DIK_U
    DIK_I
    DIK_O
    DIK_P
    DIK_LBRACKET      The [ key
    DIK_RBRACKET      The ] key
    DIK_RETURN        enter key on main keyboard
    DIK_LCONTROL      Left ctrl key
    DIK_A
    DIK_S
    DIK_D
    DIK_F
    DIK_G
    DIK_H
    DIK_J
    DIK_K
    DIK_L
    DIK_SEMICOLON
    DIK_APOSTROPHE
    DIK_GRAVE          Grave accent (`) key
    DIK_LSHIFT         Left shift key
    DIK_BACKSLASH
    DIK_Z
    DIK_X
    DIK_C
    DIK_V
    DIK_B
    DIK_N
    DIK_M
    DIK_COMMA
    DIK_PERIOD         On main keyboard
    DIK_SLASH          Forward slash on main keyboard
    DIK_RSHIFT         Right shift key
    DIK_MULTIPLY       The * key on numeric keypad
    DIK_LMENU          Left alt key
    DIK_SPACE          spacebar
    DIK_CAPITAL        caps lock key
    DIK_F1
    DIK_F2
    DIK_F3
    DIK_F4
    DIK_F5
    DIK_F6
    DIK_F7
    DIK_F8
    DIK_F9
    DIK_F10
    DIK_NUMLOCK
    DIK_SCROLL         scroll lock
    DIK_NUMPAD7
    DIK_NUMPAD8
    DIK_NUMPAD9
    DIK_SUBTRACT       minus sign on numeric keypad
    DIK_NUMPAD4
    DIK_NUMPAD5
    DIK_NUMPAD6
    DIK_ADD            plus sign on numeric keypad
    DIK_NUMPAD1
    DIK_NUMPAD2
    DIK_NUMPAD3
    DIK_NUMPAD0
    DIK_DECIMAL        period (decimal point) on numeric keypad
    DIK_F11
    DIK_F12
    DIK_F13
    DIK_F14
    DIK_F15
    DIK_KANA           On Japanese keyboard
    DIK_CONVERT        On Japanese keyboard
    DIK_NOCONVERT      On Japanese keyboard
    DIK_YEN            On Japanese keyboard
    DIK_NUMPADEQUALS   On numeric keypad (NEC PC98)
    DIK_CIRCUMFLEX     On Japanese keyboard
    DIK_AT             On Japanese keyboard
    DIK_COLON          On Japanese keyboard
    DIK_UNDERLINE      On Japanese keyboard
    DIK_KANJI          On Japanese keyboard
    DIK_STOP           On Japanese keyboard
    DIK_AX             On Japanese keyboard
    DIK_UNLABELED      On Japanese keyboard
    DIK_NUMPADENTER
    DIK_RCONTROL       Right ctrl key
    DIK_NUMPADCOMMA    comma on NEC PC98 numeric keypad
    DIK_DIVIDE         Forward slash on numeric keypad
    DIK_SYSRQ
    DIK_RMENU          Right alt key
    DIK_HOME
    DIK_UP             up arrow
    DIK_PRIOR          page up
    DIK_LEFT           left arrow
    DIK_RIGHT          right arrow
    DIK_END
    DIK_DOWN           down arrow
    DIK_NEXT           page down
    DIK_INSERT
    DIK_DELETE
    DIK_LWIN           Left Windows key
    DIK_RWIN           Right Windows key
    DIK_APPS           Application key
    DIK_PAUSE
    

    The following alternate names are available:

    
    Alternate name    Regular name    Note
    DIK_BACKSPACE     DIK_BACK        backspace
    DIK_NUMPADSTAR    DIK_MULTIPLY    * key on numeric keypad
    DIK_LALT          DIK_LMENU       Left alt
    DIK_CAPSLOCK      DIK_CAPITAL     capslock
    DIK_NUMPADMINUS   DIK__SUBTRACT   Minus key on numeric keypad
    DIK_NUMPADPLUS    DIK_ADD         Plus key on numeric keypad
    DIK_NUMPADPERIOD  DIK_DECIMAL     Period key on numeric keypad
    DIK_NUMPADSLASH   DIK__DIVIDE     Forward slash on numeric keypad
    DIK_RALT          DIK_RMENU       Right alt
    DIK_UPARROW       DIK_UP          On arrow keypad
    DIK_PGUP          DIK_PRIOR       On arrow keypad
    DIK_LEFTARROW     DIK_LEFT        On arrow keypad
    DIK_RIGHTARROW    DIK_RIGHT       On arrow keypad
    DIK_DOWNARROW     DIK_DOWN        On arrow keypad
    DIK_PGDN          DIK_NEXT        On arrow keypad
    
    

    Similarly, the mouse input relies on two functions I_GetMouse and I_ReacquireMouse. The I_GetMouse function is a bit more involved than I_GetKeyboard, it checks for a single-click (a pin click) and also constrains the mouse x and y coordinates by a bounding box of the screen (the code for this is commented and it's optional):

    
    void I_ReacquireMouse (void)
    {
        Log("reacquiring mouse...");
    
        if ((err = lpMouse->Acquire()) != DI_OK)
            Log("di_ok\n");
        else
            LogErr(dierrAcquire(err));
    }
    
    void I_GetMouse (void)
    {
        if ((err = lpMouse->GetDeviceState(sizeof(DIMOUSESTATE), (void*)&mse.state) != DI_OK))
            if ((err & DIERR_INPUTLOST) && (err & DIERR_NOTACQUIRED)) {
                // try to (re-)acquire the mouse interface
                I_ReacquireMouse();
            }
            else // ...some other error
               LogErr(dierrGetDeviceState(err));
    
        mse.x += mse.state.lX;
        mse.y += mse.state.lY;
    
        // to constrain mouse cursor position to the screen bounding box
        // uncomment the following lines and define screenWidth and screenHeight
        /*
        if (mse.x <= 0)
            mse.x = 0;
        if (mse.x > screenWidth)
            mse.x = screenwidth;
        if (mse.y <= 0)
            mse.y = 0;
        if (mse.y > screenHeight)
            mse.y = screenHeight; */
    
        mse.pin = 0;
    
        // has a single-click occured?
        if (mse.state.rgbButtons[0] && !mse.pinstate) {
            mse.pinstate = 1;
            mse.pin = 1;
        }
    
        // reset 'pin' state
        if (!mse.state.rgbButtons[0])
            mse.pinstate = 0;
    }
    
    

    The mse.pin variable works to identify a single left-button mouse click. The way it works is first you would call I_GetMouse. I_GetMouse checks if the left mouse button has been pressed. If that's the case, mse.pin is set to 1 and you can test this value in the remaining block of your code to identify a single "pin" click. mse.pin is set to 1 only for the next code execution "run". It is automatically reset to 0 again, even if you keep holding the left mouse button down. This functionality is very useful when developing a GUI system. Sometimes, you don't want the left mouse button to be constantly active.

    To conclude this tutorial, here is how you would get input by using the Direct Input library presented here:

    
    // somewhere during initialization; Initialize DI...
    I_Init();
    
    // during shutdown sequence; Shutdown DI interface...
    I_Shutdown();
    
    // during execution, get input and do something useful with it...
    I_Get();
    
    // draw the mouse pointer at the current mouse x,y location
    DrawCursor(mse.x, mse.y);
    
    if (mse.pin)
    {
        // a single click occured at [mse.x, mse.y]
    }
    
    if (key.state[DIK_LEFT])
    {
        // left arrow key is down; do something...
    }
    
    

    The source code contains files dierr.h, dierr.cpp, directinput.h, directinput.cpp.

    Did this article help you learn something new?

    I enjoy writing for the Internet audiences because my thoughts are instantly published to the entire world. My work consists of writing educational articles about making websites to help people learn. If you enjoyed reading this article, or learned something new, then I have succeeded.

    If the content on this website somehow helped you to learn something new, please let others know about it by sharing it on your website or on your Facebook page with your friends. In addition you can help by making a donation or bookmarking this site.