Replace Joystick controls with SDL GameController

liquid_container
Lukas Werling 2016-02-13 19:41:35 +01:00
parent 9c840724f2
commit bd3f020068
6 changed files with 50 additions and 443 deletions

View File

@ -305,7 +305,7 @@ endif()
# SDL
if(USE_SDL_MAINLOOP)
find_package(SDL2 REQUIRED)
elseif(NOT WIN32)
else()
# for gamepads
find_package(SDL2)
endif()
@ -911,8 +911,6 @@ elseif(USE_WIN32_WINDOWS)
list(APPEND OC_GUI_SOURCES
src/editor/C4ConsoleWin32.cpp
src/platform/C4WindowWin32.cpp
src/platform/StdJoystick.cpp
src/platform/StdJoystick.h
)
elseif(USE_COCOA)
list(APPEND OC_GUI_SOURCES

View File

@ -162,11 +162,9 @@ void C4AbstractApp::HandleSDLEvent(SDL_Event& e)
SDL_GetMouseState(&x, &y);
C4GUI::MouseMove(C4MC_Button_Wheel, x, y, flags, NULL);
break;
case SDL_JOYAXISMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
Application.pGamePadControl->FeedEvent(e);
break;
}

View File

@ -32,148 +32,7 @@ void C4GamePadControl::DoAxisInput()
Execute(true);
}
#ifdef USE_WIN32_WINDOWS
C4GamePadControl *C4GamePadControl::pInstance = NULL;
C4GamePadControl::C4GamePadControl()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
{
Gamepads[i].pGamepad = NULL;
Gamepads[i].iRefCount = 0;
}
iNumGamepads = 0;
// singleton
if (!pInstance) pInstance = this;
}
C4GamePadControl::~C4GamePadControl()
{
if (pInstance == this) pInstance = NULL;
Clear();
}
void C4GamePadControl::Clear()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
while (Gamepads[i].iRefCount) CloseGamepad(i);
}
void C4GamePadControl::OpenGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// add gamepad ref
if (!(Gamepads[id].iRefCount++))
{
// this is the first gamepad opening: Init it
Pad &rPad = Gamepads[id];
rPad.pGamepad = new CStdGamePad(id);
rPad.Buttons= 0;
for (int i=0; i< CStdGamepad_MaxAxis; ++i)
{
rPad.AxisPosis[i] = CStdGamePad::Mid;
rPad.AxisStrengths[i] = 0;
}
rPad.pGamepad->SetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
++iNumGamepads;
}
}
void C4GamePadControl::CloseGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// del gamepad ref
if (!--Gamepads[id].iRefCount)
{
Gamepads[id].pGamepad->GetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
delete Gamepads[id].pGamepad; Gamepads[id].pGamepad = NULL;
--iNumGamepads;
}
}
int C4GamePadControl::GetGamePadCount()
{
JOYINFOEX joy;
ZeroMem(&joy, sizeof(JOYINFOEX)); joy.dwSize = sizeof(JOYINFOEX); joy.dwFlags = JOY_RETURNALL;
int iCnt=0;
while (iCnt<CStdGamepad_MaxGamePad && ::joyGetPosEx(iCnt, &joy) == JOYERR_NOERROR) ++iCnt;
return iCnt;
}
const int MaxGamePadButton=10;
void C4GamePadControl::Execute(bool send_axis_strength_changes)
{
// Get gamepad inputs
int iNum = iNumGamepads;
for (int idGamepad=0; iNum && idGamepad<CStdGamepad_MaxGamePad; ++idGamepad)
{
Pad &rPad = Gamepads[idGamepad];
if (!rPad.iRefCount) continue;
--iNum;
if (!rPad.pGamepad->Update()) continue;
for (int iAxis = 0; iAxis < CStdGamepad_MaxAxis; ++iAxis)
{
int32_t iStrength = 100;
CStdGamePad::AxisPos eAxisPos = rPad.pGamepad->GetAxisPos(iAxis, &iStrength), ePrevAxisPos = rPad.AxisPosis[iAxis];
int32_t iPrevStrength = rPad.AxisStrengths[iAxis];
// Evaluate changes and pass single controls
// this is a generic Gamepad-control: Create events
if (eAxisPos != ePrevAxisPos || (send_axis_strength_changes && Abs(iPrevStrength-iStrength) > AxisStrengthChangeThreshold))
{
rPad.AxisPosis[iAxis] = eAxisPos;
rPad.AxisStrengths[iAxis] = iStrength;
if (ePrevAxisPos != CStdGamePad::Mid && eAxisPos != ePrevAxisPos)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (ePrevAxisPos==CStdGamePad::High))), KEYEV_Up, false, false, false, false);
// it's tempting to send fRepeated here for eAxisPos == ePrevAxisPos, but it would cause the key to be ignored for sync controls
// might improve the check in sync controls so they accept repeated keys if strength is updated?
if (eAxisPos != CStdGamePad::Mid)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (eAxisPos==CStdGamePad::High))), KEYEV_Down, false, false, false, false, NULL, false, iStrength);
}
}
uint32_t Buttons = rPad.pGamepad->GetButtons();
uint32_t PrevButtons = rPad.Buttons;
if (Buttons != PrevButtons)
{
rPad.Buttons = Buttons;
for (int iButton = 0; iButton < MaxGamePadButton; ++iButton)
if ((Buttons & (1 << iButton)) != (PrevButtons & (1 << iButton)))
{
bool fRelease = ((Buttons & (1 << iButton)) == 0);
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Button(iButton)), fRelease ? KEYEV_Up : KEYEV_Down, false, false, false, false);
}
}
}
}
bool C4GamePadControl::AnyButtonDown()
{
return false;
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
assert(C4GamePadControl::pInstance);
this->iGamePad = iGamepad;
C4GamePadControl::pInstance->OpenGamepad(iGamePad);
}
C4GamePadOpener::~C4GamePadOpener()
{
if (C4GamePadControl::pInstance)
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
}
void C4GamePadOpener::SetGamePad(int iNewGamePad)
{
if (iNewGamePad == iGamePad) return;
assert(C4GamePadControl::pInstance);
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
C4GamePadControl::pInstance->OpenGamepad(iGamePad = iNewGamePad);
}
#elif defined(HAVE_SDL) && !defined(USE_CONSOLE)
#if defined(HAVE_SDL) && !defined(USE_CONSOLE)
#include <SDL.h>
@ -184,16 +43,15 @@ bool C4GamePadControl::AnyButtonDown()
C4GamePadControl::C4GamePadControl()
{
// FIXME: Port to SDL_INIT_GAMECONTROLLER
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) != 0)
LogF("SDL_InitSubSystem(SDL_INIT_JOYSTICK): %s", SDL_GetError());
SDL_JoystickEventState(SDL_ENABLE);
if (!SDL_NumJoysticks()) Log("No Gamepad found");
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS) != 0)
LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError());
SDL_GameControllerEventState(SDL_ENABLE);
if (!GetGamePadCount()) Log("No Gamepad found");
}
C4GamePadControl::~C4GamePadControl()
{
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS);
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
}
void C4GamePadControl::Execute(bool)
@ -204,11 +62,9 @@ void C4GamePadControl::Execute(bool)
{
switch (event.type)
{
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
FeedEvent(event);
break;
}
@ -234,55 +90,18 @@ void C4GamePadControl::FeedEvent(SDL_Event& event)
{
switch (event.type)
{
case SDL_JOYHATMOTION:
case SDL_CONTROLLERAXISMOTION:
{
SDL_Event fakeX;
fakeX.jaxis.type = SDL_JOYAXISMOTION;
fakeX.jaxis.which = event.jhat.which;
fakeX.jaxis.axis = event.jhat.hat * 2 + 6; /* *magic*number* */
fakeX.jaxis.value = 0;
SDL_Event fakeY = fakeX;
fakeY.jaxis.axis += 1;
switch (event.jhat.value)
{
case SDL_HAT_LEFTUP: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFT: fakeX.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFTDOWN: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_UP: fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_DOWN: fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTUP: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_RIGHT: fakeX.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTDOWN: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(+1); break;
}
FeedEvent(fakeX);
FeedEvent(fakeY);
return;
}
case SDL_JOYBALLMOTION:
{
SDL_Event fake;
fake.jaxis.type = SDL_JOYAXISMOTION;
fake.jaxis.which = event.jball.which;
fake.jaxis.axis = event.jball.ball * 2 + 12; /* *magic*number* */
fake.jaxis.value = amplify(event.jball.xrel);
FeedEvent(event);
fake.jaxis.axis += 1;
fake.jaxis.value = amplify(event.jball.yrel);
FeedEvent(event);
return;
}
case SDL_JOYAXISMOTION:
{
C4KeyCode minCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, false));
C4KeyCode maxCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, true));
C4KeyCode minCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, false));
C4KeyCode maxCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, true));
// FIXME: This assumes that the axis really rests around (0, 0) if it is not used, which is not always true.
if (event.jaxis.value < -deadZone)
if (event.caxis.value < -deadZone)
{
if (PressedAxis.count(minCode) == 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEY_Gamepad(event.caxis.which, minCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(minCode);
}
@ -292,17 +111,17 @@ void C4GamePadControl::FeedEvent(SDL_Event& event)
if (PressedAxis.count(minCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEY_Gamepad(event.caxis.which, minCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(minCode);
}
}
if (event.jaxis.value > +deadZone)
if (event.caxis.value > +deadZone)
{
if (PressedAxis.count(maxCode) == 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEY_Gamepad(event.caxis.which, maxCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(maxCode);
}
@ -312,21 +131,21 @@ void C4GamePadControl::FeedEvent(SDL_Event& event)
if (PressedAxis.count(maxCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEY_Gamepad(event.caxis.which, maxCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(maxCode);
}
}
break;
}
case SDL_JOYBUTTONDOWN:
case SDL_CONTROLLERBUTTONDOWN:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)),
KEYEV_Down, false, false, false, false);
break;
case SDL_JOYBUTTONUP:
case SDL_CONTROLLERBUTTONUP:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)),
KEYEV_Up, false, false, false, false);
break;
}
@ -334,27 +153,37 @@ void C4GamePadControl::FeedEvent(SDL_Event& event)
int C4GamePadControl::GetGamePadCount()
{
return(SDL_NumJoysticks());
// Not all Joysticks are game controllers.
int count = 0;
for (int i = 0; i < SDL_NumJoysticks(); i++)
if (SDL_IsGameController(i))
count++;
return count;
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy) LogF("SDL: %s", SDL_GetError());
int n = iGamepad;
for (int i = 0; i < SDL_NumJoysticks(); i++)
if (SDL_IsGameController(i) && n-- == 0)
{
controller = SDL_GameControllerOpen(i);
if (!controller) LogF("SDL: %s", SDL_GetError());
break;
}
if (!controller) LogF("Gamepad %d not available", iGamepad);
}
C4GamePadOpener::~C4GamePadOpener()
{
if (Joy) SDL_JoystickClose(Joy);
if (controller) SDL_GameControllerClose(controller);
}
void C4GamePadOpener::SetGamePad(int iGamepad)
{
if (Joy)
SDL_JoystickClose(Joy);
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy)
LogF("SDL: %s", SDL_GetError());
// TODO: why do we need this?
LogF("SetGamePad: Not implemented yet");
}
#else
@ -371,4 +200,4 @@ C4GamePadOpener::C4GamePadOpener(int iGamepad) { }
C4GamePadOpener::~C4GamePadOpener() {}
void C4GamePadOpener::SetGamePad(int iGamepad) { }
#endif //_WIN32
#endif

