diff --git a/src/common/engine/m_joy.cpp b/src/common/engine/m_joy.cpp index 6b44bf228..28ba17e6c 100644 --- a/src/common/engine/m_joy.cpp +++ b/src/common/engine/m_joy.cpp @@ -475,40 +475,6 @@ CCMD (gamepad) return usage(); } -//=========================================================================== -// -// Joy_RemoveDeadZone -// -//=========================================================================== - -double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons) -{ - uint8_t butt; - - // Cancel out deadzone. - if (fabs(axisval) < deadzone) - { - axisval = 0; - butt = 0; - } - // Make the dead zone the new 0. - else if (axisval < 0) - { - axisval = (axisval + deadzone) / (1.0 - deadzone); - butt = 2; // button minus - } - else - { - axisval = (axisval - deadzone) / (1.0 - deadzone); - butt = 1; // button plus - } - if (buttons != NULL) - { - *buttons = butt; - } - return axisval; -} - //=========================================================================== // // Joy_ApplyResponseCurveBezier @@ -551,6 +517,54 @@ double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input) return sign*t; } +//=========================================================================== +// +// Joy_ManageSingleAxis +// +//=========================================================================== + +double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons) +{ + uint8_t butt; + + // Cancel out deadzone. + if (fabs(axisval) < deadzone) + { + axisval = 0; + butt = 0; + } + else + { + // Make the dead zone the new 0. + if (axisval < 0) + { + axisval = (axisval + deadzone) / (1.0 - deadzone); + butt = 2; // button minus + } + else + { + axisval = (axisval - deadzone) / (1.0 - deadzone); + butt = 1; // button plus + } + + // Apply input response curve + axisval = Joy_ApplyResponseCurveBezier(curve, axisval); + + if (abs(axisval) < threshold) + { + // Needs to meet digital threshold to send button + butt = 0; + } + } + + if (buttons != NULL) + { + *buttons = butt; + } + + return axisval; +} + //=========================================================================== // // Joy_XYAxesToButtons @@ -588,6 +602,86 @@ int Joy_XYAxesToButtons(double x, double y) return JoyAngleButtons[int(rad) & 7]; } +//=========================================================================== +// +// Joy_ManageThumbstick +// +// Given two axes and their settings, handle applying deadzone, digital +// threshold, input response curve, and setting button state. This is +// necessary, because doing the two axes individually creates awkward +// behavior (such as cross shaped deadzones, sluggish input response when +// pressing diagonally, so on). +// +//=========================================================================== + +double Joy_ManageThumbstick( + double *axis_x, double *axis_y, + double deadzone_x, double deadzone_y, + double threshold_x, double threshold_y, + const CubicBezier &curve_x, const CubicBezier &curve_y, + uint8_t *buttons) +{ + double ret_x = *axis_x; + double ret_y = *axis_y; + + double x_abs = abs(*axis_x); + double y_abs = abs(*axis_y); + double magnitude = sqrt((x_abs * x_abs) + (y_abs * y_abs)); + + double ret_dist = 0; + uint8_t ret_butt = 0; + + double xy_lerp = 0.5; + if (magnitude > 0) + { + // Later it would be nice to have a single deadzone / threshold / curve setting + // for the whole thumbstick instead of awkwardly trying to combine them, but + // that requires re-considering how axes are exposed to the rest of the engine. + // This will do for now. + xy_lerp = abs(cos(atan2(ret_y, ret_x))); + } + + double deadzone = (xy_lerp * deadzone_x) + ((1.0 - xy_lerp) * deadzone_y); + if (magnitude < deadzone) + { + ret_x = 0; + ret_y = 0; + } + else + { + // Make the dead zone the new 0. + ret_dist = (magnitude - deadzone) / (1.0 - deadzone); + + const CubicBezier curve = { + (float)((xy_lerp * curve_x.x1) + ((1.0 - xy_lerp) * curve_y.x1)), + (float)((xy_lerp * curve_x.y1) + ((1.0 - xy_lerp) * curve_y.y1)), + (float)((xy_lerp * curve_x.x2) + ((1.0 - xy_lerp) * curve_y.x2)), + (float)((xy_lerp * curve_x.y2) + ((1.0 - xy_lerp) * curve_y.y2)) + }; + + ret_dist = Joy_ApplyResponseCurveBezier(curve, ret_dist); + + ret_x = (ret_x / magnitude) * ret_dist; + ret_y = (ret_y / magnitude) * ret_dist; + + double threshold = (xy_lerp * threshold_x) + ((1.0 - xy_lerp) * threshold_y); + if (ret_dist >= threshold) + { + ret_butt = Joy_XYAxesToButtons(ret_x, ret_y); + } + } + + *axis_x = ret_x; + *axis_y = ret_y; + + if (buttons != NULL) + { + *buttons = ret_butt; + } + + return ret_dist; +} + //=========================================================================== // // Joy_GenerateButtonEvent diff --git a/src/common/engine/m_joy.h b/src/common/engine/m_joy.h index 2418bb4ed..b4bda3f20 100644 --- a/src/common/engine/m_joy.h +++ b/src/common/engine/m_joy.h @@ -88,9 +88,13 @@ void M_SaveJoystickConfig(IJoystickConfig *joy); void Joy_GenerateButtonEvent(bool down, EKeyCodes which); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys); -int Joy_XYAxesToButtons(double x, double y); -double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons); + double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input); +double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons); +int Joy_XYAxesToButtons(double x, double y); +double Joy_ManageThumbstick(double *axis_x, double *axis_y, double deadzone_x, double deadzone_y, + double threshold_x, double threshold_y, const CubicBezier &curve_x, const CubicBezier &curve_y, uint8_t *buttons); + // These ought to be provided by a system-specific i_input.cpp. void I_GetAxes(float axes[NUM_AXIS_CODES]); diff --git a/src/common/platform/posix/cocoa/i_joystick.cpp b/src/common/platform/posix/cocoa/i_joystick.cpp index 0ddd7cb43..4e348fb8c 100644 --- a/src/common/platform/posix/cocoa/i_joystick.cpp +++ b/src/common/platform/posix/cocoa/i_joystick.cpp @@ -638,40 +638,80 @@ void IOKitJoystick::ProcessAxes() for (size_t i = 0, count = m_axes.Size(); i < count; ++i) { - AnalogAxis& axis = m_axes[i]; - uint8_t buttonstate = 0; - static const double scaledMin = -1; static const double scaledMax = 1; - IOHIDEventStruct event; - - if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) - { - const double scaledValue = scaledMin + - (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); - const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, &buttonstate); - const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); - - axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); - } - else - { - axis.value = 0.0f; - } - if (i < NUM_JOYAXISBUTTONS && (i > 2 || m_axes.Size() == 1)) { + AnalogAxis& axis = m_axes[i]; + uint8_t buttonstate = 0; + + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) + { + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + + const double filteredValue = Joy_ManageSingleAxis( + scaledValue, + axis.deadZone, + axis.digitalThreshold, + axis.responseCurve, + &buttonstate + ); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + } + else + { + axis.value = 0.0f; + } + Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + axis.buttonValue = buttonstate; } else if (i == 1) { // Since we sorted the axes, we know that the first two are definitely X and Y. - // They are probably a single stick, so use angular position to determine buttons. - buttonstate = Joy_XYAxesToButtons(m_axes[0].value, axis.value); - Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); + // They are probably a single stick, so treat them as one. + + AnalogAxis& axis_x = m_axes[i - 1]; + AnalogAxis& axis_y = m_axes[i]; + + uint8_t buttonstate = 0; + double axisval_x = 0.0f; + double axisval_y = 0.0f; + + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_x.cookie, &event)) + { + axisval_x = scaledMin + + (event.value - axis_x.minValue) * (scaledMax - scaledMin) / (axis_x.maxValue - axis_x.minValue); + } + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_y.cookie, &event)) + { + axisval_y = scaledMin + + (event.value - axis_y.minValue) * (scaledMax - scaledMin) / (axis_y.maxValue - axis_y.minValue); + } + + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + axis_x.deadZone, axis_y.deadZone, + axis_x.digitalThreshold, axis_y.digitalThreshold, + axis_x.responseCurve, axis_y.responseCurve, + &buttonstate + ); + + axis_x.value = static_cast(axisval_x * m_sensitivity * axis_x.sensitivity); + axis_y.value = static_cast(axisval_y * m_sensitivity * axis_y.sensitivity); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(axis_x.buttonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + axis_x.buttonValue = buttonstate; } - axis.buttonValue = buttonstate; } } @@ -698,10 +738,16 @@ bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) const double scaledValue = scaledMin + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); - const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, &buttonstate); - const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); - axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); + const double filteredValue = Joy_ManageSingleAxis( + scaledValue, + axis.deadZone, + axis.digitalThreshold, + axis.responseCurve, + &buttonstate + ); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); if (i < NUM_JOYAXISBUTTONS) { diff --git a/src/common/platform/posix/sdl/i_joystick.cpp b/src/common/platform/posix/sdl/i_joystick.cpp index 2c4677c00..8f1e97107 100644 --- a/src/common/platform/posix/sdl/i_joystick.cpp +++ b/src/common/platform/posix/sdl/i_joystick.cpp @@ -41,6 +41,16 @@ #include "i_input.h" #include "m_joy.h" +static const EAxisCodes ControllerAxisCodes[][2] = +{ + { AXIS_CODE_PAD_LTHUMB_RIGHT, AXIS_CODE_PAD_LTHUMB_LEFT }, + { AXIS_CODE_PAD_LTHUMB_DOWN, AXIS_CODE_PAD_LTHUMB_UP }, + { AXIS_CODE_PAD_RTHUMB_RIGHT, AXIS_CODE_PAD_RTHUMB_LEFT }, + { AXIS_CODE_PAD_RTHUMB_DOWN, AXIS_CODE_PAD_RTHUMB_UP }, + { AXIS_CODE_PAD_LTRIGGER, AXIS_CODE_NULL }, + { AXIS_CODE_PAD_RTRIGGER, AXIS_CODE_NULL } +}; + class SDLInputJoystick: public IJoystickConfig { public: @@ -283,147 +293,200 @@ public: return id; } - void AddAxes(float axes[NUM_AXIS_CODES]) + void AddAxes(float joyaxes[NUM_AXIS_CODES]) { // Add to game axes. for (int i = 0; i < GetNumAxes(); ++i) { float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier); - int code = AXIS_CODE_NULL; + + int axis_code_pos = AXIS_CODE_NULL; + int axis_code_neg = AXIS_CODE_NULL; + int ret_code = AXIS_CODE_NULL; + + if (Mapping) + { + if (i < SDL_CONTROLLER_AXIS_MAX) + { + axis_code_pos = ControllerAxisCodes[i][0]; + axis_code_neg = ControllerAxisCodes[i][1]; + } + } + else + { + if (i < NUM_JOYAXISBUTTONS) + { + axis_code_pos = AXIS_CODE_JOY1_PLUS + (i * 2); + axis_code_neg = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; + } + } if (axis_value > 0.0f) { - code = Axes[i].Codes[0]; + ret_code = axis_code_pos; } else if (axis_value < 0.0f) { - code = Axes[i].Codes[1]; + ret_code = axis_code_neg; } - if (code != AXIS_CODE_NULL) + if (ret_code != AXIS_CODE_NULL) { - axes[code] += fabs(axis_value); + joyaxes[ret_code] += fabs(axis_value); + } + } + } + + void ProcessGameControllerThumbstick(int index1, int index2, int base) + { + AxisInfo &axis1 = Axes[index1]; + AxisInfo &axis2 = Axes[index2]; + + uint8_t buttonstate; + double axisval1, axisval2; + + axisval1 = SDL_GameControllerGetAxis(Mapping, static_cast(index1)) / 32767.0; + axisval2 = SDL_GameControllerGetAxis(Mapping, static_cast(index2)) / 32767.0; + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1.DeadZone, axis2.DeadZone, + axis1.DigitalThreshold, axis2.DigitalThreshold, + axis1.ResponseCurve, axis2.ResponseCurve, + &buttonstate + ); + + axis1.Value = float(axisval1); + axis2.Value = float(axisval2); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(axis1.ButtonValue, buttonstate, 4, base); + axis1.ButtonValue = buttonstate; + } + + void ProcessGameControllerTrigger(int index, int base) + { + AxisInfo &axis = Axes[index]; + + uint8_t buttonstate; + double axisval; + + axisval = SDL_GameControllerGetAxis(Mapping, static_cast(index)) / 32767.0; + axisval = Joy_ManageSingleAxis( + axisval, + axis.DeadZone, axis.DigitalThreshold, axis.ResponseCurve, + &buttonstate + ); + + axis.Value = float(axisval); + Joy_GenerateButtonEvents(axis.ButtonValue, buttonstate, 1, base); + axis.ButtonValue = buttonstate; + } + + void ProcessGameControllerAxes() + { + // Refactored to match more closely with XInput / raw PS2 on Windows + ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, KEY_PAD_LTHUMB_RIGHT); + ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, KEY_PAD_RTHUMB_RIGHT); + ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERLEFT, KEY_PAD_LTRIGGER); + ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, KEY_PAD_RTRIGGER); + } + + void ProcessOldJoystickAxes() + { + uint8_t buttonstate; + + for (int i = 0; i < NumAxes; ++i) + { + buttonstate = 0; + + if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) + { + AxisInfo &info = Axes[i]; + double axisval; + + axisval = SDL_JoystickGetAxis(Device, i) / 32767.0; + + info.Value = Joy_ManageSingleAxis( + axisval, + info.DeadZone, + info.DigitalThreshold, + info.ResponseCurve, + &buttonstate + ); + + Joy_GenerateButtonEvents(info.ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + info.ButtonValue = buttonstate; + } + else if (i == 1) + { + AxisInfo &info_x = Axes[i - 1]; + AxisInfo &info_y = Axes[i]; + double axisval_x, axisval_y; + + axisval_x = SDL_JoystickGetAxis(Device, i - 1) / 32767.0; + axisval_y = SDL_JoystickGetAxis(Device, i) / 32767.0; + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + info_x.DeadZone, info_y.DeadZone, + info_x.DigitalThreshold, info_y.DigitalThreshold, + info_x.ResponseCurve, info_y.ResponseCurve, + &buttonstate + ); + + info_x.Value = axisval_x; + info_y.Value = axisval_y; + + Joy_GenerateButtonEvents(info_x.ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + info_x.ButtonValue = buttonstate; + } + } + + // Map POV hats to buttons and axes. Why axes? Well apparently I have + // a gamepad where the left control stick is a POV hat (instead of the + // d-pad like you would expect, no that's pressure sensitive). Also + // KDE's joystick dialog maps them to axes as well. + for (int i = 0; i < NumHats; ++i) + { + AxisInfo &x = Axes[NumAxes + i*2]; + AxisInfo &y = Axes[NumAxes + i*2 + 1]; + + buttonstate = SDL_JoystickGetHat(Device, i); + + // If we're going to assume that we can pass SDL's value into + // Joy_GenerateButtonEvents then we might as well assume the format here. + if(buttonstate & 0x1) // Up + y.Value = -1.0; + else if(buttonstate & 0x4) // Down + y.Value = 1.0; + else + y.Value = 0.0; + if(buttonstate & 0x2) // Left + x.Value = 1.0; + else if(buttonstate & 0x8) // Right + x.Value = -1.0; + else + x.Value = 0.0; + + if (i < 4) + { + Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); + x.ButtonValue = buttonstate; } } } void ProcessInput() { - uint8_t buttonstate; - if (Mapping) { // GameController API available - - auto lastTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; - auto lastTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; - - for (auto i = 0; i < SDL_CONTROLLER_AXIS_MAX && i < NumAxes; ++i) - { - buttonstate = 0; - - if (i < NUM_JOYAXISBUTTONS) - { - Axes[i].Codes[0] = AXIS_CODE_JOY1_PLUS + (i * 2); - Axes[i].Codes[1] = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; - } - else - { - Axes[i].Codes[0] = Axes[i].Codes[1] = AXIS_CODE_NULL; - } - - Axes[i].Value = SDL_GameControllerGetAxis(Mapping, static_cast(i))/32767.0; - Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); - Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); - } - - auto currTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; - auto currTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; - - if (lastTriggerL != currTriggerL) Joy_GenerateButtonEvent(currTriggerL, KEY_PAD_LTRIGGER); - if (lastTriggerR != currTriggerR) Joy_GenerateButtonEvent(currTriggerR, KEY_PAD_RTRIGGER); - - // todo: right stick - buttonstate = Joy_XYAxesToButtons( - abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, - abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value - ); - Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); - Axes[0].ButtonValue = buttonstate; + ProcessGameControllerAxes(); } else { // Joystick API fallback - - for (int i = 0; i < NumAxes; ++i) - { - buttonstate = 0; - - if (i < NUM_JOYAXISBUTTONS) - { - Axes[i].Codes[0] = AXIS_CODE_JOY1_PLUS + (i * 2); - Axes[i].Codes[1] = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; - } - else - { - Axes[i].Codes[0] = Axes[i].Codes[1] = AXIS_CODE_NULL; - } - - Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0; - Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); - Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); - - // Map button to axis - // X and Y are handled differently so if we have 2 or more axes then we'll use that code instead. - if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS)) - { - Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); - Axes[i].ButtonValue = buttonstate; - } - } - - if (NumAxes > 1) - { - buttonstate = Joy_XYAxesToButtons( - abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, - abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value - ); - Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); - Axes[0].ButtonValue = buttonstate; - } - - // Map POV hats to buttons and axes. Why axes? Well apparently I have - // a gamepad where the left control stick is a POV hat (instead of the - // d-pad like you would expect, no that's pressure sensitive). Also - // KDE's joystick dialog maps them to axes as well. - for (int i = 0; i < NumHats; ++i) - { - AxisInfo &x = Axes[NumAxes + i*2]; - AxisInfo &y = Axes[NumAxes + i*2 + 1]; - - buttonstate = SDL_JoystickGetHat(Device, i); - - // If we're going to assume that we can pass SDL's value into - // Joy_GenerateButtonEvents then we might as well assume the format here. - if(buttonstate & 0x1) // Up - y.Value = -1.0; - else if(buttonstate & 0x4) // Down - y.Value = 1.0; - else - y.Value = 0.0; - if(buttonstate & 0x2) // Left - x.Value = 1.0; - else if(buttonstate & 0x8) // Right - x.Value = -1.0; - else - x.Value = 0.0; - - if (i < 4) - { - Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); - x.ButtonValue = buttonstate; - } - } + ProcessOldJoystickAxes(); } } @@ -436,7 +499,6 @@ protected: float DigitalThreshold; EJoyCurve ResponseCurvePreset; CubicBezier ResponseCurve; - int Codes[2]; // 0: positive, 1: negative double Value; uint8_t ButtonValue; }; diff --git a/src/common/platform/win32/i_dijoy.cpp b/src/common/platform/win32/i_dijoy.cpp index af9082a4f..1a00aaaaf 100644 --- a/src/common/platform/win32/i_dijoy.cpp +++ b/src/common/platform/win32/i_dijoy.cpp @@ -451,33 +451,58 @@ void FDInputJoystick::ProcessInput() // into button presses. for (i = 0; i < Axes.Size(); ++i) { - AxisInfo *info = &Axes[i]; - LONG value = *(LONG *)(state + info->Ofs); - double axisval; uint8_t buttonstate = 0; - // Scale to [-1.0, 1.0] - axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; - // Cancel out dead zone - axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate); - axisval = Joy_ApplyResponseCurveBezier(info->ResponseCurve, axisval); - info->Value = float(axisval); if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) { - if (abs(axisval) < info->DigitalThreshold) buttonstate = 0; + AxisInfo *info = &Axes[i]; + LONG value = *(LONG *)(state + info->Ofs); + double axisval; + + // Scale to [-1.0, 1.0] + axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; + + // Cancel out dead zone + axisval = Joy_ManageSingleAxis( + axisval, + info->DeadZone, + info->DigitalThreshold, + info->ResponseCurve, + &buttonstate + ); + Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + info->ButtonValue = buttonstate; } else if (i == 1) { // Since we sorted the axes, we know that the first two are definitely X and Y. - // They are probably a single stick, so use angular position to determine buttons. - buttonstate = Joy_XYAxesToButtons( - (abs(Axes[0].Value) < Axes[0].DigitalThreshold) ? 0 : Axes[0].Value, - (abs(axisval) < info->DigitalThreshold) ? 0 : axisval + // They are probably a single stick, so treat them as one. + AxisInfo *info_x = &Axes[i - 1]; + AxisInfo *info_y = &Axes[i]; + + LONG value_x = *(LONG *)(state + info_x->Ofs); + LONG value_y = *(LONG *)(state + info_y->Ofs); + + double axisval_x, axisval_y; + + // Scale to [-1.0, 1.0] + axisval_x = (value_x - info_x->Min) * 2.0 / (info_x->Max - info_x->Min) - 1.0; + axisval_y = (value_y - info_y->Min) * 2.0 / (info_y->Max - info_y->Min) - 1.0; + + // Cancel out dead zone + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + info_x->DeadZone, info_y->DeadZone, + info_x->DigitalThreshold, info_y->DigitalThreshold, + info_x->ResponseCurve, info_y->ResponseCurve, + &buttonstate ); - Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(info_x->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + info_x->ButtonValue = buttonstate; } - info->ButtonValue = buttonstate; } // Compare button states and generate events for buttons that have changed. diff --git a/src/common/platform/win32/i_rawps2.cpp b/src/common/platform/win32/i_rawps2.cpp index bf758a940..2b06c9abf 100644 --- a/src/common/platform/win32/i_rawps2.cpp +++ b/src/common/platform/win32/i_rawps2.cpp @@ -565,18 +565,19 @@ void FRawPS2Controller::ProcessThumbstick(int value1, AxisInfo *axis1, int value axisval1 = value1 * (2.0 / 255) - 1.0; axisval2 = value2 * (2.0 / 255) - 1.0; - axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); - axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); - axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); - axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1->DeadZone, axis2->DeadZone, + axis1->DigitalThreshold, axis2->DigitalThreshold, + axis1->ResponseCurve, axis2->ResponseCurve, + &buttonstate + ); + axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons( - (abs(axisval1) < axis1->DigitalThreshold) ? 0 : axisval1, - (abs(axisval2) < axis2->DigitalThreshold) ? 0 : axisval2 - ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } diff --git a/src/common/platform/win32/i_xinput.cpp b/src/common/platform/win32/i_xinput.cpp index 12049b4a0..5e45f1cfc 100644 --- a/src/common/platform/win32/i_xinput.cpp +++ b/src/common/platform/win32/i_xinput.cpp @@ -343,18 +343,19 @@ void FXInputController::ProcessThumbstick(int value1, AxisInfo *axis1, axisval1 = (value1 - SHRT_MIN) * 2.0 / 65536 - 1.0; axisval2 = (value2 - SHRT_MIN) * 2.0 / 65536 - 1.0; - axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); - axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); - axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); - axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1->DeadZone, axis2->DeadZone, + axis1->DigitalThreshold, axis2->DigitalThreshold, + axis1->ResponseCurve, axis2->ResponseCurve, + &buttonstate + ); + axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons( - abs(axisval1) < axis1->DigitalThreshold ? 0: axisval1, - abs(axisval2) < axis2->DigitalThreshold ? 0: axisval2 - ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } @@ -373,15 +374,15 @@ void FXInputController::ProcessTrigger(int value, AxisInfo *axis, int base) uint8_t buttonstate; double axisval; - axisval = Joy_RemoveDeadZone(value / 256.0, axis->DeadZone, &buttonstate); - axisval = Joy_ApplyResponseCurveBezier(axis->ResponseCurve, axisval); - - // TODO: probably just put this into Joy_RemoveDeadZone - if (abs(axisval) < axis->DigitalThreshold) buttonstate = 0; + axisval = Joy_ManageSingleAxis( + value / 256.0, + axis->DeadZone, axis->DigitalThreshold, axis->ResponseCurve, + &buttonstate + ); + axis->Value = float(axisval); Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 1, base); axis->ButtonValue = buttonstate; - axis->Value = float(axisval); } //========================================================================== diff --git a/src/g_game.cpp b/src/g_game.cpp index c8dad9b10..b288fcfc9 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -825,6 +825,17 @@ void G_BuildTiccmd (usercmd_t *cmd) cmd->sidemove <<= 8; } +ADD_STAT (analog) +{ + FString out; + + float axis_forward = buttonMap.ButtonAnalog(Button_Forward) - buttonMap.ButtonAnalog(Button_Back); + float axis_side = buttonMap.ButtonAnalog(Button_MoveLeft) - buttonMap.ButtonAnalog(Button_MoveRight); + out.AppendFormat("[%.3f, %.3f]", axis_forward, axis_side); + + return out; +} + static int LookAdjust(int look) { look <<= 16;