Controllers: Make deadzone circular

This commit is contained in:
Connor McLaughlin 2022-10-21 21:14:27 +10:00
parent ca571f8a78
commit f5b7311a45
7 changed files with 133 additions and 17 deletions

View file

@ -157,8 +157,7 @@ void AnalogController::SetBindState(u32 index, float value)
if (sub_index >= static_cast<u32>(m_half_axis_state.size())) if (sub_index >= static_cast<u32>(m_half_axis_state.size()))
return; return;
value = ApplyAnalogDeadzoneSensitivity(m_analog_deadzone, m_analog_sensitivity, value); const u8 u8_value = static_cast<u8>(std::clamp(value * m_analog_sensitivity * 255.0f, 0.0f, 255.0f));
const u8 u8_value = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
if (u8_value == m_half_axis_state[sub_index]) if (u8_value == m_half_axis_state[sub_index])
return; return;
@ -168,7 +167,6 @@ void AnalogController::SetBindState(u32 index, float value)
#define MERGE(pos, neg) \ #define MERGE(pos, neg) \
((m_half_axis_state[static_cast<u32>(pos)] != 0) ? (127u + ((m_half_axis_state[static_cast<u32>(pos)] + 1u) / 2u)) : \ ((m_half_axis_state[static_cast<u32>(pos)] != 0) ? (127u + ((m_half_axis_state[static_cast<u32>(pos)] + 1u) / 2u)) : \
(127u - (m_half_axis_state[static_cast<u32>(neg)] / 2u))) (127u - (m_half_axis_state[static_cast<u32>(neg)] / 2u)))
switch (static_cast<HalfAxis>(sub_index)) switch (static_cast<HalfAxis>(sub_index))
{ {
case HalfAxis::LLeft: case HalfAxis::LLeft:
@ -203,6 +201,41 @@ void AnalogController::SetBindState(u32 index, float value)
break; break;
} }
if (m_analog_deadzone > 0.0f)
{
#define MERGE_F(pos, neg) \
((m_half_axis_state[static_cast<u32>(pos)] != 0) ? \
(static_cast<float>(m_half_axis_state[static_cast<u32>(pos)]) / 255.0f) : \
(static_cast<float>(m_half_axis_state[static_cast<u32>(neg)]) / -255.0f))
float pos_x, pos_y;
if (static_cast<HalfAxis>(sub_index) < HalfAxis::RLeft)
{
pos_x = ((m_invert_left_stick & 1u) != 0u) ? MERGE_F(HalfAxis::LLeft, HalfAxis::LRight) :
MERGE_F(HalfAxis::LRight, HalfAxis::LLeft);
pos_y = ((m_invert_left_stick & 2u) != 0u) ? MERGE_F(HalfAxis::LUp, HalfAxis::LDown) :
MERGE_F(HalfAxis::LDown, HalfAxis::LUp);
}
else
{
pos_x = ((m_invert_right_stick & 1u) != 0u) ? MERGE_F(HalfAxis::RLeft, HalfAxis::RRight) :
MERGE_F(HalfAxis::RRight, HalfAxis::RLeft);
;
pos_y = ((m_invert_right_stick & 2u) != 0u) ? MERGE_F(HalfAxis::RUp, HalfAxis::RDown) :
MERGE_F(HalfAxis::RDown, HalfAxis::RUp);
}
if (InCircularDeadzone(m_analog_deadzone, pos_x, pos_y))
{
// Set to 127 (center).
if (static_cast<HalfAxis>(sub_index) < HalfAxis::RLeft)
m_axis_state[static_cast<u8>(Axis::LeftX)] = m_axis_state[static_cast<u8>(Axis::LeftY)] = 127;
else
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = 127;
}
#undef MERGE_F
}
#undef MERGE #undef MERGE
return; return;
@ -828,7 +861,6 @@ static const SettingInfo s_settings[] = {
"Inverts the direction of the left analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, "Inverts the direction of the left analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f},
{SettingInfo::Type::IntegerList, "InvertRightStick", "Invert Right Stick", {SettingInfo::Type::IntegerList, "InvertRightStick", "Invert Right Stick",
"Inverts the direction of the right analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, "Inverts the direction of the right analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f},
}; };
const Controller::ControllerInfo AnalogController::INFO = {ControllerType::AnalogController, const Controller::ControllerInfo AnalogController::INFO = {ControllerType::AnalogController,

View file

@ -99,8 +99,7 @@ void AnalogJoystick::SetBindState(u32 index, float value)
if (sub_index >= static_cast<u32>(m_half_axis_state.size())) if (sub_index >= static_cast<u32>(m_half_axis_state.size()))
return; return;
value = ApplyAnalogDeadzoneSensitivity(m_analog_deadzone, m_analog_sensitivity, value); const u8 u8_value = static_cast<u8>(std::clamp(value * m_analog_sensitivity * 255.0f, 0.0f, 255.0f));
const u8 u8_value = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
if (u8_value != m_half_axis_state[sub_index]) if (u8_value != m_half_axis_state[sub_index])
System::SetRunaheadReplayFlag(); System::SetRunaheadReplayFlag();
@ -114,28 +113,71 @@ void AnalogJoystick::SetBindState(u32 index, float value)
{ {
case HalfAxis::LLeft: case HalfAxis::LLeft:
case HalfAxis::LRight: case HalfAxis::LRight:
m_axis_state[static_cast<u8>(Axis::LeftX)] = MERGE(HalfAxis::LRight, HalfAxis::LLeft); m_axis_state[static_cast<u8>(Axis::LeftX)] = ((m_invert_left_stick & 1u) != 0u) ?
MERGE(HalfAxis::LLeft, HalfAxis::LRight) :
MERGE(HalfAxis::LRight, HalfAxis::LLeft);
break; break;
case HalfAxis::LDown: case HalfAxis::LDown:
case HalfAxis::LUp: case HalfAxis::LUp:
m_axis_state[static_cast<u8>(Axis::LeftY)] = MERGE(HalfAxis::LDown, HalfAxis::LUp); m_axis_state[static_cast<u8>(Axis::LeftY)] = ((m_invert_left_stick & 2u) != 0u) ?
MERGE(HalfAxis::LUp, HalfAxis::LDown) :
MERGE(HalfAxis::LDown, HalfAxis::LUp);
break; break;
case HalfAxis::RLeft: case HalfAxis::RLeft:
case HalfAxis::RRight: case HalfAxis::RRight:
m_axis_state[static_cast<u8>(Axis::RightX)] = MERGE(HalfAxis::RRight, HalfAxis::RLeft); m_axis_state[static_cast<u8>(Axis::RightX)] = ((m_invert_right_stick & 1u) != 0u) ?
MERGE(HalfAxis::RLeft, HalfAxis::RRight) :
MERGE(HalfAxis::RRight, HalfAxis::RLeft);
break; break;
case HalfAxis::RDown: case HalfAxis::RDown:
case HalfAxis::RUp: case HalfAxis::RUp:
m_axis_state[static_cast<u8>(Axis::RightY)] = MERGE(HalfAxis::RDown, HalfAxis::RUp); m_axis_state[static_cast<u8>(Axis::RightY)] = ((m_invert_right_stick & 2u) != 0u) ?
MERGE(HalfAxis::RUp, HalfAxis::RDown) :
MERGE(HalfAxis::RDown, HalfAxis::RUp);
break; break;
default: default:
break; break;
} }
if (m_analog_deadzone > 0.0f)
{
#define MERGE_F(pos, neg) \
((m_half_axis_state[static_cast<u32>(pos)] != 0) ? \
(static_cast<float>(m_half_axis_state[static_cast<u32>(pos)]) / 255.0f) : \
(static_cast<float>(m_half_axis_state[static_cast<u32>(neg)]) / -255.0f))
float pos_x, pos_y;
if (static_cast<HalfAxis>(sub_index) < HalfAxis::RLeft)
{
pos_x = ((m_invert_left_stick & 1u) != 0u) ? MERGE_F(HalfAxis::LLeft, HalfAxis::LRight) :
MERGE_F(HalfAxis::LRight, HalfAxis::LLeft);
pos_y = ((m_invert_left_stick & 2u) != 0u) ? MERGE_F(HalfAxis::LUp, HalfAxis::LDown) :
MERGE_F(HalfAxis::LDown, HalfAxis::LUp);
}
else
{
pos_x = ((m_invert_right_stick & 1u) != 0u) ? MERGE_F(HalfAxis::RLeft, HalfAxis::RRight) :
MERGE_F(HalfAxis::RRight, HalfAxis::RLeft);
;
pos_y = ((m_invert_right_stick & 2u) != 0u) ? MERGE_F(HalfAxis::RUp, HalfAxis::RDown) :
MERGE_F(HalfAxis::RDown, HalfAxis::RUp);
}
if (InCircularDeadzone(m_analog_deadzone, pos_x, pos_y))
{
// Set to 127 (center).
if (static_cast<HalfAxis>(sub_index) < HalfAxis::RLeft)
m_axis_state[static_cast<u8>(Axis::LeftX)] = m_axis_state[static_cast<u8>(Axis::LeftY)] = 127;
else
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = 127;
}
#undef MERGE_F
}
#undef MERGE #undef MERGE
return; return;
@ -329,6 +371,11 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#undef BUTTON #undef BUTTON
}; };
static const char* s_invert_settings[] = {TRANSLATABLE("AnalogJoystick", "Not Inverted"),
TRANSLATABLE("AnalogJoystick", "Invert Left/Right"),
TRANSLATABLE("AnalogJoystick", "Invert Up/Down"),
TRANSLATABLE("AnalogJoystick", "Invert Left/Right + Up/Down"), nullptr};
static const SettingInfo s_settings[] = { static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATABLE("AnalogJoystick", "Analog Deadzone"), {SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATABLE("AnalogJoystick", "Analog Deadzone"),
TRANSLATABLE("AnalogJoystick", TRANSLATABLE("AnalogJoystick",
@ -339,7 +386,12 @@ static const SettingInfo s_settings[] = {
"AnalogJoystick", "AnalogJoystick",
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent " "Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."), "controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f}}; "1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f},
{SettingInfo::Type::IntegerList, "InvertLeftStick", "Invert Left Stick",
"Inverts the direction of the left analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f},
{SettingInfo::Type::IntegerList, "InvertRightStick", "Invert Right Stick",
"Inverts the direction of the right analog stick.", "0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f},
};
const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJoystick, const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJoystick,
"AnalogJoystick", "AnalogJoystick",
@ -356,4 +408,6 @@ void AnalogJoystick::LoadSettings(SettingsInterface& si, const char* section)
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f); m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);
m_analog_sensitivity = m_analog_sensitivity =
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f); std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);
m_invert_left_stick = static_cast<u8>(si.GetIntValue(section, "InvertLeftStick", 0));
m_invert_right_stick = static_cast<u8>(si.GetIntValue(section, "InvertRightStick", 0));
} }

View file

@ -94,6 +94,8 @@ private:
float m_analog_deadzone = 0.0f; float m_analog_deadzone = 0.0f;
float m_analog_sensitivity = 1.33f; float m_analog_sensitivity = 1.33f;
u8 m_invert_left_stick = 0;
u8 m_invert_right_stick = 0;
// On original hardware, the mode toggle is a switch rather than a button, so we'll enable Analog Mode by default // On original hardware, the mode toggle is a switch rather than a button, so we'll enable Analog Mode by default
bool m_analog_mode = true; bool m_analog_mode = true;

View file

@ -230,3 +230,21 @@ std::string Controller::GetSettingsSection(u32 pad)
{ {
return fmt::format("Pad{}", pad + 1u); return fmt::format("Pad{}", pad + 1u);
} }
bool Controller::InCircularDeadzone(float deadzone, float pos_x, float pos_y)
{
if (pos_x == 0.0f && pos_y == 0.0f)
return false;
// Compute the angle at the given position in the stick's square bounding box.
const float theta = std::atan2(pos_y, pos_x);
// Compute the position that the edge of the circle would be at, given the angle.
const float dz_x = std::cos(theta) * deadzone;
const float dz_y = std::sin(theta) * deadzone;
// We're in the deadzone if our position is less than the circle edge.
const bool in_x = (pos_x < 0.0f) ? (pos_x > dz_x) : (pos_x <= dz_x);
const bool in_y = (pos_y < 0.0f) ? (pos_y > dz_y) : (pos_y <= dz_y);
return (in_x && in_y);
}

