genesis-3d_engine/Engine/addons/input/xinput/xinputgamepad.cc

289 lines
10 KiB
C++
Raw Normal View History

/****************************************************************************
Copyright (c) 2007,Radon Labs GmbH
Copyright (c) 2011-2013,WebJet Business Division,CYOU
http://www.genesis-3d.com.cn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#if WIN32
#include "input/input_stdneb.h"
#include "input/xinput/xinputgamepad.h"
#include "framesync/framesynctimer.h"
namespace XInput
{
__ImplementClass(XInput::XInputGamePad, 'XIGP', Input::InputGamePadBase);
using namespace Math;
using namespace FrameSync;
//------------------------------------------------------------------------------
/**
*/
XInputGamePad::XInputGamePad() :
lastPacketNumber(0xffffffff),
lastCheckConnectedTime(0)
{
// empty
}
//------------------------------------------------------------------------------
/**
*/
XInputGamePad::~XInputGamePad()
{
// empty
}
//------------------------------------------------------------------------------
/**
*/
void
XInputGamePad::OnAttach(const GPtr<Input::InputServerBase>& inputServer)
{
InputGamePadBase::OnAttach(inputServer);
// start our timer, the timer is used to measure time since the last
// XInputGetState() for disconnected game pads, this is an expensive
// operation, thus we're only doing it every half a second or so
this->lastCheckConnectedTime = FrameSyncTimer::Instance()->GetTicks() - CheckConnectedInterval;
}
//------------------------------------------------------------------------------
/**
This compares the current state of the game pad against the
previous state and sets the internal state accordingly.
FIXME: Calling XInputGetState() on non-connected controllers is very
expensive, thus if XInputGetState return ERROR_DEVICE_NOT_CONNECTED, only
call XInputGetState() every 2 seconds to check if a device has actually
been connected!!!
*/
void
XInputGamePad::OnBeginFrame()
{
InputGamePadBase::OnBeginFrame();
// get current state of the game pad, this looks a bit complicated
// because disconnected game pads are only checked once in a while
// (getting state from a non-connected device is fairly expensive)
Timing::Tick curTime = FrameSyncTimer::Instance()->GetTicks();
XINPUT_STATE curState = { 0 };
DWORD result = ERROR_DEVICE_NOT_CONNECTED;
if (!this->isConnected)
{
// if we're not currently connected, only check every little while
if ((curTime - this->lastCheckConnectedTime) >= CheckConnectedInterval)
{
result = XInputGetState(this->playerIndex, &curState);
this->lastCheckConnectedTime = curTime;
}
}
else
{
// if we're currently connected, getting the current state is cheap
result = XInputGetState(this->playerIndex, &curState);
this->lastCheckConnectedTime = curTime;
}
// check result
if (ERROR_DEVICE_NOT_CONNECTED == result)
{
// game pad is currently not connected, if it just has been
// disconnected we need to reset the game pad
if (this->isConnected)
{
this->OnReset();
this->isConnected = false;
}
}
else if (ERROR_SUCCESS == result)
{
this->isConnected = true;
// check if state of controller has actually changed
if (curState.dwPacketNumber != this->lastPacketNumber)
{
this->lastPacketNumber = curState.dwPacketNumber;
// update button states
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_DPAD_UP, DPadUpButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_DPAD_DOWN, DPadDownButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_DPAD_LEFT, DPadLeftButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_DPAD_RIGHT, DPadRightButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_START, StartButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_BACK, BackButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_LEFT_THUMB, LeftThumbButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_RIGHT_THUMB, RightThumbButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_LEFT_SHOULDER, LeftShoulderButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_RIGHT_SHOULDER, RightShoulderButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_A, AButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_B, BButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_X, XButton);
this->UpdateButtonState(curState.Gamepad, XINPUT_GAMEPAD_Y, YButton);
// update axis states
this->UpdateTriggerAxis(curState.Gamepad, LeftTriggerAxis);
this->UpdateTriggerAxis(curState.Gamepad, RightTriggerAxis);
this->UpdateThumbAxis(curState.Gamepad, LeftThumbXAxis);
this->UpdateThumbAxis(curState.Gamepad, LeftThumbYAxis);
this->UpdateThumbAxis(curState.Gamepad, RightThumbXAxis);
this->UpdateThumbAxis(curState.Gamepad, RightThumbYAxis);
}
else
{
// reset button up state
IndexT btnIdx;
for (btnIdx = 0; btnIdx < this->buttonStates.Size(); ++btnIdx)
{
this->buttonStates[btnIdx].up = false;
this->buttonStates[btnIdx].down = false;
}
}
}
else
{
// can't happen?
n_error("XInputGamePad: can't happen (invalid return value from XInputGetState!)");
}
// if the vibrator settings have changed, update accordingly
if (this->isConnected && this->vibratorsDirty)
{
XINPUT_VIBRATION vib;
vib.wLeftMotorSpeed = (WORD) (this->lowFreqVibrator * 65535.0f);
vib.wRightMotorSpeed = (WORD) (this->highFreqVibrator * 65535.0f);
XInputSetState(this->playerIndex, &vib);
this->vibratorsDirty = false;
}
}
//------------------------------------------------------------------------------
/**
Compares the previous and current state of a game pad button
and updates the parent class' state accordingly.
*/
void
XInputGamePad::UpdateButtonState(const XINPUT_GAMEPAD& curState, WORD xiBtn, Button btn)
{
if (0 != (curState.wButtons & xiBtn))
{
// has button been down-pressed in this frame?
if (!this->buttonStates[btn].pressed)
{
this->buttonStates[btn].down = true;
}
else
{
this->buttonStates[btn].down = false;
}
this->buttonStates[btn].pressed = true;
this->buttonStates[btn].up = false;
}
else
{
// has button been released in this frame?
if (this->buttonStates[btn].pressed)
{
this->buttonStates[btn].up = true;
}
else
{
this->buttonStates[btn].up = false;
}
this->buttonStates[btn].pressed = false;
this->buttonStates[btn].down = false;
}
}
//------------------------------------------------------------------------------
/**
*/
void
XInputGamePad::UpdateTriggerAxis(const XINPUT_GAMEPAD& curState, Axis axis)
{
n_assert((axis == LeftTriggerAxis) || (axis == RightTriggerAxis));
BYTE rawValue = 0;
if (LeftTriggerAxis == axis)
{
rawValue = curState.bLeftTrigger;
}
else
{
rawValue = curState.bRightTrigger;
}
this->axisValues[axis] = n_max(0.0f, float(rawValue - XINPUT_GAMEPAD_TRIGGER_THRESHOLD)) / (255.0f - XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
}
//------------------------------------------------------------------------------
/**
*/
void
XInputGamePad::UpdateThumbAxis(const XINPUT_GAMEPAD& curState, Axis axis)
{
n_assert((axis == LeftThumbXAxis) || (axis == LeftThumbYAxis) || (axis == RightThumbXAxis) || (axis == RightThumbYAxis));
SHORT rawValue = 0;
SHORT deadZone = 0;
switch (axis)
{
case LeftThumbXAxis:
rawValue = curState.sThumbLX;
deadZone = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
break;
case LeftThumbYAxis:
rawValue = curState.sThumbLY;
deadZone = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
break;
case RightThumbXAxis:
rawValue = curState.sThumbRX;
deadZone = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
break;
case RightThumbYAxis:
rawValue = curState.sThumbRY;
deadZone = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
break;
}
float val = 0.0f;
if ((rawValue > deadZone) || (rawValue < -deadZone))
{
// outside dead zone, scale from -1.0f to +1.0f
if (rawValue > 0)
{
// in positive range
val = n_max(0.0f, float(rawValue - deadZone)) / (32767.0f - deadZone);
}
else
{
// in negative range
val = n_min(0.0f, float(rawValue + deadZone)) / (32768.0f - deadZone);
}
}
this->axisValues[axis] = val;
}
} // namespace XInput
#endif