View File

@ -20,43 +20,20 @@
#ifndef INC_C4GamePadCon
#define INC_C4GamePadCon
#ifdef USE_WIN32_WINDOWS
#include <StdJoystick.h>
#endif
#ifdef HAVE_SDL
#include <C4KeyboardInput.h>
#include <set>
#endif
struct _SDL_Joystick;
typedef struct _SDL_Joystick SDL_Joystick;
struct _SDL_GameController;
typedef struct _SDL_GameController SDL_GameController;
union SDL_Event;
typedef union SDL_Event SDL_Event;
class C4GamePadControl
{
#ifdef USE_WIN32_WINDOWS
private:
struct Pad
{
CStdGamePad *pGamepad;
int iRefCount;
uint32_t Buttons;
CStdGamePad::AxisPos AxisPosis[CStdGamepad_MaxAxis];
int32_t AxisStrengths[CStdGamepad_MaxAxis];
};
Pad Gamepads[CStdGamepad_MaxGamePad];
int iNumGamepads;
enum { AxisStrengthChangeThreshold = 2 }; // if axis strength change > this value, a new control is issued
public:
void OpenGamepad(int id); // add gamepad ref
void CloseGamepad(int id); // del gamepad ref
static C4GamePadControl *pInstance; // singleton
#elif defined(HAVE_SDL)
#ifdef HAVE_SDL
public:
void FeedEvent(SDL_Event& e);
private:
@ -74,16 +51,12 @@ public:
class C4GamePadOpener
{
#ifdef USE_WIN32_WINDOWS
int iGamePad;
int GetGamePadIndex() const { return iGamePad; }
#endif
public:
C4GamePadOpener(int iGamePad);
~C4GamePadOpener();
void SetGamePad(int iNewGamePad);
#ifdef HAVE_SDL
SDL_Joystick *Joy;
SDL_GameController *controller;
#endif
};

View File

@ -1,135 +0,0 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2010-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Simple joystick handling with DirectInput 1 */
#include "C4Include.h"
#include <StdJoystick.h>
#include <C4windowswrapper.h>
#include <windowsx.h>
uint32_t POV2Position(DWORD dwPOV, bool fVertical)
{
// POV value is a 360° angle multiplied by 100
double dAxis;
// Centered
if (dwPOV == JOY_POVCENTERED)
dAxis = 0.0;
// Angle: convert to linear value -100 to +100
else
dAxis = (fVertical ? -cos((dwPOV/100) * M_PI / 180.0) : sin((dwPOV/100) * M_PI / 180.0)) * 100.0;
// Gamepad configuration wants unsigned and gets 0 to 200
return (uint32_t) (dAxis + 100.0);
}
CStdGamePad::CStdGamePad(int id) : id(id)
{
ResetCalibration();
}
void CStdGamePad::ResetCalibration()
{
// no calibration yet
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
fAxisCalibrated[i]=false;
}
}
void CStdGamePad::SetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated)
{
// params to calibration
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
dwAxisMin[i] = pdwAxisMin[i];
dwAxisMax[i] = pdwAxisMax[i];
fAxisCalibrated[i] = pfAxisCalibrated[i];
}
}
void CStdGamePad::GetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated)
{
// calibration to params
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
pdwAxisMin[i] = dwAxisMin[i];
pdwAxisMax[i] = dwAxisMax[i];
pfAxisCalibrated[i] = fAxisCalibrated[i];
}
}
bool CStdGamePad::Update()
{
joynfo.dwSize=sizeof(joynfo);
joynfo.dwFlags=JOY_RETURNBUTTONS | JOY_RETURNRAWDATA | JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | JOY_RETURNPOV;
return joyGetPosEx(JOYSTICKID1+id,&joynfo) == JOYERR_NOERROR;
}
uint32_t CStdGamePad::GetButtons()
{
return joynfo.dwButtons;
}
CStdGamePad::AxisPos CStdGamePad::GetAxisPos(int idAxis, int32_t *out_strength)
{
if (out_strength) *out_strength = 0; // default no strength
if (idAxis<0 || idAxis>=CStdGamepad_MaxAxis) return Mid; // wrong axis
// get raw axis data
if (idAxis<CStdGamepad_MaxCalAxis)
{
uint32_t dwPos = (&joynfo.dwXpos)[idAxis];
// evaluate axis calibration
if (fAxisCalibrated[idAxis])
{
// update it
dwAxisMin[idAxis] = std::min(dwAxisMin[idAxis], dwPos);
dwAxisMax[idAxis] = std::max(dwAxisMax[idAxis], dwPos);
// Calculate center
DWORD dwCenter = (dwAxisMin[idAxis] + dwAxisMax[idAxis]) / 2;
// Axis strength
DWORD dwRange = (dwAxisMax[idAxis] - dwCenter);
// Trigger range is 20% off center
DWORD dwThresh = dwRange / 5;
if (dwPos < dwCenter - dwThresh)
{
if (out_strength && dwRange) *out_strength = (dwCenter-dwPos)*100/dwRange;
return Low;
}
if (dwPos > dwCenter + dwThresh)
{
if (out_strength && dwRange) *out_strength = (dwPos-dwCenter)*100/dwRange;
return High;
}
}
else
{
// init it
dwAxisMin[idAxis] = dwAxisMax[idAxis] = dwPos;
fAxisCalibrated[idAxis] = true;
}
}
else
{
// It's a POV head
DWORD dwPos = POV2Position(joynfo.dwPOV, idAxis==PAD_Axis_POVy);
if (out_strength) *out_strength = Abs(int32_t(dwPos) - 100);
if (dwPos > 130) return High; else if (dwPos < 70) return Low;
}
return Mid;
}

View File

@ -1,56 +0,0 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Simple joystick handling with DirectInput 1 */
#ifndef INC_StdJoystick
#define INC_StdJoystick
#include <C4windowswrapper.h>
#include <mmsystem.h>
const int32_t PAD_Axis_POVx = 6;
const int32_t PAD_Axis_POVy = 7; // virtual axises of the coolie hat
const int CStdGamepad_MaxGamePad = 15, // maximum number of supported gamepads
CStdGamepad_MaxCalAxis = 6, // maximum number of calibrated axises
CStdGamepad_MaxAxis = 8; // number of axises plus coolie hat axises
class CStdGamePad
{
public:
enum AxisPos { Low, Mid, High, }; // quantized axis positions
private:
int id; // gamepad number
JOYINFOEX joynfo; // WIN32 gamepad info
public:
uint32_t dwAxisMin[CStdGamepad_MaxCalAxis], dwAxisMax[CStdGamepad_MaxCalAxis]; // axis ranges - auto calibrated
bool fAxisCalibrated[CStdGamepad_MaxCalAxis]; // set if an initial value for axis borders has been determined already
CStdGamePad(int id); // ctor
void ResetCalibration(); // resets axis min and max
void SetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated);
void GetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated);
bool Update(); // read current gamepad data
uint32_t GetButtons(); // returns bitmask of pressed buttons for last retrieved info
AxisPos GetAxisPos(int idAxis, int32_t *out_strength=NULL); // return axis extension - mid for error or center position
};
#endif