From 7a00f55cfa6e65e58ebf9f279bfff61f8b0f8c13 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 09:10:32 -0600 Subject: [PATCH] ControllerEmu::Cursor: Add input radius/shape settings to IR Cursor mappings to allow use of round inputs in absolute mode. Make relative input option obey the center/width/height settings. Make the mapping indicator pretty and actually show what the relative/center/w/h settings are doing. --- .../Config/Mapping/MappingIndicator.cpp | 216 ++++++++++-------- .../Config/Mapping/MappingIndicator.h | 24 +- .../ControlGroup/AnalogStick.cpp | 11 +- .../ControllerEmu/ControlGroup/AnalogStick.h | 7 +- .../ControllerEmu/ControlGroup/Cursor.cpp | 151 +++++++----- .../ControllerEmu/ControlGroup/Cursor.h | 40 +++- .../ControllerEmu/ControlGroup/Tilt.cpp | 33 ++- .../ControllerEmu/ControlGroup/Tilt.h | 11 +- .../InputCommon/ControllerEmu/StickGate.cpp | 4 +- .../InputCommon/ControllerEmu/StickGate.h | 6 +- 10 files changed, 292 insertions(+), 211 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index c2ae2e6135..78cbf6ced7 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -14,6 +14,7 @@ #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/Control/Control.h" +#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h" #include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h" #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/ControllerInterface/Device.h" @@ -42,106 +43,11 @@ MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group { setMinimumHeight(128); - switch (m_group->type) - { - case ControllerEmu::GroupType::Cursor: - BindCursorControls(false); - break; - case ControllerEmu::GroupType::Stick: - // Nothing needed: - break; - case ControllerEmu::GroupType::Tilt: - BindCursorControls(true); - break; - case ControllerEmu::GroupType::MixedTriggers: - // Nothing needed: - break; - default: - break; - } - m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, [this] { repaint(); }); m_timer->start(1000 / 30); } -void MappingIndicator::BindCursorControls(bool tilt) -{ - m_cursor_up = m_group->controls[0]->control_ref.get(); - m_cursor_down = m_group->controls[1]->control_ref.get(); - m_cursor_left = m_group->controls[2]->control_ref.get(); - m_cursor_right = m_group->controls[3]->control_ref.get(); - - if (!tilt) - { - m_cursor_forward = m_group->controls[4]->control_ref.get(); - m_cursor_backward = m_group->controls[5]->control_ref.get(); - - m_cursor_center = m_group->numeric_settings[0].get(); - m_cursor_width = m_group->numeric_settings[1].get(); - m_cursor_height = m_group->numeric_settings[2].get(); - m_cursor_deadzone = m_group->numeric_settings[3].get(); - } - else - { - m_cursor_deadzone = m_group->numeric_settings[0].get(); - } -} - -static ControlState PollControlState(ControlReference* ref) -{ - Settings::Instance().SetControllerStateNeeded(true); - - auto state = ref->State(); - - Settings::Instance().SetControllerStateNeeded(false); - - if (state != 0) - return state; - else - return 0; -} - -void MappingIndicator::DrawCursor(bool tilt) -{ - float centerx = width() / 2., centery = height() / 2.; - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing, true); - p.setRenderHint(QPainter::SmoothPixmapTransform, true); - - float width = 64, height = 64; - float deadzone = m_cursor_deadzone->GetValue() * 48; - - if (!tilt) - { - float depth = centery - PollControlState(m_cursor_forward) * this->height() / 2.5 + - PollControlState(m_cursor_backward) * this->height() / 2.5; - - p.fillRect(0, depth, this->width(), 4, Qt::gray); - - width *= m_cursor_width->GetValue(); - height *= m_cursor_height->GetValue(); - } - - float curx = centerx - 4 - std::min(PollControlState(m_cursor_left), 0.5) * width + - std::min(PollControlState(m_cursor_right), 0.5) * width, - cury = centery - 4 - std::min(PollControlState(m_cursor_up), 0.5) * height + - std::min(PollControlState(m_cursor_down), 0.5) * height; - - // Draw background - p.setBrush(Qt::white); - p.setPen(Qt::black); - p.drawRect(centerx - (width / 2), centery - (height / 2), width, height); - - // Draw deadzone - p.setBrush(Qt::lightGray); - p.drawEllipse(centerx - (deadzone / 2), centery - (deadzone / 2), deadzone, deadzone); - - // Draw cursor - p.fillRect(curx, cury, 8, 8, Qt::red); -} - // Constructs a polygon by querying a radius at varying angles: template QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale) @@ -163,18 +69,128 @@ QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale) return shape; } +void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor) +{ + const QColor tv_brush_color = 0xaed6f1; + const QColor tv_pen_color = tv_brush_color.darker(125); + + // TODO: This SetControllerStateNeeded interface leaks input into the game + // We should probably hold the mutex for UI updates. + Settings::Instance().SetControllerStateNeeded(true); + const auto raw_coord = cursor.GetState(false); + const auto adj_coord = cursor.GetState(true); + Settings::Instance().SetControllerStateNeeded(false); + + // Bounding box size: + const double scale = height() / 2.5; + + QPainter p(this); + p.translate(width() / 2, height() / 2); + + // Bounding box. + p.setBrush(BBOX_BRUSH_COLOR); + p.setPen(BBOX_PEN_COLOR); + p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1); + + // UI y-axis is opposite that of stick. + p.scale(1.0, -1.0); + + // Enable AA after drawing bounding box. + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + + // Deadzone for Z (forward/backward): + const double deadzone = cursor.numeric_settings[cursor.SETTING_DEADZONE]->GetValue(); + if (deadzone > 0.0) + { + p.setPen(DEADZONE_COLOR); + p.setBrush(DEADZONE_BRUSH); + p.drawRect(QRectF(-scale, -deadzone * scale, scale * 2, deadzone * scale * 2)); + } + + // Raw Z: + p.setPen(Qt::NoPen); + p.setBrush(RAW_INPUT_COLOR); + p.drawRect( + QRectF(-scale, raw_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS)); + + // Adjusted Z: + if (adj_coord.z) + { + p.setBrush(ADJ_INPUT_COLOR); + p.drawRect( + QRectF(-scale, adj_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS)); + } + + // TV screen or whatever you want to call this: + constexpr double tv_scale = 0.75; + constexpr double center_scale = 2.0 / 3.0; + + const double tv_center = (cursor.numeric_settings[cursor.SETTING_CENTER]->GetValue() - 0.5); + const double tv_width = cursor.numeric_settings[cursor.SETTING_WIDTH]->GetValue(); + const double tv_height = cursor.numeric_settings[cursor.SETTING_HEIGHT]->GetValue(); + + p.setPen(tv_pen_color); + p.setBrush(tv_brush_color); + auto gate_polygon = GetPolygonFromRadiusGetter( + [&cursor](double ang) { return cursor.GetGateRadiusAtAngle(ang); }, scale); + for (auto& pt : gate_polygon) + { + pt = {pt.x() * tv_width, pt.y() * tv_height + tv_center * center_scale * scale}; + pt *= tv_scale; + } + p.drawPolygon(gate_polygon); + + // Deadzone. + p.setPen(DEADZONE_COLOR); + p.setBrush(DEADZONE_BRUSH); + p.drawPolygon(GetPolygonFromRadiusGetter( + [&cursor](double ang) { return cursor.GetDeadzoneRadiusAtAngle(ang); }, scale)); + + // Input shape. + p.setPen(INPUT_SHAPE_PEN); + p.setBrush(Qt::NoBrush); + p.drawPolygon(GetPolygonFromRadiusGetter( + [&cursor](double ang) { return cursor.GetInputRadiusAtAngle(ang); }, scale)); + + // Raw stick position. + p.setPen(Qt::NoPen); + p.setBrush(RAW_INPUT_COLOR); + p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + + // Adjusted cursor position if not hidden: + if (adj_coord.x < 10000) + { + p.setPen(Qt::NoPen); + p.setBrush(ADJ_INPUT_COLOR); + const QPointF pt(adj_coord.x / 2.0, (adj_coord.y - tv_center) / 2.0 + tv_center * center_scale); + p.drawEllipse(pt * scale * tv_scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); + } +} + void MappingIndicator::DrawReshapableInput(ControllerEmu::ReshapableInput& stick) { - // Make the c-stick yellow: + // Some hacks for pretty colors: const bool is_c_stick = m_group->name == "C-Stick"; - const QColor gate_brush_color = is_c_stick ? Qt::yellow : Qt::lightGray; + const bool is_tilt = m_group->name == "Tilt"; + const bool is_cursor = m_group->name == "IR"; + + QColor gate_brush_color = Qt::lightGray; + + if (is_c_stick) + gate_brush_color = Qt::yellow; + else if (is_tilt) + gate_brush_color = 0xa2d9ce; + else if (is_cursor) + gate_brush_color = 0xaed6f1; + const QColor gate_pen_color = gate_brush_color.darker(125); // TODO: This SetControllerStateNeeded interface leaks input into the game // We should probably hold the mutex for UI updates. Settings::Instance().SetControllerStateNeeded(true); - const auto raw_coord = stick.GetState(false); - const auto adj_coord = stick.GetState(true); + const auto raw_coord = stick.GetReshapableState(false); + const auto adj_coord = stick.GetReshapableState(true); Settings::Instance().SetControllerStateNeeded(false); // Bounding box size: @@ -329,7 +345,7 @@ void MappingIndicator::paintEvent(QPaintEvent*) switch (m_group->type) { case ControllerEmu::GroupType::Cursor: - DrawCursor(false); + DrawCursor(*static_cast(m_group)); break; case ControllerEmu::GroupType::Stick: case ControllerEmu::GroupType::Tilt: diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h index 9fcb4296df..3a863ec788 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h @@ -6,47 +6,31 @@ #include -#include "InputCommon/ControllerEmu/StickGate.h" - namespace ControllerEmu { class Control; class ControlGroup; +class Cursor; class NumericSetting; +class ReshapableInput; } // namespace ControllerEmu class QPaintEvent; class QTimer; -class ControlReference; - class MappingIndicator : public QWidget { public: explicit MappingIndicator(ControllerEmu::ControlGroup* group); private: - void BindCursorControls(bool tilt); - - void DrawCursor(bool tilt); + void DrawCursor(ControllerEmu::Cursor& cursor); void DrawReshapableInput(ControllerEmu::ReshapableInput& stick); void DrawMixedTriggers(); void paintEvent(QPaintEvent*) override; + ControllerEmu::ControlGroup* m_group; - // Cursor settings - ControlReference* m_cursor_up; - ControlReference* m_cursor_down; - ControlReference* m_cursor_left; - ControlReference* m_cursor_right; - ControlReference* m_cursor_forward; - ControlReference* m_cursor_backward; - - ControllerEmu::NumericSetting* m_cursor_center; - ControllerEmu::NumericSetting* m_cursor_width; - ControllerEmu::NumericSetting* m_cursor_height; - ControllerEmu::NumericSetting* m_cursor_deadzone; - QTimer* m_timer; }; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp index e072f6b7bd..de9a75dd23 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp @@ -37,10 +37,10 @@ AnalogStick::AnalogStick(const char* const name_, const char* const ui_name_, AddReshapingSettings(GetGateRadiusAtAngle(0.0), 0.0, 50); } -AnalogStick::StateData AnalogStick::GetState(bool adjusted) +AnalogStick::StateData AnalogStick::GetReshapableState(bool adjusted) { - ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State(); - ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + const ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + const ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State(); // Return raw values. (used in UI) if (!adjusted) @@ -51,6 +51,11 @@ AnalogStick::StateData AnalogStick::GetState(bool adjusted) return Reshape(x, y, modifier); } +AnalogStick::StateData AnalogStick::GetState() +{ + return GetReshapableState(true); +} + ControlState AnalogStick::GetGateRadiusAtAngle(double ang) const { return m_stick_gate->GetRadiusAtAngle(ang); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h index 25de898331..bbf8236066 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h @@ -13,13 +13,16 @@ namespace ControllerEmu class AnalogStick : public ReshapableInput { public: + typedef ReshapeData StateData; + AnalogStick(const char* name, std::unique_ptr&& stick_gate); AnalogStick(const char* name, const char* ui_name, std::unique_ptr&& stick_gate); - StateData GetState(bool adjusted = true) override; - + StateData GetReshapableState(bool adjusted) final override; ControlState GetGateRadiusAtAngle(double ang) const override; + StateData GetState(); + private: std::unique_ptr m_stick_gate; }; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp index b0633ec6a5..fc373f647c 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp @@ -21,7 +21,8 @@ namespace ControllerEmu { -Cursor::Cursor(const std::string& name_) : ControlGroup(name_, GroupType::Cursor) +Cursor::Cursor(const std::string& name_) + : ReshapableInput(name_, name_, GroupType::Cursor), m_last_update(Clock::now()) { for (auto& named_direction : named_directions) controls.emplace_back(std::make_unique(Translate, named_direction)); @@ -31,89 +32,121 @@ Cursor::Cursor(const std::string& name_) : ControlGroup(name_, GroupType::Cursor controls.emplace_back(std::make_unique(Translate, _trans("Hide"))); controls.emplace_back(std::make_unique(Translate, _trans("Recenter"))); + // Default shape is a 1.0 square (no resizing/reshaping): + AddReshapingSettings(1.0, 0.5, 50); + numeric_settings.emplace_back(std::make_unique(_trans("Center"), 0.5)); numeric_settings.emplace_back(std::make_unique(_trans("Width"), 0.5)); numeric_settings.emplace_back(std::make_unique(_trans("Height"), 0.5)); - numeric_settings.emplace_back(std::make_unique(_trans("Dead Zone"), 0, 0, 20)); + boolean_settings.emplace_back(std::make_unique(_trans("Relative Input"), false)); boolean_settings.emplace_back(std::make_unique(_trans("Auto-Hide"), false)); } +ReshapableInput::ReshapeData Cursor::GetReshapableState(bool adjusted) +{ + const ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + const ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + + // Return raw values. (used in UI) + if (!adjusted) + return {x, y}; + + return Reshape(x, y, 0.0); +} + +ControlState Cursor::GetGateRadiusAtAngle(double ang) const +{ + // TODO: Change this to 0.5 and adjust the math, + // so pointer doesn't have to be clamped to the configured width/height? + return SquareStickGate(1.0).GetRadiusAtAngle(ang); +} + Cursor::StateData Cursor::GetState(const bool adjusted) { - const ControlState zz = controls[4]->control_ref->State() - controls[5]->control_ref->State(); + ControlState z = controls[4]->control_ref->State() - controls[5]->control_ref->State(); - // silly being here - if (zz > m_state.z) - m_state.z = std::min(m_state.z + 0.1, zz); - else if (zz < m_state.z) - m_state.z = std::max(m_state.z - 0.1, zz); - - StateData result; - result.z = m_state.z; - - if (m_autohide_timer > -1) + if (!adjusted) { - --m_autohide_timer; + const auto raw_input = GetReshapableState(false); + + return {raw_input.x, raw_input.y, z}; } - ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); - ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + const auto input = GetReshapableState(true); - const ControlState deadzone = numeric_settings[3]->GetValue(); + // TODO: Using system time is ugly. + // Kill this after state is moved into wiimote rather than this class. + const auto now = Clock::now(); + const auto ms_since_update = + std::chrono::duration_cast(now - m_last_update).count(); + m_last_update = now; - // reset auto-hide timer - if (std::abs(m_prev_xx - xx) > deadzone || std::abs(m_prev_yy - yy) > deadzone) - { - m_autohide_timer = TIMER_VALUE; - } + const double max_step = STEP_PER_SEC / 1000.0 * ms_since_update; + const double max_z_step = STEP_Z_PER_SEC / 1000.0 * ms_since_update; - // hide - const bool autohide = boolean_settings[1]->GetValue() && m_autohide_timer < 0; - if (controls[6]->control_ref->State() > 0.5 || autohide) + // Apply deadzone to z: + const ControlState deadzone = numeric_settings[SETTING_DEADZONE]->GetValue(); + z = std::copysign(std::max(0.0, std::abs(z) - deadzone) / (1.0 - deadzone), z); + + // Smooth out z movement: + // FYI: Not using relative input for Z. + m_state.z += MathUtil::Clamp(z - m_state.z, -max_z_step, max_z_step); + + // Relative input: + if (boolean_settings[0]->GetValue()) { - result.x = 10000; - result.y = 0; - } - else - { - // adjust cursor according to settings - if (adjusted) + // Recenter: + if (controls[7]->control_ref->State() > BUTTON_THRESHOLD) { - xx *= (numeric_settings[1]->GetValue() * 2); - yy *= (numeric_settings[2]->GetValue() * 2); - yy += (numeric_settings[0]->GetValue() - 0.5); - } - - // relative input - if (boolean_settings[0]->GetValue()) - { - // deadzone to avoid the cursor slowly drifting - if (std::abs(xx) > deadzone) - m_state.x = MathUtil::Clamp(m_state.x + xx * SPEED_MULTIPLIER, -1.0, 1.0); - if (std::abs(yy) > deadzone) - m_state.y = MathUtil::Clamp(m_state.y + yy * SPEED_MULTIPLIER, -1.0, 1.0); - - // recenter - if (controls[7]->control_ref->State() > 0.5) - { - m_state.x = 0.0; - m_state.y = 0.0; - } + m_state.x = 0.0; + m_state.y = 0.0; } else { - m_state.x = xx; - m_state.y = yy; + m_state.x = MathUtil::Clamp(m_state.x + input.x * max_step, -1.0, 1.0); + m_state.y = MathUtil::Clamp(m_state.y + input.y * max_step, -1.0, 1.0); } - - result.x = m_state.x; - result.y = m_state.y; + } + // Absolute input: + else + { + m_state.x = input.x; + m_state.y = input.y; } - m_prev_xx = xx; - m_prev_yy = yy; + StateData result = m_state; + + // Adjust cursor according to settings: + result.x *= (numeric_settings[SETTING_WIDTH]->GetValue() * 2); + result.y *= (numeric_settings[SETTING_HEIGHT]->GetValue() * 2); + result.y += (numeric_settings[SETTING_CENTER]->GetValue() - 0.5); + + const bool autohide = boolean_settings[1]->GetValue(); + + // Auto-hide timer: + // TODO: should Z movement reset this? + if (!autohide || std::abs(m_prev_result.x - result.x) > AUTO_HIDE_DEADZONE || + std::abs(m_prev_result.y - result.y) > AUTO_HIDE_DEADZONE) + { + m_auto_hide_timer = AUTO_HIDE_MS; + } + else if (m_auto_hide_timer) + { + m_auto_hide_timer -= std::min(ms_since_update, m_auto_hide_timer); + } + + m_prev_result = result; + + // If auto-hide time is up or hide button is held: + if (!m_auto_hide_timer || controls[6]->control_ref->State() > BUTTON_THRESHOLD) + { + // TODO: Use NaN or something: + result.x = 10000; + result.y = 0; + } return result; } + } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h index b24cfdcba3..fcd82a7d50 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h @@ -4,13 +4,15 @@ #pragma once +#include #include -#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" + +#include "InputCommon/ControllerEmu/StickGate.h" #include "InputCommon/ControllerInterface/Device.h" namespace ControllerEmu { -class Cursor : public ControlGroup +class Cursor : public ReshapableInput { public: struct StateData @@ -20,22 +22,42 @@ public: ControlState z{}; }; + enum + { + SETTING_CENTER = ReshapableInput::SETTING_COUNT, + SETTING_WIDTH, + SETTING_HEIGHT, + }; + explicit Cursor(const std::string& name); - StateData GetState(bool adjusted = false); + ReshapeData GetReshapableState(bool adjusted) final override; + ControlState GetGateRadiusAtAngle(double ang) const override; + + StateData GetState(bool adjusted); private: // This is used to reduce the cursor speed for relative input // to something that makes sense with the default range. - static constexpr double SPEED_MULTIPLIER = 0.04; + static constexpr double STEP_PER_SEC = 0.04 * 200; - // Sets the length for the auto-hide timer - static constexpr int TIMER_VALUE = 500; + // Smooth out forward/backward movements: + static constexpr double STEP_Z_PER_SEC = 0.05 * 200; + static constexpr int AUTO_HIDE_MS = 2500; + static constexpr double AUTO_HIDE_DEADZONE = 0.001; + + static constexpr double BUTTON_THRESHOLD = 0.5; + + // Not adjusted by width/height/center: StateData m_state; - int m_autohide_timer = TIMER_VALUE; - ControlState m_prev_xx; - ControlState m_prev_yy; + // Adjusted: + StateData m_prev_result; + + int m_auto_hide_timer = AUTO_HIDE_MS; + + typedef std::chrono::steady_clock Clock; + Clock::time_point m_last_update; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp index b00ae5a4c9..998cce17ee 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.cpp @@ -10,6 +10,8 @@ #include #include "Common/Common.h" +#include "Common/MathUtil.h" + #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/Control/Control.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -35,10 +37,10 @@ Tilt::Tilt(const std::string& name_) numeric_settings.emplace_back(std::make_unique(_trans("Angle"), 0.9, 0, 180)); } -Tilt::StateData Tilt::GetState(bool adjusted) +Tilt::StateData Tilt::GetReshapableState(bool adjusted) { - ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State(); - ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + const ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + const ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State(); // Return raw values. (used in UI) if (!adjusted) @@ -49,26 +51,37 @@ Tilt::StateData Tilt::GetState(bool adjusted) // Compute desired tilt: StateData target = Reshape(x, y, modifier); - // Step the simulation. This is pretty ugly being here. + // Step the simulation. This is somewhat ugly being here. + // We should be able to GetState without changing state. + // State should be stored outside of this object inside the wiimote, + // and separately inside the UI. + + // We're using system time rather than ticks to step this. + // I don't think that's too horrible as we can consider this part of user input. + // And at least the Mapping UI will behave sanely this way. + // TODO: when state is moved outside of this class have a separate Step() + // function that takes a ms_passed argument const auto now = Clock::now(); const auto ms_since_update = std::chrono::duration_cast(now - m_last_update).count(); m_last_update = now; - constexpr int MAX_DEG_PER_SEC = 360 * 2; - const double MAX_STEP = MAX_DEG_PER_SEC / 180.0 * ms_since_update / 1000; + const double max_step = MAX_DEG_PER_SEC / 180.0 * ms_since_update / 1000; // TODO: Allow wrap around from 1.0 to -1.0 // (take the fastest route to target) - const double diff_x = (target.x - m_tilt.x); - m_tilt.x += std::min(MAX_STEP, std::abs(diff_x)) * ((diff_x < 0) ? -1 : 1); - const double diff_y = (target.y - m_tilt.y); - m_tilt.y += std::min(MAX_STEP, std::abs(diff_y)) * ((diff_y < 0) ? -1 : 1); + m_tilt.x += MathUtil::Clamp(target.x - m_tilt.x, -max_step, max_step); + m_tilt.y += MathUtil::Clamp(target.y - m_tilt.y, -max_step, max_step); return m_tilt; } +Tilt::StateData Tilt::GetState() +{ + return GetReshapableState(true); +} + ControlState Tilt::GetGateRadiusAtAngle(double ang) const { const ControlState max_tilt_angle = numeric_settings[SETTING_MAX_ANGLE]->GetValue() / 1.8; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h index 9515ff2c2a..965151ea29 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Tilt.h @@ -15,6 +15,8 @@ namespace ControllerEmu class Tilt : public ReshapableInput { public: + typedef ReshapeData StateData; + enum { SETTING_MAX_ANGLE = ReshapableInput::SETTING_COUNT, @@ -22,14 +24,17 @@ public: explicit Tilt(const std::string& name); - StateData GetState(bool adjusted = true); - + StateData GetReshapableState(bool adjusted) final override; ControlState GetGateRadiusAtAngle(double ang) const override; + StateData GetState(); + private: - typedef std::chrono::steady_clock Clock; + static constexpr int MAX_DEG_PER_SEC = 360 * 6; StateData m_tilt; + + typedef std::chrono::steady_clock Clock; Clock::time_point m_last_update; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp index 21d05184c2..64cf238e17 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp @@ -76,8 +76,8 @@ void ReshapableInput::AddReshapingSettings(ControlState default_radius, ControlS numeric_settings.emplace_back(std::make_unique(_trans("Dead Zone"), 0, 0, 50)); } -ReshapableInput::StateData ReshapableInput::Reshape(ControlState x, ControlState y, - ControlState modifier) +ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlState y, + ControlState modifier) { // TODO: make the AtAngle functions work with negative angles: const ControlState ang = std::atan2(y, x) + MathUtil::TAU; diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h index f0c02e05e3..93b5c5792c 100644 --- a/Source/Core/InputCommon/ControllerEmu/StickGate.h +++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h @@ -58,7 +58,7 @@ class ReshapableInput : public ControlGroup public: ReshapableInput(std::string name, std::string ui_name, GroupType type); - struct StateData + struct ReshapeData { ControlState x{}; ControlState y{}; @@ -77,13 +77,13 @@ public: ControlState GetInputRadiusAtAngle(double ang) const; virtual ControlState GetGateRadiusAtAngle(double ang) const = 0; - virtual StateData GetState(bool adjusted = true) = 0; + virtual ReshapeData GetReshapableState(bool adjusted) = 0; protected: void AddReshapingSettings(ControlState default_radius, ControlState default_shape, int max_deadzone); - StateData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0); + ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0); private: ControlState CalculateInputShapeRadiusAtAngle(double ang) const;