From 82a3aa5ff67b0239b85d18a6da77d3fd2900640c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 3 Feb 2020 19:45:36 -0600 Subject: [PATCH] InputCommon: Add "Dead Zone" setting to raw gyro inputs. --- .../Config/Mapping/MappingIndicator.cpp | 88 +++++++++++-------- .../ControlGroup/IMUGyroscope.cpp | 31 ++++++- .../ControllerEmu/ControlGroup/IMUGyroscope.h | 8 ++ 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index 2fd39b2a5b..61b291c96b 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -754,33 +754,34 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*) p.setBrush(Qt::NoBrush); p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); - // Red dot upright target. - p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); - p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + p.setPen(Qt::NoPen); // Red dot. const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST}; if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE) { - p.setPen(Qt::NoPen); p.setBrush(GetAdjustedInputColor()); p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); } - // Blue dot target. - p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); - p.setBrush(Qt::NoBrush); - p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); - // Blue dot. const auto point2 = -point; if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE) { - p.setPen(Qt::NoPen); p.setBrush(Qt::blue); p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); } + p.setBrush(Qt::NoBrush); + + // Red dot upright target. + p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); + p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + + // Blue dot target. + p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); + p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + // Only draw g-force text if acceleration data is present. if (!accel_state.has_value()) return; @@ -802,6 +803,7 @@ GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group) void GyroMappingIndicator::paintEvent(QPaintEvent*) { const auto gyro_state = m_gyro_group.GetState(); + const auto raw_gyro_state = m_gyro_group.GetRawState(); const auto angular_velocity = gyro_state.value_or(Common::Vec3{}); m_state *= Common::Matrix33::FromQuaternion(angular_velocity.x / -INDICATOR_UPDATE_FREQ / 2, @@ -810,8 +812,8 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*) // Reset orientation when stable for a bit: constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ; - // This works well with my DS4 but a potentially noisy device might not behave. - const bool is_stable = angular_velocity.Length() < MathUtil::TAU / 30; + // Consider device stable when data (with deadzone applied) is zero. + const bool is_stable = !angular_velocity.LengthSquared(); if (!is_stable) m_stable_steps = 0; @@ -839,10 +841,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*) p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::SmoothPixmapTransform, true); + // Deadzone. + if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value) + { + static constexpr auto DEADZONE_DRAW_SIZE = 1 - SPHERE_SIZE; + static constexpr auto DEADZONE_DRAW_BOTTOM = 1; + + p.setPen(GetDeadZonePen()); + p.setBrush(GetDeadZoneBrush()); + p.scale(-1.0, 1.0); + p.drawRect(-scale, DEADZONE_DRAW_BOTTOM * scale, scale * 2, -scale * DEADZONE_DRAW_SIZE); + p.scale(-1.0, 1.0); + + if (gyro_state.has_value()) + { + const auto max_velocity = std::max( + {std::abs(raw_gyro_state.x), std::abs(raw_gyro_state.y), std::abs(raw_gyro_state.z)}); + const auto max_velocity_line_y = + std::min(max_velocity / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0); + p.setPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS)); + p.drawLine(-scale, max_velocity_line_y * -scale, scale, max_velocity_line_y * -scale); + + // Sphere background. + p.setPen(Qt::NoPen); + p.setBrush(GetBBoxBrush()); + p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); + } + } + + // Sphere dots. p.setPen(Qt::NoPen); p.setBrush(GetRawInputColor()); - GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&, this](const Common::Vec3& point) { + GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) { const auto pt = rotation * point; if (pt.y > 0) @@ -850,49 +881,36 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*) }); // Sphere outline. - p.setPen(GetRawInputColor()); + p.setPen(is_stable ? GetRawInputColor() : GetAdjustedInputColor()); p.setBrush(Qt::NoBrush); p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); - // Red dot upright target. - p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); - p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + p.setPen(Qt::NoPen); // Red dot. const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST}; if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE) { - p.setPen(Qt::NoPen); p.setBrush(GetAdjustedInputColor()); p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); } - - // Blue dot target. - p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); - p.setBrush(Qt::NoBrush); - p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); - // Blue dot. const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0}; if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE) { - p.setPen(Qt::NoPen); p.setBrush(Qt::blue); p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); } - // Only draw text if data is present. - if (!gyro_state.has_value()) - return; + p.setBrush(Qt::NoBrush); - // Angle of red dot from starting position. - const auto angle = std::acos(point.Normalized().Dot({0, 0, -1})); + // Red dot upright target. + p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); + p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); - // Angle text: - p.setPen(GetTextColor()); - p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight, - // i18n: "°" is the symbol for degrees (angular measurement). - QString::fromStdString(fmt::format("{:.2f} °", angle / MathUtil::TAU * 360))); + // Blue dot target. + p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); + p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); } void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point) diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp index 84f9f20821..6109f20b8c 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp @@ -7,6 +7,7 @@ #include #include "Common/Common.h" +#include "Common/MathUtil.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/Control/Control.h" @@ -23,6 +24,21 @@ IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name) AddInput(Translate, _trans("Roll Right")); AddInput(Translate, _trans("Yaw Left")); AddInput(Translate, _trans("Yaw Right")); + + AddSetting(&m_deadzone_setting, + {_trans("Dead Zone"), + // i18n: "°/s" is the symbol for degrees (angular measurement) divided by seconds. + _trans("°/s"), + // i18n: Refers to the dead-zone setting of gyroscope input. + _trans("Angular velocity to ignore.")}, + 1, 0, 180); +} + +auto IMUGyroscope::GetRawState() const -> StateData +{ + return StateData(controls[1]->GetState() - controls[0]->GetState(), + controls[2]->GetState() - controls[3]->GetState(), + controls[4]->GetState() - controls[5]->GetState()); } std::optional IMUGyroscope::GetState() const @@ -30,11 +46,18 @@ std::optional IMUGyroscope::GetState() const if (controls[0]->control_ref->BoundCount() == 0) return std::nullopt; - StateData state; - state.x = (controls[1]->GetState() - controls[0]->GetState()); - state.y = (controls[2]->GetState() - controls[3]->GetState()); - state.z = (controls[4]->GetState() - controls[5]->GetState()); + auto state = GetRawState(); + + // Apply "deadzone". + for (auto& c : state.data) + c *= std::abs(c) > GetDeadzone(); + return state; } +ControlState IMUGyroscope::GetDeadzone() const +{ + return m_deadzone_setting.GetValue() / 360 * MathUtil::TAU; +} + } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h index c173c2f8b2..1258a3eb76 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h @@ -9,6 +9,7 @@ #include "Common/Matrix.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" +#include "InputCommon/ControllerEmu/Setting/NumericSetting.h" namespace ControllerEmu { @@ -19,6 +20,13 @@ public: IMUGyroscope(std::string name, std::string ui_name); + StateData GetRawState() const; std::optional GetState() const; + + // Value is in rad/s. + ControlState GetDeadzone() const; + +private: + SettingValue m_deadzone_setting; }; } // namespace ControllerEmu