Refactor and unify analog input settings

This commit is contained in:
Yifan Gu 2021-06-26 23:16:52 -04:00 committed by Henrik Rydgård
parent c96692fd2e
commit 633a6f612b
9 changed files with 100 additions and 212 deletions

View File

@ -962,18 +962,11 @@ static ConfigSetting controlSettings[] = {
ConfigSetting("AnalogRotationCWKeyX", "AnalogRotationKeyCWY", "AnalogRotationKeyCWScale", "ShowAnalogRotationCWKey", &g_Config.touchAnalogRotationCWKey, defaultTouchPosHide, true, true),
ConfigSetting("AnalogRotationCCWKeyX", "AnalogRotationKeyCCWY", "AnalogRotationKeyCCWScale", "ShowAnalogRotationCCWKey", &g_Config.touchAnalogRotationCCWKey, defaultTouchPosHide, true, true),
#ifdef _WIN32
ConfigSetting("DInputAnalogDeadzone", &g_Config.fDInputAnalogDeadzone, 0.1f, true, true),
ConfigSetting("DInputAnalogInverseMode", &g_Config.iDInputAnalogInverseMode, 0, true, true),
ConfigSetting("DInputAnalogInverseDeadzone", &g_Config.fDInputAnalogInverseDeadzone, 0.0f, true, true),
ConfigSetting("DInputAnalogSensitivity", &g_Config.fDInputAnalogSensitivity, 1.0f, true, true),
ConfigSetting("AnalogDeadzone", &g_Config.fAnalogDeadzone, 0.0f, true, true),
ConfigSetting("AnalogInverseDeadzone", &g_Config.fAnalogInverseDeadzone, 0.0f, true, true),
ConfigSetting("AnalogSensitivity", &g_Config.fAnalogSensitivity, 1.0f, true, true),
ConfigSetting("AnalogIsCircular", &g_Config.bAnalogIsCircular, true, true, true),
ConfigSetting("XInputAnalogDeadzone", &g_Config.fXInputAnalogDeadzone, 0.24f, true, true),
ConfigSetting("XInputAnalogInverseMode", &g_Config.iXInputAnalogInverseMode, 0, true, true),
ConfigSetting("XInputAnalogInverseDeadzone", &g_Config.fXInputAnalogInverseDeadzone, 0.0f, true, true),
#endif
// Also reused as generic analog sensitivity
ConfigSetting("XInputAnalogSensitivity", &g_Config.fXInputAnalogSensitivity, 1.0f, true, true),
ConfigSetting("AnalogLimiterDeadzone", &g_Config.fAnalogLimiterDeadzone, 0.6f, true, true),
ConfigSetting("UseMouse", &g_Config.bMouseControl, false, true, true),

View File

@ -381,16 +381,13 @@ public:
bool bHapticFeedback;
float fDInputAnalogDeadzone;
int iDInputAnalogInverseMode;
float fDInputAnalogInverseDeadzone;
float fDInputAnalogSensitivity;
// We also use the XInput settings as analog settings on other platforms like Android.
float fXInputAnalogDeadzone;
int iXInputAnalogInverseMode;
float fXInputAnalogInverseDeadzone;
float fXInputAnalogSensitivity;
float fAnalogDeadzone;
float fAnalogInverseDeadzone;
float fAnalogSensitivity;
// convert analog stick circle to square
bool bAnalogIsCircular;
float fAnalogLimiterDeadzone;

View File

@ -174,8 +174,7 @@ void SDLJoystick::ProcessInput(SDL_Event &event){
case SDL_CONTROLLERAXISMOTION:
AxisInput axis;
axis.axisId = event.caxis.axis;
// 1.2 to try to approximate the PSP's clamped rectangular range.
axis.value = 1.2 * event.caxis.value * g_Config.fXInputAnalogSensitivity / 32767.0f;
axis.value = event.caxis.value / 32767.0f;
if (axis.value > 1.0f) axis.value = 1.0f;
if (axis.value < -1.0f) axis.value = -1.0f;
axis.deviceId = DEVICE_ID_PAD_0 + getDeviceIndex(event.caxis.which);

View File

@ -727,23 +727,12 @@ void GameSettingsScreen::CreateViews() {
style->SetEnabledPtr(&g_Config.bShowTouchControls);
}
#ifdef _WIN32
static const char *inverseDeadzoneModes[] = { "Off", "X", "Y", "X + Y" };
controlsSettings->Add(new ItemHeader(co->T("Analog Settings", "Analog Settings")));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogDeadzone, 0.0f, 1.0f, co->T("Deadzone Radius"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogInverseDeadzone, 0.0f, 1.0f, co->T("Analog Mapper Low End", "Analog Mapper Low End (Inverse Deadzone)"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogSensitivity, 0.0f, 10.0f, co->T("Analog Mapper High End", "Analog Mapper High End (Axis Sensitivity)"), 0.01f, screenManager(), "x"));
controlsSettings->Add(new CheckBox(&g_Config.bAnalogIsCircular, co->T("Circular Analog Stick Input")));
controlsSettings->Add(new ItemHeader(co->T("DInput Analog Settings", "DInput Analog Settings")));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fDInputAnalogDeadzone, 0.0f, 1.0f, co->T("Deadzone Radius"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupMultiChoice(&g_Config.iDInputAnalogInverseMode, co->T("Analog Mapper Mode"), inverseDeadzoneModes, 0, ARRAY_SIZE(inverseDeadzoneModes), co->GetName(), screenManager()));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fDInputAnalogInverseDeadzone, 0.0f, 1.0f, co->T("Analog Mapper Low End", "Analog Mapper Low End (Inverse Deadzone)"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fDInputAnalogSensitivity, 0.0f, 10.0f, co->T("Analog Mapper High End", "Analog Mapper High End (Axis Sensitivity)"), 0.01f, screenManager(), "x"));
controlsSettings->Add(new ItemHeader(co->T("XInput Analog Settings", "XInput Analog Settings")));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fXInputAnalogDeadzone, 0.0f, 1.0f, co->T("Deadzone Radius"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupMultiChoice(&g_Config.iXInputAnalogInverseMode, co->T("Analog Mapper Mode"), inverseDeadzoneModes, 0, ARRAY_SIZE(inverseDeadzoneModes), co->GetName(), screenManager()));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fXInputAnalogInverseDeadzone, 0.0f, 1.0f, co->T("Analog Mapper Low End", "Analog Mapper Low End (Inverse Deadzone)"), 0.01f, screenManager(), "/ 1.0"));
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fXInputAnalogSensitivity, 0.0f, 10.0f, co->T("Analog Mapper High End", "Analog Mapper High End (Axis Sensitivity)"), 0.01f, screenManager(), "x"));
#else
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fXInputAnalogSensitivity, 0.0f, 10.0f, co->T("Analog Axis Sensitivity", "Analog Axis Sensitivity"), 0.01f, screenManager(), "x"));
#endif
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogAutoRotSpeed, 0.0f, 25.0f, co->T("Analog auto-rotation speed"), 1.0f, screenManager()));
controlsSettings->Add(new ItemHeader(co->T("Keyboard", "Keyboard Control Settings")));

View File

@ -1326,9 +1326,84 @@ bool NativeKey(const KeyInput &key) {
return retval;
}
static float MapAxisValue(float v) {
const float deadzone = g_Config.fAnalogDeadzone;
const float invDeadzone = g_Config.fAnalogInverseDeadzone;
const float sensitivity = g_Config.fAnalogSensitivity;
const float sign = v >= 0.0f ? 1.0f : -1.0f;
return sign * Clamp(invDeadzone + (abs(v) - deadzone) / (1.0f - deadzone) * (sensitivity - invDeadzone), 0.0f, 1.0f);
}
static void ConvertAnalogStick(float &x, float &y) {
const bool isCircular = g_Config.bAnalogIsCircular;
float norm = std::max(fabsf(x), fabsf(y));
if (norm == 0.0f)
return;
if (isCircular) {
float newNorm = sqrtf(x * x + y * y);
float factor = newNorm / norm;
x *= factor;
y *= factor;
norm = newNorm;
}
float mappedNorm = MapAxisValue(norm);
x = Clamp(x / norm * mappedNorm, -1.0f, 1.0f);
y = Clamp(y / norm * mappedNorm, -1.0f, 1.0f);
}
static bool AnalogStickAxis(const AxisInput &axis) {
static float history[JOYSTICK_AXIS_MAX+1] = { 0.0f };
if (history[axis.axisId] == axis.value) {
return true;
}
history[axis.axisId] = axis.value;
AxisInput axisA = axis;
AxisInput axisB = axis;
switch (axis.axisId) {
case JOYSTICK_AXIS_X:
case JOYSTICK_AXIS_Y:
axisA.axisId = JOYSTICK_AXIS_X;
axisB.axisId = JOYSTICK_AXIS_Y;
axisA.value = history[JOYSTICK_AXIS_X];
axisB.value = history[JOYSTICK_AXIS_Y];
break;
case JOYSTICK_AXIS_Z:
case JOYSTICK_AXIS_RZ:
axisA.axisId = JOYSTICK_AXIS_Z;
axisB.axisId = JOYSTICK_AXIS_RZ;
axisA.value = history[JOYSTICK_AXIS_Z];
axisB.value = history[JOYSTICK_AXIS_RZ];
break;
default:
break;
}
ConvertAnalogStick(axisA.value, axisB.value);
bool retA = screenManager->axis(axisA);
bool retB = screenManager->axis(axisB);
return retA && retB;
}
bool NativeAxis(const AxisInput &axis) {
using namespace TiltEventProcessor;
switch (axis.axisId) {
case JOYSTICK_AXIS_X:
case JOYSTICK_AXIS_Y:
case JOYSTICK_AXIS_Z:
case JOYSTICK_AXIS_RZ:
return AnalogStickAxis(axis);
default:
break;
}
// only handle tilt events if tilt is enabled.
if (g_Config.iTiltInputType == TILT_NULL) {
// if tilt events are disabled, then run it through the usual way.

View File

@ -176,7 +176,7 @@ DinputDevice::DinputDevice(int devnum) {
dipw.diph.dwHow = DIPH_DEVICE;
dipw.diph.dwObj = 0;
// dwData 10000 is deadzone(0% - 100%), multiply by config scalar
dipw.dwData = (int)(g_Config.fDInputAnalogDeadzone * 10000);
dipw.dwData = 0;
analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true;
}
@ -209,60 +209,6 @@ void SendNativeAxis(int deviceId, int value, int &lastValue, int axisId) {
lastValue = value;
}
inline int Signs(int val) {
return (0 < val) - (val < 0);
}
inline int LinearMaps(int val, int a0, int a1, int b0, int b1) {
return b0 + (((val - a0) * (b1 - b0)) / (a1 - a0));
}
inline float LinearMaps(float val, float a0, float a1, float b0, float b1) {
return b0 + (((val - a0) * (b1 - b0)) / (a1 - a0));
}
static void ApplyNormalization(LONG &jsx, LONG &jsy) {
// Circle to Square mapping, cribbed from XInputDevice
float sx = (float)jsx;
float sy = (float)jsy;
float scaleFactor = sqrtf((sx * sx + sy * sy) / std::max(sx * sx, sy * sy));
jsx = (int)(sx * scaleFactor);
jsy = (int)(sy * scaleFactor);
// Linear range mapping (used to invert deadzones)
float dz = g_Config.fDInputAnalogDeadzone;
int idzm = g_Config.iDInputAnalogInverseMode;
float idz = g_Config.fDInputAnalogInverseDeadzone;
float md = std::max(dz, idz);
float st = g_Config.fDInputAnalogSensitivity;
float magnitude = sqrtf((float)(jsx * jsx + jsy * jsy));
if (magnitude > dz * 10000.0f) {
if (idzm == 1) {
int xSign = Signs(jsx);
if (xSign != 0) {
jsx = LinearMaps(jsx, xSign * (int)(dz * 10000), xSign * 10000, xSign * (int)(md * 10000), xSign * (int)(st * 10000));
}
} else if (idzm == 2) {
int ySign = Signs(jsy);
if (ySign != 0) {
jsy = LinearMaps(jsy, ySign * (int)(dz * 10000.0f), ySign * 10000, ySign * (int)(md * 10000.0f), ySign * (int)(st * 10000));
}
} else if (idzm == 3) {
float xNorm = (float)jsx / magnitude;
float yNorm = (float)jsy / magnitude;
float mapMag = LinearMaps(magnitude, dz * 10000.0f, 10000.0f, md * 10000.0f, 10000.0f * st);
jsx = (short)(xNorm * mapMag);
jsy = (short)(yNorm * mapMag);
}
} else {
jsx = 0;
jsy = 0;
}
jsx = (short)std::min(10000.0f, std::max((float)jsx, -10000.0f));
jsy = (short)std::min(10000.0f, std::max((float)jsy, -10000.0f));
}
static LONG *ValueForAxisId(DIJOYSTATE2 &js, int axisId) {
switch (axisId) {
case JOYSTICK_AXIS_X: return &js.lX;
@ -275,14 +221,6 @@ static LONG *ValueForAxisId(DIJOYSTATE2 &js, int axisId) {
}
}
static void ApplyNormalization(DIJOYSTATE2 &js, int xAxisId, int yAxisId) {
LONG *nrmX = ValueForAxisId(js, xAxisId);
LONG *nrmY = ValueForAxisId(js, yAxisId);
if (nrmX != nullptr && nrmY != nullptr) {
ApplyNormalization(*nrmX, *nrmY);
}
}
int DinputDevice::UpdateState() {
if (!pJoystick) return -1;
@ -303,10 +241,6 @@ int DinputDevice::UpdateState() {
axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;
auto axesToSquare = KeyMap::MappedAxesForDevice(axis.deviceId);
ApplyNormalization(js, axesToSquare.leftX.axisId, axesToSquare.leftY.axisId);
// Prevent double normalization.
if (axesToSquare.leftX.axisId != axesToSquare.rightX.axisId && axesToSquare.leftX.axisId != axesToSquare.rightY.axisId)
ApplyNormalization(js, axesToSquare.rightX.axisId, axesToSquare.rightY.axisId);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);

View File

@ -151,88 +151,6 @@ struct Stick {
float y;
};
inline float Clampf(float val, float min, float max) {
if (val < min) return min;
if (val > max) return max;
return val;
}
inline float Signf(float val) {
return (float)((0.0f < val) - (val < 0.0f));
}
inline float LinearMapf(float val, float a0, float a1, float b0, float b1) {
return b0 + (((val - a0) * (b1 - b0)) / (a1 - a0));
}
static Stick NormalizedDeadzoneFilter(short x, short y, float dz, int idzm, float idz, float st) {
Stick s(x, y, 1.0f / 32767.0f);
float magnitude = sqrtf(s.x * s.x + s.y * s.y);
if (magnitude > dz) {
// Circle to square mapping (the PSP stick outputs the full -1..1 square of values)
#if 1
// Looks way better than the old one, below, in the axis tester.
float sx = s.x;
float sy = s.y;
float scaleFactor = sqrtf((sx * sx + sy * sy) / std::max(sx * sx, sy * sy));
s.x = sx * scaleFactor;
s.y = sy * scaleFactor;
#else
if (magnitude > 1.0f) {
s.x *= 1.41421f;
s.y *= 1.41421f;
}
#endif
// Linear range mapping (used to invert deadzones)
float md = std::max(dz, idz);
if (idzm == 1)
{
float xSign = Signf(s.x);
if (xSign != 0.0f) {
s.x = LinearMapf(s.x, xSign * dz, xSign, xSign * md, xSign * st);
}
}
else if (idzm == 2)
{
float ySign = Signf(s.y);
if (ySign != 0.0f) {
s.y = LinearMapf(s.y, ySign * dz, ySign, ySign * md, ySign * st);
}
}
else if (idzm == 3)
{
float xNorm = s.x / magnitude;
float yNorm = s.y / magnitude;
float mapMag = LinearMapf(magnitude, dz, 1.0f, md, st);
s.x = xNorm * mapMag;
s.y = yNorm * mapMag;
}
s.x = Clampf(s.x, -1.0f, 1.0f);
s.y = Clampf(s.y, -1.0f, 1.0f);
} else {
s.x = 0.0f;
s.y = 0.0f;
}
return s;
}
bool NormalizedDeadzoneDiffers(short x1, short y1, short x2, short y2, const float dz) {
Stick s1(x1, y1, 1.0f / 32767.0f);
Stick s2(x2, y2, 1.0f / 32767.0f);
float magnitude1 = sqrtf(s1.x * s1.x + s1.y * s1.y);
float magnitude2 = sqrtf(s2.x * s2.x + s2.y * s2.y);
if (magnitude1 > dz || magnitude2 > dz) {
return x1 != x2 || y1 != y2;
}
return false;
}
bool NormalizedDeadzoneDiffers(u8 x1, u8 x2, const u8 thresh) {
if (x1 > thresh || x2 > thresh) {
return x1 != x2;
@ -277,11 +195,6 @@ void XinputDevice::UpdatePad(int pad, const XINPUT_STATE &state, XINPUT_VIBRATIO
ApplyButtons(pad, state);
ApplyVibration(pad, vibration);
const float STICK_DEADZONE = g_Config.fXInputAnalogDeadzone;
const int STICK_INV_MODE = g_Config.iXInputAnalogInverseMode;
const float STICK_INV_DEADZONE = g_Config.fXInputAnalogInverseDeadzone;
const float STICK_SENSITIVITY = g_Config.fXInputAnalogSensitivity;
AxisInput axis;
axis.deviceId = DEVICE_ID_X360_0 + pad;
auto sendAxis = [&](AndroidJoystickAxis axisId, float value) {
@ -290,19 +203,10 @@ void XinputDevice::UpdatePad(int pad, const XINPUT_STATE &state, XINPUT_VIBRATIO
NativeAxis(axis);
};
if (NormalizedDeadzoneDiffers(prevState[pad].Gamepad.sThumbLX, prevState[pad].Gamepad.sThumbLY, state.Gamepad.sThumbLX, state.Gamepad.sThumbLY, STICK_DEADZONE)) {
Stick left = NormalizedDeadzoneFilter(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY, STICK_DEADZONE, STICK_INV_MODE, STICK_INV_DEADZONE, STICK_SENSITIVITY);
sendAxis(JOYSTICK_AXIS_X, left.x);
sendAxis(JOYSTICK_AXIS_Y, left.y);
}
if (NormalizedDeadzoneDiffers(prevState[pad].Gamepad.sThumbRX, prevState[pad].Gamepad.sThumbRY, state.Gamepad.sThumbRX, state.Gamepad.sThumbRY, STICK_DEADZONE)) {
Stick right = NormalizedDeadzoneFilter(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY, STICK_DEADZONE, STICK_INV_MODE, STICK_INV_DEADZONE, STICK_SENSITIVITY);
sendAxis(JOYSTICK_AXIS_Z, right.x);
sendAxis(JOYSTICK_AXIS_RZ, right.y);
}
sendAxis(JOYSTICK_AXIS_X, (float)state.Gamepad.sThumbLX / 32767.0f);
sendAxis(JOYSTICK_AXIS_Y, (float)state.Gamepad.sThumbLY / 32767.0f);
sendAxis(JOYSTICK_AXIS_Z, (float)state.Gamepad.sThumbRX / 32767.0f);
sendAxis(JOYSTICK_AXIS_RZ, (float)state.Gamepad.sThumbRY / 32767.0f);
if (NormalizedDeadzoneDiffers(prevState[pad].Gamepad.bLeftTrigger, state.Gamepad.bLeftTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD)) {
sendAxis(JOYSTICK_AXIS_LTRIGGER, (float)state.Gamepad.bLeftTrigger / 255.0f);

View File

@ -1082,9 +1082,6 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
axis.deviceId = deviceId;
axis.value = value;
float sensitivity = g_Config.fXInputAnalogSensitivity;
axis.value *= sensitivity;
return NativeAxis(axis);
}

View File

@ -718,7 +718,7 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) {
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.flags = 0;
axisInput.axisId = JOYSTICK_AXIS_X;
axisInput.value = value * g_Config.fXInputAnalogSensitivity;
axisInput.value = value;
NativeAxis(axisInput);
};
@ -727,7 +727,7 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) {
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.flags = 0;
axisInput.axisId = JOYSTICK_AXIS_Y;
axisInput.value = -value * g_Config.fXInputAnalogSensitivity;
axisInput.value = -value;
NativeAxis(axisInput);
};
@ -737,7 +737,7 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) {
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.flags = 0;
axisInput.axisId = JOYSTICK_AXIS_Z;
axisInput.value = value * g_Config.fXInputAnalogSensitivity;
axisInput.value = value;
NativeAxis(axisInput);
};
@ -746,7 +746,7 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) {
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.flags = 0;
axisInput.axisId = JOYSTICK_AXIS_RZ;
axisInput.value = -value * g_Config.fXInputAnalogSensitivity;
axisInput.value = -value;
NativeAxis(axisInput);
};
}