Controller: Make center a distinct value

Instead of attaching 128 to one side of the axis, make 128 the
all-zeros/center position, which leaves exactly 127 positions
on either side.

Fixes the center not being reported as center in padtest.
This commit is contained in:
Stenzek
2026-06-08 19:09:40 +10:00
parent cae25968fe
commit 1e6a006bd0
5 changed files with 77 additions and 73 deletions
+26 -35
View File
@@ -186,40 +186,36 @@ void AnalogController::SetBindState(u32 index, float value)
m_half_axis_state[sub_index] = u8_value;
#define MERGE(pos, neg) \
((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)))
const auto prev_axis_state = m_axis_state;
switch (static_cast<HalfAxis>(sub_index))
{
case HalfAxis::LLeft:
case HalfAxis::LRight:
m_axis_state[static_cast<u8>(Axis::LeftX)] = ((m_invert_left_stick & 1u) != 0u) ?
MERGE(HalfAxis::LLeft, HalfAxis::LRight) :
MERGE(HalfAxis::LRight, HalfAxis::LLeft);
m_axis_state[static_cast<u8>(Axis::LeftX)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::LLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LRight)], (m_invert_left_stick & 1));
break;
case HalfAxis::LDown:
case 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);
m_axis_state[static_cast<u8>(Axis::LeftY)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::LUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LDown)], (m_invert_left_stick & 2));
break;
case HalfAxis::RLeft:
case HalfAxis::RRight:
m_axis_state[static_cast<u8>(Axis::RightX)] = ((m_invert_right_stick & 1u) != 0u) ?
MERGE(HalfAxis::RLeft, HalfAxis::RRight) :
MERGE(HalfAxis::RRight, HalfAxis::RLeft);
m_axis_state[static_cast<u8>(Axis::RightX)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::RLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RRight)], (m_invert_right_stick & 1));
break;
case HalfAxis::RDown:
case 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);
m_axis_state[static_cast<u8>(Axis::RightY)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::RUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RDown)], (m_invert_right_stick & 2));
break;
default:
@@ -228,43 +224,38 @@ void AnalogController::SetBindState(u32 index, float value)
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);
pos_x =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::LLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LRight)], (m_invert_left_stick & 1));
pos_y =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::LUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LDown)], (m_invert_left_stick & 2));
}
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);
pos_x =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::RLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RRight)], (m_invert_right_stick & 1));
pos_y =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::RUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RDown)], (m_invert_right_stick & 2));
}
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;
m_axis_state[static_cast<u8>(Axis::LeftX)] = m_axis_state[static_cast<u8>(Axis::LeftY)] = AXIS_CENTER;
else
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = 127;
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = AXIS_CENTER;
}
#undef MERGE_F
}
if (std::memcmp(m_axis_state.data(), prev_axis_state.data(), m_axis_state.size()) != 0)
System::SetRunaheadReplayFlag(true);
#undef MERGE
return;
}
+26 -35
View File
@@ -106,40 +106,36 @@ void AnalogJoystick::SetBindState(u32 index, float value)
m_half_axis_state[sub_index] = u8_value;
#define MERGE(pos, neg) \
((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)))
const auto prev_axis_state = m_axis_state;
switch (static_cast<HalfAxis>(sub_index))
{
case HalfAxis::LLeft:
case HalfAxis::LRight:
m_axis_state[static_cast<u8>(Axis::LeftX)] = ((m_invert_left_stick & 1u) != 0u) ?
MERGE(HalfAxis::LLeft, HalfAxis::LRight) :
MERGE(HalfAxis::LRight, HalfAxis::LLeft);
m_axis_state[static_cast<u8>(Axis::LeftX)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::LLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LRight)], (m_invert_left_stick & 1));
break;
case HalfAxis::LDown:
case 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);
m_axis_state[static_cast<u8>(Axis::LeftY)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::LUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LDown)], (m_invert_left_stick & 2));
break;
case HalfAxis::RLeft:
case HalfAxis::RRight:
m_axis_state[static_cast<u8>(Axis::RightX)] = ((m_invert_right_stick & 1u) != 0u) ?
MERGE(HalfAxis::RLeft, HalfAxis::RRight) :
MERGE(HalfAxis::RRight, HalfAxis::RLeft);
m_axis_state[static_cast<u8>(Axis::RightX)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::RLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RRight)], (m_invert_right_stick & 1));
break;
case HalfAxis::RDown:
case 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);
m_axis_state[static_cast<u8>(Axis::RightY)] =
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::RUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RDown)], (m_invert_right_stick & 2));
break;
default:
@@ -148,43 +144,38 @@ void AnalogJoystick::SetBindState(u32 index, float value)
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);
pos_x =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::LLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LRight)], (m_invert_left_stick & 1));
pos_y =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::LUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::LDown)], (m_invert_left_stick & 2));
}
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);
pos_x =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::RLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RRight)], (m_invert_right_stick & 1));
pos_y =
MergeHalfAxesToFloat(m_half_axis_state[static_cast<size_t>(HalfAxis::RUp)],
m_half_axis_state[static_cast<size_t>(HalfAxis::RDown)], (m_invert_right_stick & 2));
}
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;
m_axis_state[static_cast<u8>(Axis::LeftX)] = m_axis_state[static_cast<u8>(Axis::LeftY)] = AXIS_CENTER;
else
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = 127;
m_axis_state[static_cast<u8>(Axis::RightX)] = m_axis_state[static_cast<u8>(Axis::RightY)] = AXIS_CENTER;
}
#undef MERGE_F
}
if (std::memcmp(m_axis_state.data(), prev_axis_state.data(), m_axis_state.size()) != 0)
System::SetRunaheadReplayFlag(true);
#undef MERGE
return;
}
+14
View File
@@ -239,3 +239,17 @@ bool Controller::CanStartInAnalogMode(ControllerType ctype)
return ((dbentry->supported_controllers & (1u << static_cast<u8>(ctype))) != 0 &&
!dbentry->HasTrait(GameDatabase::Trait::DisableAutoAnalogMode));
}
u8 Controller::MergeHalfAxes(u8 neg_value, u8 pos_value, bool invert)
{
if (invert)
std::swap(neg_value, pos_value);
return static_cast<u8>(128 + (static_cast<u32>(pos_value) / 2) - ((static_cast<u32>(neg_value) + 1) / 2));
}
float Controller::MergeHalfAxesToFloat(u8 neg_value, u8 pos_value, bool invert)
{
const float result = (static_cast<s32>(pos_value) - static_cast<s32>(neg_value)) / 255.0f;
return (invert ? -result : result);
}
+9
View File
@@ -53,6 +53,9 @@ public:
static constexpr float DEFAULT_STICK_SENSITIVITY = 1.33f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.25f;
/// Center position when dealing with axis values.
static constexpr u8 AXIS_CENTER = 0x80;
explicit Controller(u32 index);
virtual ~Controller();
@@ -119,6 +122,12 @@ public:
/// Returns true if automatic analog mode can be used.
static bool CanStartInAnalogMode(ControllerType ctype);
/// Converts a 0..255 half-axis value to an unsigned 8-bit value, with 128 indicating center.
static u8 MergeHalfAxes(u8 neg_value, u8 pos_value, bool invert);
/// Converts a 0..255 half-axis value to a normalized floating-point value, with 0 indicating center.
static float MergeHalfAxesToFloat(u8 neg_value, u8 pos_value, bool invert);
protected:
u32 m_index;
};
+2 -3
View File
@@ -171,10 +171,9 @@ void NeGconRumble::SetBindState(u32 index, float value)
m_half_axis_state[index - static_cast<u32>(Button::Count)] =
static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
// Merge left/right. Seems to be inverted.
m_axis_state[static_cast<u32>(Axis::Steering)] =
((m_half_axis_state[1] != 0) ? (127u + ((m_half_axis_state[1] + 1u) / 2u)) :
(127u - (m_half_axis_state[0] / 2u)));
MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::SteeringLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::SteeringRight)], false);
}
else if (index >= static_cast<u32>(Button::Count))
{