From 55dd3d73376746c4fc46df02d8266269a6e4537e Mon Sep 17 00:00:00 2001 From: Nick Michael Date: Thu, 29 Oct 2020 22:11:34 +0000 Subject: [PATCH] Virtual Notch settings and UI for octagonal stick --- .../Config/Mapping/MappingIndicator.cpp | 59 +++++++++++++++++++ .../ControlGroup/AnalogStick.cpp | 6 ++ .../ControllerEmu/ControlGroup/AnalogStick.h | 5 ++ .../ControlGroup/ControlGroup.cpp | 11 ++++ .../ControllerEmu/ControlGroup/ControlGroup.h | 2 + .../InputCommon/ControllerEmu/StickGate.cpp | 22 ++++++- .../InputCommon/ControllerEmu/StickGate.h | 5 ++ 7 files changed, 108 insertions(+), 2 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index a946059cab..fa47c36630 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -169,6 +169,45 @@ QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter) return shape; } +// Constructs a polygon by querying a radius at varying angles: +template +QPolygonF GetPolygonSegmentFromRadiusGetter(F&& radius_getter, double direction, + double segment_size, double segment_depth) +{ + constexpr int shape_point_count = 6; + QPolygonF shape{shape_point_count}; + + // We subtract from the provided direction angle so it's better + // to add Tau here to prevent a negative value instead of + // expecting the function call to be aware of this internal logic + const double center_angle = direction + MathUtil::TAU; + const double center_radius_outer = radius_getter(center_angle); + const double center_radius_inner = center_radius_outer - segment_depth; + + const double lower_angle = center_angle - segment_size / 2; + const double lower_radius_outer = radius_getter(lower_angle); + const double lower_radius_inner = lower_radius_outer - segment_depth; + + const double upper_angle = center_angle + segment_size / 2; + const double upper_radius_outer = radius_getter(upper_angle); + const double upper_radius_inner = upper_radius_outer - segment_depth; + + shape[0] = {std::cos(lower_angle) * (lower_radius_inner), + std::sin(lower_angle) * (lower_radius_inner)}; + shape[1] = {std::cos(center_angle) * (center_radius_inner), + std::sin(center_angle) * (center_radius_inner)}; + shape[2] = {std::cos(upper_angle) * (upper_radius_inner), + std::sin(upper_angle) * (upper_radius_inner)}; + shape[3] = {std::cos(upper_angle) * upper_radius_outer, + std::sin(upper_angle) * upper_radius_outer}; + shape[4] = {std::cos(center_angle) * center_radius_outer, + std::sin(center_angle) * center_radius_outer}; + shape[5] = {std::cos(lower_angle) * lower_radius_outer, + std::sin(lower_angle) * lower_radius_outer}; + + return shape; +} + // Used to check if the user seems to have attempted proper calibration. bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::CalibrationData& data) { @@ -210,6 +249,24 @@ bool IsPointOutsideCalibration(Common::DVec2 point, ControllerEmu::ReshapableInp return current_radius > input_radius * ALLOWED_ERROR; } +void DrawVirtualNotches(QPainter& p, ControllerEmu::ReshapableInput& stick, QColor notch_color) +{ + const double segment_size = stick.GetVirtualNotchSize(); + if (segment_size <= 0.0) + return; + + p.setBrush(notch_color); + for (int i = 0; i < 8; ++i) + { + const double segment_depth = 1.0 - ControllerEmu::MINIMUM_NOTCH_DISTANCE; + const double segment_gap = MathUtil::TAU / 8.0; + const double direction = segment_gap * i; + p.drawPolygon(GetPolygonSegmentFromRadiusGetter( + [&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }, direction, segment_size, + segment_depth)); + } +} + template void GenerateFibonacciSphere(int point_count, F&& callback) { @@ -301,6 +358,8 @@ void ReshapableInputIndicator::DrawReshapableInput( p.drawPolygon( GetPolygonFromRadiusGetter([&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); })); + DrawVirtualNotches(p, stick, gate_pen_color); + const auto center = stick.GetCenter(); p.save(); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp index a0157eec8f..881be39471 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp @@ -65,6 +65,12 @@ OctagonAnalogStick::OctagonAnalogStick(const char* name_, const char* ui_name_, ControlState gate_radius) : AnalogStick(name_, ui_name_, std::make_unique(gate_radius)) { + AddVirtualNotchSetting(&m_virtual_notch_setting, 45); +} + +ControlState OctagonAnalogStick::GetVirtualNotchSize() const +{ + return m_virtual_notch_setting.GetValue() * MathUtil::TAU / 360; } } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h index cafb1dad51..d13fd60a68 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h @@ -33,6 +33,11 @@ class OctagonAnalogStick : public AnalogStick public: OctagonAnalogStick(const char* name, ControlState gate_radius); OctagonAnalogStick(const char* name, const char* ui_name, ControlState gate_radius); + + ControlState GetVirtualNotchSize() const override; + +private: + SettingValue m_virtual_notch_setting; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp index 70975937c9..ee33e32f0f 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.cpp @@ -28,6 +28,17 @@ ControlGroup::ControlGroup(std::string name_, std::string ui_name_, const GroupT { } +void ControlGroup::AddVirtualNotchSetting(SettingValue* value, double max_virtual_notch_deg) +{ + AddSetting(value, + {_trans("Virtual Notches"), + // i18n: The degrees symbol. + _trans("°"), + // i18n: Snap the thumbstick position to the nearest octagonal axis. + _trans("Snap the thumbstick position to the nearest octagonal axis.")}, + 0, 0, max_virtual_notch_deg); +} + void ControlGroup::AddDeadzoneSetting(SettingValue* value, double maximum_deadzone) { AddSetting(value, diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h index 46b8172d14..45c2367c10 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h @@ -82,6 +82,8 @@ public: std::make_unique>(value, details, default_value_, min_value, max_value)); } + void AddVirtualNotchSetting(SettingValue* value, double max_virtual_notch_deg); + void AddDeadzoneSetting(SettingValue* value, double maximum_deadzone); template diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp index 1497471c14..797d19aadf 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp @@ -48,6 +48,16 @@ std::optional GetRayLineIntersection(Common::DVec2 ray, Common::DVec2 po return diff.Cross(-point1) / dot; } +double GetNearestNotch(double angle, double virtual_notch_angle) +{ + constexpr auto sides = 8; + constexpr auto rounding = MathUtil::TAU / sides; + const auto closest_notch = std::round(angle / rounding) * rounding; + const auto angle_diff = + std::fmod(angle - closest_notch + MathUtil::PI, MathUtil::TAU) - MathUtil::PI; + return std::abs(angle_diff) < virtual_notch_angle / 2 ? closest_notch : angle; +} + Common::DVec2 GetPointFromAngleAndLength(double angle, double length) { return Common::DVec2{std::cos(angle), std::sin(angle)} * length; @@ -276,16 +286,24 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta y -= m_center.y; // TODO: make the AtAngle functions work with negative angles: - const ControlState angle = std::atan2(y, x) + MathUtil::TAU; + ControlState angle = std::atan2(y, x) + MathUtil::TAU; - const ControlState gate_max_dist = GetGateRadiusAtAngle(angle); const ControlState input_max_dist = GetInputRadiusAtAngle(angle); + ControlState gate_max_dist = GetGateRadiusAtAngle(angle); // If input radius (from calibration) is zero apply no scaling to prevent division by zero. const ControlState max_dist = input_max_dist ? input_max_dist : gate_max_dist; ControlState dist = Common::DVec2{x, y}.Length() / max_dist; + const double virtual_notch_size = GetVirtualNotchSize(); + + if (virtual_notch_size > 0.0 && dist >= MINIMUM_NOTCH_DISTANCE) + { + angle = GetNearestNotch(angle, virtual_notch_size); + gate_max_dist = GetGateRadiusAtAngle(angle); + } + // If the modifier is pressed, scale the distance by the modifier's value. // This is affected by the modifier's "range" setting which defaults to 50%. if (modifier) diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h index aa5096b271..8a0a0ab100 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.h +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h @@ -15,6 +15,9 @@ namespace ControllerEmu { +// Minimum stick distance from the center before virtual notches are applied. +constexpr ControlState MINIMUM_NOTCH_DISTANCE = 0.9; + // An abstract class representing the plastic shell that limits an analog stick's movement. class StickGate { @@ -85,6 +88,8 @@ public: ControlState GetDeadzonePercentage() const; + virtual ControlState GetVirtualNotchSize() const { return 0.0; }; + virtual ControlState GetGateRadiusAtAngle(double angle) const = 0; virtual ReshapeData GetReshapableState(bool adjusted) = 0; virtual ControlState GetDefaultInputRadiusAtAngle(double ang) const;