View file

@ -140,6 +140,9 @@ public:
return (value < deadzone) ? 0.0f : ((value - deadzone) / (1.0f - deadzone) * sensitivity); return (value < deadzone) ? 0.0f : ((value - deadzone) / (1.0f - deadzone) * sensitivity);
} }
/// Returns true if the specified coordinates are inside a circular deadzone.
static bool InCircularDeadzone(float deadzone, float pos_x, float pos_y);
protected: protected:
u32 m_index; u32 m_index;
}; };

View file

@ -71,7 +71,9 @@ void NeGcon::SetBindState(u32 index, float value)
if (index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) || if (index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) ||
index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight))) index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight)))
{ {
value = ApplyAnalogDeadzoneSensitivity(m_steering_deadzone, 1.0f, value); value *= m_steering_sensitivity;
if (value < m_steering_deadzone)
value = 0.0f;
m_half_axis_state[index - static_cast<u32>(Button::Count)] = m_half_axis_state[index - static_cast<u32>(Button::Count)] =
static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f)); static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
@ -249,10 +251,14 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#undef BUTTON #undef BUTTON
}; };
static const SettingInfo s_settings[] = {{SettingInfo::Type::Float, "SteeringDeadzone", static const SettingInfo s_settings[] = {
TRANSLATABLE("NeGcon", "Steering Axis Deadzone"), {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATABLE("NeGcon", "Steering Axis Deadzone"),
TRANSLATABLE("NeGcon", "Sets deadzone size for steering axis."), "0.00f", TRANSLATABLE("NeGcon", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f", "%.0f%%",
"0.00f", "0.99f", "0.01f", "%.0f%%", nullptr, 100.0f}}; nullptr, 100.0f},
{SettingInfo::Type::Float, "SteeringSensitivity", TRANSLATABLE("NeGcon", "Steering Axis Sensitivity"),
TRANSLATABLE("NeGcon", "Sets the steering axis scaling factor."), "1.00f", "0.01f", "2.00f", "0.01f", "%.0f%%",
nullptr, 100.0f},
};
const Controller::ControllerInfo NeGcon::INFO = {ControllerType::NeGcon, const Controller::ControllerInfo NeGcon::INFO = {ControllerType::NeGcon,
"NeGcon", "NeGcon",
@ -267,4 +273,5 @@ void NeGcon::LoadSettings(SettingsInterface& si, const char* section)
{ {
Controller::LoadSettings(si, section); Controller::LoadSettings(si, section);
m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f); m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f);
m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f);
} }

View file

@ -88,5 +88,5 @@ private:
TransferState m_transfer_state = TransferState::Idle; TransferState m_transfer_state = TransferState::Idle;
float m_steering_deadzone = 0.00f; float m_steering_deadzone = 0.00f;
float m_steering_sensitivity = 1.00f;
}; };