Unify thumbsticks

- Combined the functions that handle dead zone, digital threshold, and response curves together.
  - A lot less copy-paste between the different platforms.
- Combine the X and Y settings for dead zone, digital threshold, and response curve for analog sticks. This fixes many issues:
  - The dead zone shape for sticks are now a true circle, instead of being axial.
  - The new response curves work better when moving diagonally. Previously, it'd nerf your move speed up to ~60-50% if you had it on anything but Linear.
- Refactored the new POSIX SDL gamepad axis code to work more like Windows' XInput code. This comes with the following benefits:
  - Right thumbstick should function now.
  - Left thumbstick now sends the gamepad keycodes instead of raw joystick keycodes.
- Added `stat analog`, to display current movement analog stick values. (I've needed it a lot lately.)
This commit is contained in:
Sally Coolatta 2025-07-11 00:17:01 -04:00 committed by Rachael Alexanderson
commit e1a5326947
8 changed files with 442 additions and 198 deletions

View file

@ -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