idk_me/src/engine/win32/XInputSystem.cpp
2024-05-07 00:33:19 +08:00

176 lines
5.9 KiB
C++

#include "pch.h"
#include "XInputSystem.h"
#include <idk.h>
#include <Windows.h>
#include <Xinput.h>
#include <core/Core.h>
#undef max
namespace idk::win
{
// number of seconds between each check for new player
constexpr static auto check_for_new_connection_delay = 3.0f;
void XInputSystem::Init()
{
ZeroMemory(&_buffers, sizeof(_buffers));
DWORD dwResult;
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++)
{
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
// Simply get the state of the controller from XInput.
dwResult = XInputGetState(i, &state);
if (dwResult == ERROR_SUCCESS)
{
// Controller is connected
_curr_buf()[i] = _prev_buf()[i] = _process_state(state);
_connected_users |= 1 << i;
}
else
{
// Controller is not connected
_connected_users &= ~(1 << i);
_curr_buf()[i] = _prev_buf()[i] = x_gamepad_state{ 0, 0, 0, 0, 0, 0, 0 };
}
}
_new_connection_check_timer = check_for_new_connection_delay;
}
void XInputSystem::Shutdown()
{
}
void XInputSystem::Update()
{
_swap_bufs();
_new_connection_check_timer -= Core::GetRealDT().count();
const bool check_for_new_connection = _new_connection_check_timer <= 0;
if (check_for_new_connection)
_new_connection_check_timer = check_for_new_connection_delay;
DWORD dwResult;
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++)
{
if (!check_for_new_connection)
{
if ((_connected_users & (1 << i)) == 0)
continue;
}
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
// Simply get the state of the controller from XInput.
dwResult = XInputGetState(i, &state);
if (dwResult == ERROR_SUCCESS)
{
// Controller is connected
_curr_buf()[i] = _process_state(state);
_connected_users |= 1 << i;
}
else
{
// Controller is not connected
_connected_users &= ~(1 << i);
_curr_buf()[i] = _prev_buf()[i] = x_gamepad_state{ 0, 0, 0, 0, 0, 0, 0 };
}
}
}
bool XInputSystem::GetButtonDown(char player, GamepadButton button) const
{
return (_prev_buf()[player].buttons & static_cast<unsigned short>(button)) == 0 &&
(_curr_buf()[player].buttons & static_cast<unsigned short>(button)) != 0;
}
bool XInputSystem::GetButtonUp(char player, GamepadButton button) const
{
return (_prev_buf()[player].buttons & static_cast<unsigned short>(button)) != 0 &&
(_curr_buf()[player].buttons & static_cast<unsigned short>(button)) == 0;
}
bool XInputSystem::GetButton(char player, GamepadButton button) const
{
return (_curr_buf()[player].buttons & static_cast<unsigned short>(button)) != 0;
}
float XInputSystem::GetAxis(char player, GamepadAxis axis) const
{
switch (axis)
{
case GamepadAxis::LeftStickX: return _curr_buf()[player].thumb_left_x;
case GamepadAxis::LeftStickY: return _curr_buf()[player].thumb_left_y;
case GamepadAxis::RightStickX: return _curr_buf()[player].thumb_right_x;
case GamepadAxis::RightStickY: return _curr_buf()[player].thumb_right_y;
case GamepadAxis::LeftTrigger: return _curr_buf()[player].left_trigger;
case GamepadAxis::RightTrigger: return _curr_buf()[player].right_trigger;
default: return 0;
}
}
void XInputSystem::SetRumble(char player, float low_freq, float high_freq)
{
if ((_connected_users & (1 << player)) == 0)
return;
XINPUT_VIBRATION vibration;
ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
vibration.wLeftMotorSpeed = static_cast<WORD>(std::clamp(low_freq, 0.0f, 1.0f) * 0xffff);
vibration.wRightMotorSpeed = static_cast<WORD>(std::clamp(high_freq, 0.0f, 1.0f) * 0xffff);
XInputSetState(player, &vibration);
}
char XInputSystem::GetConnectedPlayers() const
{
return _connected_users;
}
static vec2 thumbstick_deadzone_filter(short x, short y, short deadzone)
{
vec2 ret{ 0, 0 };
float mag = sqrtf(static_cast<float>(x) * x + static_cast<float>(y) * y);
//check if the controller is outside a circular dead zone
if (mag > deadzone)
{
if (mag > SHRT_MAX) mag = SHRT_MAX;
mag -= deadzone;
float scale = mag / (SHRT_MAX - deadzone);
ret.x = std::max((static_cast<float>(x) / SHRT_MAX) * scale, -1.0f);
ret.y = std::max((static_cast<float>(y) / SHRT_MAX) * scale, -1.0f);
}
return ret;
}
static float trigger_deadzone_filter(unsigned char val)
{
if (val > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
return std::max(static_cast<float>(val - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) / (UCHAR_MAX - XINPUT_GAMEPAD_TRIGGER_THRESHOLD), -1.0f);
else
return 0;
}
XInputSystem::x_gamepad_state XInputSystem::_process_state(const _XINPUT_STATE& state)
{
x_gamepad_state ret;
ret.buttons = state.Gamepad.wButtons;
ret.left_trigger = trigger_deadzone_filter(state.Gamepad.bLeftTrigger);
ret.right_trigger = trigger_deadzone_filter(state.Gamepad.bRightTrigger);
reinterpret_cast<vec2&>(ret.thumb_left_x) = thumbstick_deadzone_filter(
state.Gamepad.sThumbLX, state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
reinterpret_cast<vec2&>(ret.thumb_right_x) = thumbstick_deadzone_filter(
state.Gamepad.sThumbRX, state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
return ret;
}
}