diff --git a/Source/RMG-Input/CMakeLists.txt b/Source/RMG-Input/CMakeLists.txt index 849f2a63..af649e8e 100644 --- a/Source/RMG-Input/CMakeLists.txt +++ b/Source/RMG-Input/CMakeLists.txt @@ -27,6 +27,7 @@ set(RMG_INPUT_SOURCES Utilities/QtKeyToSdl2Key.cpp Utilities/InputDevice.cpp Thread/SDLThread.cpp + common.cpp main.cpp ) diff --git a/Source/RMG-Input/UserInterface/Widget/ControllerImageWidget.cpp b/Source/RMG-Input/UserInterface/Widget/ControllerImageWidget.cpp index a074feda..c4f022af 100644 --- a/Source/RMG-Input/UserInterface/Widget/ControllerImageWidget.cpp +++ b/Source/RMG-Input/UserInterface/Widget/ControllerImageWidget.cpp @@ -170,23 +170,18 @@ void ControllerImageWidget::paintEvent(QPaintEvent *event) const double maxOffsetx = maxOffsety; const double dist = sqrt(pow(this->xAxisState, 2) + pow(this->yAxisState, 2)); - // take deadzone into account - if (dist > this->deadzoneValue) - { - offsetx = (maxOffsetx / 100 * this->xAxisState); - offsety = (maxOffsety / 100 * this->yAxisState); - } + int octagonX = 0, octagonY = 0; + simulateOctagon( + this->xAxisState / 100.0, // inputX + this->yAxisState / 100.0, // inputY + this->deadzoneValue / 100.0, // deadzoneFactor + this->rangeValue / 100.0, // scalingFactor + octagonX, // outputX + octagonY // outputY + ); - // take circle range into account - if (dist > 100) - { - offsetx = (maxOffsetx / 100 * (this->xAxisState / dist * 100)); - offsety = (maxOffsety / 100 * (this->yAxisState / dist * 100)); - } - - // take range into account - offsetx = offsetx / 100 * this->rangeValue; - offsety = offsety / 100 * this->rangeValue; + offsetx = octagonX * (maxOffsetx / N64_AXIS_PEAK); + offsety = octagonY * (maxOffsety / N64_AXIS_PEAK); // adjust rect with offset rectF.adjust( diff --git a/Source/RMG-Input/UserInterface/Widget/ControllerWidget.cpp b/Source/RMG-Input/UserInterface/Widget/ControllerWidget.cpp index 2606a553..38156bb5 100644 --- a/Source/RMG-Input/UserInterface/Widget/ControllerWidget.cpp +++ b/Source/RMG-Input/UserInterface/Widget/ControllerWidget.cpp @@ -923,7 +923,7 @@ void ControllerWidget::SaveDefaultSettings() CoreSettingsSetValue(SettingsID::Input_DeviceName, section, std::string("Keyboard")); CoreSettingsSetValue(SettingsID::Input_DeviceNum, section, -1); CoreSettingsSetValue(SettingsID::Input_Range, section, 100); - CoreSettingsSetValue(SettingsID::Input_Deadzone, section, 15); + CoreSettingsSetValue(SettingsID::Input_Deadzone, section, 9); CoreSettingsSetValue(SettingsID::Input_Pak, section, 0); CoreSettingsSetValue(SettingsID::Input_RemoveDuplicateMappings, section, true); diff --git a/Source/RMG-Input/common.cpp b/Source/RMG-Input/common.cpp new file mode 100644 index 00000000..61e83fd3 --- /dev/null +++ b/Source/RMG-Input/common.cpp @@ -0,0 +1,67 @@ +#include "common.hpp" + +#include +#include + +#define MAX_DIAGONAL_VALUE 69 +#define DEADZONE_VALUE 7 + +double simulateDeadzone(double n64InputAxis, double maxAxis, int deadzone, double axisRange) +{ + double axisAbsolute = std::abs(n64InputAxis); + + if (axisAbsolute < deadzone) + { + axisAbsolute = 0; // No input when inside deadzone + } + else + { + // Create linear scaling factor from 0 at inner deadzone to MAX_AXIS at outer limit + axisAbsolute = (axisAbsolute - deadzone) * maxAxis / axisRange / axisAbsolute; + } + + return axisAbsolute; +} + +// Credit: MerryMage +void simulateOctagon(double inputX, double inputY, double deadzoneFactor, double scalingFactor, int& outputX, int& outputY) +{ + double maxAxis = scalingFactor * N64_AXIS_PEAK; + double maxDiagonal = scalingFactor * MAX_DIAGONAL_VALUE; + int deadzone = static_cast(deadzoneFactor * N64_AXIS_PEAK); + double axisRange = maxAxis - deadzone; + // scale to [-maxAxis, maxAxis] + double ax = inputX * maxAxis; + double ay = inputY * maxAxis; + + // check whether (ax, ay) is within the circle of radius MAX_AXIS + if (double len = std::sqrt(ax*ax + ay*ay); len <= maxAxis) + { + // scale inputs + ax *= simulateDeadzone(ax, maxAxis, deadzone, axisRange); + ay *= simulateDeadzone(ay, maxAxis, deadzone, axisRange); + } + else + { + // scale ax and ay to stay on the same line, but at the edge of the circle + len = maxAxis / len; + ax *= len; + ay *= len; + } + + // bound diagonals to an octagonal range [-69, 69] + if (ax != 0.0 && ay != 0.0) + { + double slope = ay / ax; + double edgex = copysign(maxAxis / (std::abs(slope) + (maxAxis - maxDiagonal) / maxDiagonal), ax); + double edgey = copysign(std::min(std::abs(edgex * slope), maxAxis / (1.0 / std::abs(slope) + (maxAxis - maxDiagonal) / maxDiagonal)), ay); + edgex = edgey / slope; + + double scale = std::sqrt(edgex*edgex + edgey*edgey) / maxAxis; + ax *= scale; + ay *= scale; + } + + outputX = static_cast(ax); + outputY = static_cast(ay); +} diff --git a/Source/RMG-Input/common.hpp b/Source/RMG-Input/common.hpp index 52354e8a..7ef604a6 100644 --- a/Source/RMG-Input/common.hpp +++ b/Source/RMG-Input/common.hpp @@ -11,6 +11,7 @@ #define COMMON_HPP #define SDL_AXIS_PEAK 32767 +#define N64_AXIS_PEAK 85 enum class N64ControllerButton { @@ -58,4 +59,6 @@ enum class N64ControllerPak None, }; +void simulateOctagon(double inputX, double inputY, double deadzoneFactor, double scalingFactor, int& outputX, int& outputY); + #endif // COMMON_HPP diff --git a/Source/RMG-Input/main.cpp b/Source/RMG-Input/main.cpp index d0eee78b..3b660350 100644 --- a/Source/RMG-Input/main.cpp +++ b/Source/RMG-Input/main.cpp @@ -29,7 +29,6 @@ // #define NUM_CONTROLLERS 4 -#define MAX_AXIS_VALUE 85 #define RD_GETSTATUS 0x00 // get status #define RD_READKEYS 0x01 // read button values @@ -317,21 +316,22 @@ static int get_button_state(InputProfile* profile, InputMapping* inputMapping) return 0; } -static int get_axis_state(InputProfile* profile, InputMapping* inputMapping, int direction, int value) +// returns axis input scaled to the range [-1, 1] +static double get_axis_state(InputProfile* profile, InputMapping* inputMapping, int direction, double value) { switch (inputMapping->Type) { case InputType::GamepadButton: { int buttonState = SDL_GameControllerGetButton(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerButton)inputMapping->Data); - return buttonState ? MAX_AXIS_VALUE * direction : value; + return buttonState ? direction : value; } break; case InputType::GamepadAxis: { - int axis_value = SDL_GameControllerGetAxis(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerAxis)inputMapping->Data); + double axis_value = SDL_GameControllerGetAxis(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerAxis)inputMapping->Data); if (inputMapping->ExtraData ? axis_value > 0 : axis_value < 0) { - axis_value = ((float)((float)axis_value / SDL_AXIS_PEAK * 100) * ((float)MAX_AXIS_VALUE / 100 * profile->RangeValue / 100)); + axis_value = (axis_value / SDL_AXIS_PEAK) * (profile->RangeValue / 100.0); axis_value = abs(axis_value) * direction; return axis_value; } @@ -339,21 +339,21 @@ static int get_axis_state(InputProfile* profile, InputMapping* inputMapping, int case InputType::JoystickButton: { int buttonState = SDL_JoystickGetButton(profile->InputDevice.GetJoystickHandle(), inputMapping->Data); - return buttonState ? MAX_AXIS_VALUE * direction : value; + return buttonState ? direction : value; } break; case InputType::JoystickAxis: { - int axis_value = SDL_JoystickGetAxis(profile->InputDevice.GetJoystickHandle(), inputMapping->Data); + double axis_value = SDL_JoystickGetAxis(profile->InputDevice.GetJoystickHandle(), inputMapping->Data); if (inputMapping->ExtraData ? axis_value > 0 : axis_value < 0) { - axis_value = ((float)((float)axis_value / SDL_AXIS_PEAK * 100) * ((float)MAX_AXIS_VALUE / 100 * profile->RangeValue / 100)); + axis_value = (axis_value / SDL_AXIS_PEAK) * (profile->RangeValue / 100.0); axis_value = abs(axis_value) * direction; return axis_value; } } break; case InputType::Keyboard: { - return l_KeyboardState[inputMapping->Data] ? MAX_AXIS_VALUE * direction : value; + return l_KeyboardState[inputMapping->Data] ? direction : value; } break; default: break; @@ -639,17 +639,24 @@ EXPORT void CALL GetKeys(int Control, BUTTONS* Keys) Keys->R_TRIG = get_button_state(profile, &profile->Button_RightTrigger); Keys->Z_TRIG = get_button_state(profile, &profile->Button_ZTrigger); - Keys->Y_AXIS = get_axis_state(profile, &profile->AnalogStick_Up, 1, Keys->Y_AXIS); - Keys->Y_AXIS = get_axis_state(profile, &profile->AnalogStick_Down, -1, Keys->Y_AXIS); - Keys->X_AXIS = get_axis_state(profile, &profile->AnalogStick_Left, -1, Keys->X_AXIS); - Keys->X_AXIS = get_axis_state(profile, &profile->AnalogStick_Right, 1, Keys->X_AXIS); + double inputY = get_axis_state(profile, &profile->AnalogStick_Up, 1, Keys->Y_AXIS); + inputY = get_axis_state(profile, &profile->AnalogStick_Down, -1, inputY); + double inputX = get_axis_state(profile, &profile->AnalogStick_Left, -1, Keys->X_AXIS); + inputX = get_axis_state(profile, &profile->AnalogStick_Right, 1, inputX); - // take deadzone into account - if (is_deadzone(Keys->X_AXIS, Keys->Y_AXIS, ((float)MAX_AXIS_VALUE / 100 * profile->DeadzoneValue))) - { - Keys->Y_AXIS = 0; - Keys->X_AXIS = 0; - } + // Required temporaries because the axes are defined as bit fields + int octagonX = 0, octagonY = 0; + simulateOctagon( + inputX, + inputY, + profile->DeadzoneValue / 100.0, // deadzoneFactor + profile->RangeValue / 100.0, // scalingFactor + octagonX, // outputX + octagonY // outputY + ); + + Keys->X_AXIS = octagonX; + Keys->Y_AXIS = octagonY; } EXPORT void CALL InitiateControllers(CONTROL_INFO ControlInfo)