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:
parent
ca562829c5
commit
e1a5326947
8 changed files with 442 additions and 198 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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<float>(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<float>(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<float>(axisval_x * m_sensitivity * axis_x.sensitivity);
|
||||
axis_y.value = static_cast<float>(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<float>(smoothedValue * m_sensitivity * axis.sensitivity);
|
||||
const double filteredValue = Joy_ManageSingleAxis(
|
||||
scaledValue,
|
||||
axis.deadZone,
|
||||
axis.digitalThreshold,
|
||||
axis.responseCurve,
|
||||
&buttonstate
|
||||
);
|
||||
|
||||
axis.value = static_cast<float>(filteredValue * m_sensitivity * axis.sensitivity);
|
||||
|
||||
if (i < NUM_JOYAXISBUTTONS)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<SDL_GameControllerAxis>(index1)) / 32767.0;
|
||||
axisval2 = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(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<SDL_GameControllerAxis>(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<SDL_GameControllerAxis>(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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue