Shipwright/libultraship/libultraship/SDLController.cpp

481 lines
20 KiB
C++

#include "SDLController.h"
#include "spdlog/spdlog.h"
#include "Window.h"
#include <Utils/StringHelper.h>
#ifdef _MSC_VER
#define strdup _strdup
#endif
extern "C" uint8_t __osMaxControllers;
namespace Ship {
SDLController::SDLController(int32_t physicalSlot) : Controller(), Cont(nullptr), physicalSlot(physicalSlot) {
}
bool SDLController::Open() {
const auto NewCont = SDL_GameControllerOpen(physicalSlot);
// We failed to load the controller. Go to next.
if (NewCont == nullptr) {
SPDLOG_ERROR("SDL Controller failed to open: ({})", SDL_GetError());
return false;
}
supportsGyro = false;
if (SDL_GameControllerHasSensor(NewCont, SDL_SENSOR_GYRO)) {
SDL_GameControllerSetSensorEnabled(NewCont, SDL_SENSOR_GYRO, SDL_TRUE);
supportsGyro = true;
}
char GuidBuf[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(physicalSlot), GuidBuf, sizeof(GuidBuf));
Cont = NewCont;
#ifdef __SWITCH__
GUID = StringHelper::Sprintf("%s:%d", GuidBuf, physicalSlot);
ControllerName = StringHelper::Sprintf("%s #%d", SDL_GameControllerNameForIndex(physicalSlot), physicalSlot + 1);
#else
GUID = std::string(GuidBuf);
ControllerName = std::string(SDL_GameControllerNameForIndex(physicalSlot));
#endif
return true;
}
bool SDLController::Close() {
if (Cont != nullptr && SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
if (CanRumble()) {
SDL_GameControllerRumble(Cont, 0, 0, 0);
}
SDL_GameControllerClose(Cont);
}
Cont = nullptr;
return true;
}
void SDLController::NormalizeStickAxis(SDL_GameControllerAxis axisX, SDL_GameControllerAxis axisY, int16_t axisThreshold, int32_t virtualSlot) {
auto profile = getProfile(virtualSlot);
const auto axisValueX = SDL_GameControllerGetAxis(Cont, axisX);
const auto axisValueY = SDL_GameControllerGetAxis(Cont, axisY);
//scale {-32768 ... +32767} to {-84 ... +84}
auto ax = axisValueX * 85.0f / 32767.0f;
auto ay = axisValueY * 85.0f / 32767.0f;
//create scaled circular dead-zone in range {-15 ... +15}
auto len = sqrt(ax * ax + ay * ay);
if (len < axisThreshold) {
len = 0.0f;
}
else if (len > 85.0) {
len = 85.0f / len;
}
else {
len = (len - axisThreshold) * 85.0f / (85.0f - axisThreshold) / len;
}
ax *= len;
ay *= len;
//bound diagonals to an octagonal range {-68 ... +68}
if (ax != 0.0f && ay != 0.0f) {
auto slope = ay / ax;
auto edgex = copysign(85.0f / (abs(slope) + 16.0f / 69.0f), ax);
auto edgey = copysign(std::min(abs(edgex * slope), 85.0f / (1.0f / abs(slope) + 16.0f / 69.0f)), ay);
edgex = edgey / slope;
auto scale = sqrt(edgex * edgex + edgey * edgey) / 85.0f;
ax *= scale;
ay *= scale;
}
if (axisX == SDL_CONTROLLER_AXIS_LEFTX) {
getLeftStickX(virtualSlot) = +ax;
getLeftStickY(virtualSlot) = -ay;
} else if (axisX == SDL_CONTROLLER_AXIS_RIGHTX) {
getRightStickX(virtualSlot) = +ax;
getRightStickY(virtualSlot) = -ay;
}
}
int32_t SDLController::ReadRawPress() {
SDL_GameControllerUpdate();
for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
if (SDL_GameControllerGetButton(Cont, static_cast<SDL_GameControllerButton>(i))) {
return i;
}
}
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
const auto Axis = static_cast<SDL_GameControllerAxis>(i);
const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis) / 32767.0f;
if (AxisValue < -0.7f) {
return -(Axis + AXIS_SCANCODE_BIT);
}
if (AxisValue > 0.7f) {
return (Axis + AXIS_SCANCODE_BIT);
}
}
return -1;
}
void SDLController::ReadFromSource(int32_t virtualSlot) {
auto profile = getProfile(virtualSlot);
SDL_GameControllerUpdate();
// If the controller is disconnected, close it.
if (Cont != nullptr && !SDL_GameControllerGetAttached(Cont)) {
Close();
}
// Attempt to load the controller if it's not loaded
if (Cont == nullptr) {
// If we failed to load the controller, don't process it.
if (!Open()) {
return;
}
}
if (supportsGyro && profile->UseGyro) {
float gyroData[3];
SDL_GameControllerGetSensorData(Cont, SDL_SENSOR_GYRO, gyroData, 3);
float gyro_drift_x = profile->GyroData[DRIFT_X] / 100.0f;
float gyro_drift_y = profile->GyroData[DRIFT_Y] / 100.0f;
const float gyro_sensitivity = profile->GyroData[GYRO_SENSITIVITY];
if (gyro_drift_x == 0) {
gyro_drift_x = gyroData[0];
}
if (gyro_drift_y == 0) {
gyro_drift_y = gyroData[1];
}
profile->GyroData[DRIFT_X] = gyro_drift_x * 100.0f;
profile->GyroData[DRIFT_Y] = gyro_drift_y * 100.0f;
getGyroX(virtualSlot) = gyroData[0] - gyro_drift_x;
getGyroY(virtualSlot) = gyroData[1] - gyro_drift_y;
getGyroX(virtualSlot) *= gyro_sensitivity;
getGyroY(virtualSlot) *= gyro_sensitivity;
}
else {
getGyroX(virtualSlot) = 0;
getGyroY(virtualSlot) = 0;
}
getPressedButtons(virtualSlot) = 0;
for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
if (profile->Mappings.contains(i)) {
if (SDL_GameControllerGetButton(Cont, static_cast<SDL_GameControllerButton>(i))) {
getPressedButtons(virtualSlot) |= profile->Mappings[i];
}
else {
getPressedButtons(virtualSlot) &= ~profile->Mappings[i];
}
}
}
SDL_GameControllerAxis LStickAxisX = SDL_CONTROLLER_AXIS_INVALID;
SDL_GameControllerAxis LStickAxisY = SDL_CONTROLLER_AXIS_INVALID;
int32_t LStickDeadzone = 0;
SDL_GameControllerAxis RStickAxisX = SDL_CONTROLLER_AXIS_INVALID;
SDL_GameControllerAxis RStickAxisY = SDL_CONTROLLER_AXIS_INVALID;
int32_t RStickDeadzone = 0;
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
const auto Axis = static_cast<SDL_GameControllerAxis>(i);
const auto PosScancode = i | AXIS_SCANCODE_BIT;
const auto NegScancode = -PosScancode;
const auto AxisDeadzone = profile->AxisDeadzones[i];
const auto AxisMinimumPress = profile->AxisMinimumPress[i];
const auto PosButton = profile->Mappings[PosScancode];
const auto NegButton = profile->Mappings[NegScancode];
const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis);
#ifdef TARGET_WEB
// Firefox has a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1606562
// It sets down y to 32768.0f / 32767.0f, which is greater than the allowed 1.0f,
// which SDL then converts to a int16_t by multiplying by 32767.0f, which overflows into -32768.
// Maximum up will hence never become -32768 with the current version of SDL2,
// so this workaround should be safe in compliant browsers.
if (AxisValue == -32768) {
AxisValue = 32767;
}
#endif
if (!(
PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT ||
PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN ||
NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT ||
NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN ||
PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT ||
PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN ||
NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT ||
NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN)) {
// The axis is being treated as a "button"
if (AxisValue > AxisMinimumPress) {
getPressedButtons(virtualSlot) |= PosButton;
getPressedButtons(virtualSlot) &= ~NegButton;
}
else if (AxisValue < -AxisMinimumPress) {
getPressedButtons(virtualSlot) &= ~PosButton;
getPressedButtons(virtualSlot) |= NegButton;
}
else {
getPressedButtons(virtualSlot) &= ~PosButton;
getPressedButtons(virtualSlot) &= ~NegButton;
}
} else {
// The axis is being treated as a "stick"
// Left stick
if (PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT) {
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) {
SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", LStickAxisX, Axis);
}
if (LStickDeadzone != 0 && LStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", LStickDeadzone, AxisDeadzone);
}
LStickDeadzone = AxisDeadzone;
LStickAxisX = Axis;
}
if (PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN) {
if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) {
SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", LStickAxisY, Axis);
}
if (LStickDeadzone != 0 && LStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisDeadzone);
}
LStickDeadzone = AxisDeadzone;
LStickAxisY = Axis;
}
if (NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT) {
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) {
SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", LStickAxisX, Axis);
}
if (LStickDeadzone != 0 && LStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisDeadzone);
}
LStickDeadzone = AxisDeadzone;
LStickAxisX = Axis;
}
if (NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN) {
if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) {
SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", LStickAxisY, Axis);
}
if (LStickDeadzone != 0 && LStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisDeadzone);
}
LStickDeadzone = AxisDeadzone;
LStickAxisY = Axis;
}
// Right Stick
if (PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT) {
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) {
SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", RStickAxisX, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", RStickDeadzone, AxisDeadzone);
}
RStickDeadzone = AxisDeadzone;
RStickAxisX = Axis;
}
if (PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN) {
if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) {
SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", RStickAxisY, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisDeadzone);
}
RStickDeadzone = AxisDeadzone;
RStickAxisY = Axis;
}
if (NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT) {
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) {
SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", RStickAxisX, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisDeadzone);
}
RStickDeadzone = AxisDeadzone;
RStickAxisX = Axis;
}
if (NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN) {
if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) {
SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", RStickAxisY, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisDeadzone) {
SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisDeadzone);
}
RStickDeadzone = AxisDeadzone;
RStickAxisY = Axis;
}
}
}
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != SDL_CONTROLLER_AXIS_INVALID) {
NormalizeStickAxis(LStickAxisX, LStickAxisY, LStickDeadzone, virtualSlot);
}
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != SDL_CONTROLLER_AXIS_INVALID) {
NormalizeStickAxis(RStickAxisX, RStickAxisY, RStickDeadzone, virtualSlot);
}
}
void SDLController::WriteToSource(int32_t virtualSlot, ControllerCallback* controller)
{
if (CanRumble() && getProfile(virtualSlot)->UseRumble) {
if (controller->rumble > 0) {
float rumble_strength = getProfile(virtualSlot)->RumbleStrength;
SDL_GameControllerRumble(Cont, 0xFFFF * rumble_strength, 0xFFFF * rumble_strength, 0);
}
else {
SDL_GameControllerRumble(Cont, 0, 0, 0);
}
}
if (SDL_GameControllerHasLED(Cont)) {
switch (controller->ledColor) {
case 0:
SDL_JoystickSetLED(SDL_GameControllerGetJoystick(Cont), 255, 0, 0);
break;
case 1:
SDL_JoystickSetLED(SDL_GameControllerGetJoystick(Cont), 0x1E, 0x69, 0x1B);
break;
case 2:
SDL_JoystickSetLED(SDL_GameControllerGetJoystick(Cont), 0x64, 0x14, 0x00);
break;
case 3:
SDL_JoystickSetLED(SDL_GameControllerGetJoystick(Cont), 0x00, 0x3C, 0x64);
break;
}
}
}
const std::string SDLController::GetButtonName(int32_t virtualSlot, int32_t n64Button) {
char buffer[50];
std::map<int32_t, int32_t>& Mappings = getProfile(virtualSlot)->Mappings;
const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair<int32_t, int32_t>& pair) {
return pair.second == n64Button;
});
if (find == Mappings.end()) return "Unknown";
int btn = abs(find->first);
if(btn >= AXIS_SCANCODE_BIT) {
btn -= AXIS_SCANCODE_BIT;
snprintf(buffer, sizeof(buffer), "%s%s", AxisNames[btn], find->first > 0 ? "+" : "-");
return buffer;
}
snprintf(buffer, sizeof(buffer), "Button %d", btn);
return buffer;
}
const std::string SDLController::GetControllerName() {
return ControllerName;
}
void SDLController::CreateDefaultBinding(int32_t virtualSlot) {
auto profile = getProfile(virtualSlot);
profile->Mappings.clear();
profile->AxisDeadzones.clear();
profile->AxisMinimumPress.clear();
profile->GyroData.clear();
profile->Version = DEVICE_PROFILE_CURRENT_VERSION;
profile->UseRumble = true;
profile->RumbleStrength = 1.0f;
profile->UseGyro = false;
profile->Mappings[SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT] = BTN_CRIGHT;
profile->Mappings[-(SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT)] = BTN_CLEFT;
profile->Mappings[SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT] = BTN_CDOWN;
profile->Mappings[-(SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT)] = BTN_CUP;
profile->Mappings[SDL_CONTROLLER_AXIS_LEFTX | AXIS_SCANCODE_BIT] = BTN_STICKRIGHT;
profile->Mappings[-(SDL_CONTROLLER_AXIS_LEFTX | AXIS_SCANCODE_BIT)] = BTN_STICKLEFT;
profile->Mappings[SDL_CONTROLLER_AXIS_LEFTY | AXIS_SCANCODE_BIT] = BTN_STICKDOWN;
profile->Mappings[-(SDL_CONTROLLER_AXIS_LEFTY | AXIS_SCANCODE_BIT)] = BTN_STICKUP;
profile->Mappings[SDL_CONTROLLER_AXIS_TRIGGERRIGHT | AXIS_SCANCODE_BIT] = BTN_R;
profile->Mappings[SDL_CONTROLLER_AXIS_TRIGGERLEFT | AXIS_SCANCODE_BIT] = BTN_Z;
profile->Mappings[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = BTN_L;
profile->Mappings[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = BTN_DRIGHT;
profile->Mappings[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = BTN_DLEFT;
profile->Mappings[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = BTN_DDOWN;
profile->Mappings[SDL_CONTROLLER_BUTTON_DPAD_UP] = BTN_DUP;
profile->Mappings[SDL_CONTROLLER_BUTTON_START] = BTN_START;
profile->Mappings[SDL_CONTROLLER_BUTTON_B] = BTN_B;
profile->Mappings[SDL_CONTROLLER_BUTTON_A] = BTN_A;
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
profile->AxisDeadzones[i] = 16.0f;
profile->AxisMinimumPress[i] = 7680.0f;
}
profile->GyroData[DRIFT_X] = 0.0f;
profile->GyroData[DRIFT_Y] = 0.0f;
profile->GyroData[GYRO_SENSITIVITY] = 1.0f;
}
bool SDLController::Connected() const {
return Cont != nullptr;
}
bool SDLController::CanGyro() const {
return supportsGyro;
}
bool SDLController::CanRumble() const {
#if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2,0,18)
return SDL_GameControllerHasRumble(Cont);
#endif
return false;
}
void SDLController::ClearRawPress() {
}
}