summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorMat M <mathew1800@gmail.com>2018-12-28 06:18:56 -0500
committerGitHub <noreply@github.com>2018-12-28 06:18:56 -0500
commitde03019c77691f4d596d5d9b0d67bcceefa8ce9d (patch)
tree0ad62c370609b92c2a664ae172c6b8a0ad5eab14 /Source/Core
parentf006af441e87f0331ea39d38082a0a52a9b86277 (diff)
parentc614f5f534cdb7a083387505aff90e8be94fed73 (diff)
Merge pull request #7628 from jordan-woyak/stick-shapes
ControllerEmu: Add ability to reshape analog sticks. Make the mapping indicator pretty.
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Common/MathUtil.h3
-rw-r--r--Source/Core/Core/HW/GCPadEmu.cpp15
-rw-r--r--Source/Core/Core/HW/GCPadEmu.h8
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Attachment.h3
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.cpp9
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.h2
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.cpp11
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.h6
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.cpp11
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.h6
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.cpp5
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.h1
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.cpp11
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.h6
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp150
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h11
-rw-r--r--Source/Core/InputCommon/CMakeLists.txt1
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp116
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h30
-rw-r--r--Source/Core/InputCommon/ControllerEmu/StickGate.cpp47
-rw-r--r--Source/Core/InputCommon/ControllerEmu/StickGate.h55
-rw-r--r--Source/Core/InputCommon/InputCommon.vcxproj4
-rw-r--r--Source/Core/InputCommon/InputCommon.vcxproj.filters6
23 files changed, 367 insertions, 150 deletions
diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h
index bc0162b9ee..01a39db5b1 100644
--- a/Source/Core/Common/MathUtil.h
+++ b/Source/Core/Common/MathUtil.h
@@ -17,6 +17,9 @@
namespace MathUtil
{
+constexpr double TAU = 6.2831853071795865;
+constexpr double PI = TAU / 2;
+
template <class T>
constexpr T Clamp(const T val, const T& min, const T& max)
{
diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp
index 8f94fb7519..c0d6836f7d 100644
--- a/Source/Core/Core/HW/GCPadEmu.cpp
+++ b/Source/Core/Core/HW/GCPadEmu.cpp
@@ -63,10 +63,14 @@ GCPad::GCPad(const unsigned int index) : m_index(index)
}
// sticks
- groups.emplace_back(m_main_stick = new ControllerEmu::AnalogStick(
- "Main Stick", _trans("Control Stick"), DEFAULT_PAD_STICK_RADIUS));
- groups.emplace_back(m_c_stick = new ControllerEmu::AnalogStick("C-Stick", _trans("C Stick"),
- DEFAULT_PAD_STICK_RADIUS));
+ constexpr auto main_gate_radius =
+ ControlState(MAIN_STICK_GATE_RADIUS) / GCPadStatus::MAIN_STICK_RADIUS;
+ groups.emplace_back(m_main_stick = new ControllerEmu::OctagonAnalogStick(
+ "Main Stick", _trans("Control Stick"), main_gate_radius));
+
+ constexpr auto c_gate_radius = ControlState(C_STICK_GATE_RADIUS) / GCPadStatus::MAIN_STICK_RADIUS;
+ groups.emplace_back(m_c_stick = new ControllerEmu::OctagonAnalogStick(
+ "C-Stick", _trans("C Stick"), c_gate_radius));
// triggers
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(_trans("Triggers")));
@@ -226,7 +230,8 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface)
m_main_stick->SetControlExpression(4, "LSHIFT"); // Modifier
#elif __APPLE__
- m_c_stick->SetControlExpression(4, "Left Control"); // Modifier
+ // Modifier
+ m_c_stick->SetControlExpression(4, "Left Control");
// Control Stick
m_main_stick->SetControlExpression(0, "Up Arrow"); // Up
diff --git a/Source/Core/Core/HW/GCPadEmu.h b/Source/Core/Core/HW/GCPadEmu.h
index c5a10acfa6..58272dc74a 100644
--- a/Source/Core/Core/HW/GCPadEmu.h
+++ b/Source/Core/Core/HW/GCPadEmu.h
@@ -16,7 +16,7 @@ namespace ControllerEmu
class AnalogStick;
class Buttons;
class MixedTriggers;
-}
+} // namespace ControllerEmu
enum class PadGroup
{
@@ -45,6 +45,9 @@ public:
void LoadDefaults(const ControllerInterface& ciface) override;
+ static const u8 MAIN_STICK_GATE_RADIUS = 87;
+ static const u8 C_STICK_GATE_RADIUS = 74;
+
private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_main_stick;
@@ -57,7 +60,4 @@ private:
ControllerEmu::BooleanSetting* m_always_connected;
const unsigned int m_index;
-
- // Default analog stick radius for GameCube controllers.
- static constexpr ControlState DEFAULT_PAD_STICK_RADIUS = 1.0;
};
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Attachment.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Attachment.h
index dca1d23972..b1bae12086 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Attachment.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Attachment.h
@@ -25,9 +25,6 @@ public:
std::string GetName() const override;
protected:
- // Default radius for attachment analog sticks.
- static constexpr ControlState DEFAULT_ATTACHMENT_STICK_RADIUS = 1.0;
-
std::array<u8, 6> m_id{};
std::array<u8, 0x10> m_calibration{};
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.cpp b/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.cpp
index bf2b41fc6b..1cabaf60c4 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.cpp
@@ -83,10 +83,11 @@ Classic::Classic(ExtensionReg& reg) : Attachment(_trans("Classic"), reg)
}
// sticks
- groups.emplace_back(m_left_stick = new ControllerEmu::AnalogStick(
- _trans("Left Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
- groups.emplace_back(m_right_stick = new ControllerEmu::AnalogStick(
- _trans("Right Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
+ constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / LEFT_STICK_RADIUS;
+ groups.emplace_back(m_left_stick =
+ new ControllerEmu::OctagonAnalogStick(_trans("Left Stick"), gate_radius));
+ groups.emplace_back(
+ m_right_stick = new ControllerEmu::OctagonAnalogStick(_trans("Right Stick"), gate_radius));
// triggers
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(_trans("Triggers")));
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.h
index a914569516..357ad9a1cc 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Classic.h
@@ -68,6 +68,8 @@ public:
RIGHT_TRIGGER_RANGE = 0x1F,
};
+ static const u8 STICK_GATE_RADIUS = 0x16;
+
private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::MixedTriggers* m_triggers;
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.cpp b/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.cpp
index 726a20739b..b32b767b01 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.cpp
@@ -54,8 +54,9 @@ Drums::Drums(ExtensionReg& reg) : Attachment(_trans("Drums"), reg)
}
// stick
- groups.emplace_back(
- m_stick = new ControllerEmu::AnalogStick(_trans("Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
+ constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
+ groups.emplace_back(m_stick =
+ new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -76,8 +77,8 @@ void Drums::GetState(u8* const data)
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
- drum_data.sx = static_cast<u8>((stick_state.x * 0x1F) + 0x20);
- drum_data.sy = static_cast<u8>((stick_state.y * 0x1F) + 0x20);
+ drum_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
+ drum_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
}
// TODO: softness maybe
@@ -119,4 +120,4 @@ ControllerEmu::ControlGroup* Drums::GetGroup(DrumsGroup group)
return nullptr;
}
}
-}
+} // namespace WiimoteEmu
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.h
index 83caa78814..ea36869cb5 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Drums.h
@@ -40,6 +40,12 @@ public:
PAD_ORANGE = 0x8000,
};
+ static const u8 STICK_CENTER = 0x20;
+ static const u8 STICK_RADIUS = 0x1f;
+
+ // TODO: Test real hardware. Is this accurate?
+ static const u8 STICK_GATE_RADIUS = 0x16;
+
private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_pads;
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.cpp b/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.cpp
index cb2d59a885..8b3d447376 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.cpp
@@ -83,8 +83,9 @@ Guitar::Guitar(ExtensionReg& reg) : Attachment(_trans("Guitar"), reg)
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "+"));
// stick
- groups.emplace_back(
- m_stick = new ControllerEmu::AnalogStick(_trans("Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
+ constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
+ groups.emplace_back(m_stick =
+ new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
// whammy
groups.emplace_back(m_whammy = new ControllerEmu::Triggers(_trans("Whammy")));
@@ -108,8 +109,8 @@ void Guitar::GetState(u8* const data)
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
- guitar_data.sx = static_cast<u8>((stick_state.x * 0x1F) + 0x20);
- guitar_data.sy = static_cast<u8>((stick_state.y * 0x1F) + 0x20);
+ guitar_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
+ guitar_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
}
// slider bar
@@ -174,4 +175,4 @@ ControllerEmu::ControlGroup* Guitar::GetGroup(GuitarGroup group)
return nullptr;
}
}
-}
+} // namespace WiimoteEmu
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.h
index 36fd809af8..8964221511 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Guitar.h
@@ -43,6 +43,12 @@ public:
FRET_ORANGE = 0x8000,
};
+ static const u8 STICK_CENTER = 0x20;
+ static const u8 STICK_RADIUS = 0x1f;
+
+ // TODO: Test real hardware. Is this accurate?
+ static const u8 STICK_GATE_RADIUS = 0x16;
+
private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_frets;
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.cpp b/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.cpp
index d280272e6b..accc64bc74 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.cpp
@@ -38,8 +38,9 @@ Nunchuk::Nunchuk(ExtensionReg& reg) : Attachment(_trans("Nunchuk"), reg)
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
// stick
- groups.emplace_back(
- m_stick = new ControllerEmu::AnalogStick(_trans("Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
+ constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
+ groups.emplace_back(m_stick =
+ new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
// swing
groups.emplace_back(m_swing = new ControllerEmu::Force(_trans("Swing")));
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.h
index b16df713d7..9b36cac1b2 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Nunchuk.h
@@ -48,6 +48,7 @@ public:
{
STICK_CENTER = 0x80,
STICK_RADIUS = 0x7F,
+ STICK_GATE_RADIUS = 0x52,
};
void LoadDefaults(const ControllerInterface& ciface) override;
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.cpp b/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.cpp
index eb73093e2f..76eef2ab2a 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.cpp
@@ -69,8 +69,9 @@ Turntable::Turntable(ExtensionReg& reg) : Attachment(_trans("Turntable"), reg)
new ControllerEmu::Slider("Table Right", _trans("Right Table")));
// stick
- groups.emplace_back(
- m_stick = new ControllerEmu::AnalogStick(_trans("Stick"), DEFAULT_ATTACHMENT_STICK_RADIUS));
+ constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
+ groups.emplace_back(m_stick =
+ new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
// effect dial
groups.emplace_back(m_effect_dial = new ControllerEmu::Triggers(_trans("Effect")));
@@ -92,8 +93,8 @@ void Turntable::GetState(u8* const data)
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
- tt_data.sx = static_cast<u8>((stick_state.x * 0x1F) + 0x20);
- tt_data.sy = static_cast<u8>((stick_state.y * 0x1F) + 0x20);
+ tt_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
+ tt_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
}
// left table
@@ -170,4 +171,4 @@ ControllerEmu::ControlGroup* Turntable::GetGroup(TurntableGroup group)
return nullptr;
}
}
-}
+} // namespace WiimoteEmu
diff --git a/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.h b/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.h
index c97b68ccad..8d2a0b0988 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Attachment/Turntable.h
@@ -45,6 +45,12 @@ public:
BUTTON_PLUS = 0x04,
};
+ static const u8 STICK_CENTER = 0x20;
+ static const u8 STICK_RADIUS = 0x1f;
+
+ // TODO: Test real hardware. Is this accurate?
+ static const u8 STICK_GATE_RADIUS = 0x16;
+
private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_stick;
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
index 5416b4fd2f..a09b41e5db 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
@@ -10,9 +10,11 @@
#include <QPainter>
#include <QTimer>
+#include "Common/MathUtil.h"
+
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
-#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
+#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/Device.h"
@@ -28,7 +30,7 @@ MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group
BindCursorControls(false);
break;
case ControllerEmu::GroupType::Stick:
- BindStickControls();
+ // Nothing needed:
break;
case ControllerEmu::GroupType::Tilt:
BindCursorControls(true);
@@ -68,18 +70,6 @@ void MappingIndicator::BindCursorControls(bool tilt)
}
}
-void MappingIndicator::BindStickControls()
-{
- m_stick_up = m_group->controls[0]->control_ref.get();
- m_stick_down = m_group->controls[1]->control_ref.get();
- m_stick_left = m_group->controls[2]->control_ref.get();
- m_stick_right = m_group->controls[3]->control_ref.get();
- m_stick_modifier = m_group->controls[4]->control_ref.get();
-
- m_stick_radius = m_group->numeric_settings[0].get();
- m_stick_deadzone = m_group->numeric_settings[1].get();
-}
-
void MappingIndicator::BindMixedTriggersControls()
{
m_mixed_triggers_l_button = m_group->controls[0]->control_ref.get();
@@ -144,83 +134,93 @@ void MappingIndicator::DrawCursor(bool tilt)
p.fillRect(curx, cury, 8, 8, Qt::red);
}
-void MappingIndicator::DrawStick()
+// Constructs a polygon by querying a radius at varying angles:
+template <typename F>
+QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale)
{
- float centerx = width() / 2., centery = height() / 2.;
+ // A multiple of 8 (octagon) and enough points to be visibly pleasing:
+ constexpr int shape_point_count = 32;
+ QPolygonF shape{shape_point_count};
- bool c_stick = m_group->name == "C-Stick";
- bool classic_controller = m_group->name == "Left Stick" || m_group->name == "Right Stick";
+ int p = 0;
+ for (auto& point : shape)
+ {
+ const double angle = MathUtil::TAU * p / shape.size();
+ const double radius = radius_getter(angle) * scale;
- float ratio = 1;
+ point = {std::cos(angle) * radius, std::sin(angle) * radius};
+ ++p;
+ }
- if (c_stick)
- ratio = 1.;
- else if (classic_controller)
- ratio = 0.9f;
+ return shape;
+}
- // Polled values
- float mod = PollControlState(m_stick_modifier) ? 0.5 : 1;
- float radius = m_stick_radius->GetValue();
- float curx = -PollControlState(m_stick_left) + PollControlState(m_stick_right),
- cury = -PollControlState(m_stick_up) + PollControlState(m_stick_down);
- // The maximum deadzone value covers 50% of the stick area
- float deadzone = m_stick_deadzone->GetValue() / 2.;
+void MappingIndicator::DrawStick()
+{
+ // Make the c-stick yellow:
+ const bool is_c_stick = m_group->name == "C-Stick";
+ const QColor gate_brush_color = is_c_stick ? Qt::yellow : Qt::lightGray;
+ const QColor gate_pen_color = gate_brush_color.darker(125);
- // Size parameters
- float max_size = (height() / 2.5) / ratio;
- float stick_size = (height() / 3.) / ratio;
+ auto& stick = *static_cast<ControllerEmu::AnalogStick*>(m_group);
- // Emulated cursor position
- float virt_curx, virt_cury;
+ // 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);
+ Settings::Instance().SetControllerStateNeeded(false);
- if (std::abs(curx) < deadzone && std::abs(cury) < deadzone)
- {
- virt_curx = virt_cury = 0;
- }
- else
- {
- virt_curx = curx * mod;
- virt_cury = cury * mod;
- }
+ // Bounding box size:
+ const double scale = height() / 2.5;
- // Coordinates for an octagon
- std::array<QPointF, 8> radius_octagon = {{
- QPointF(centerx, centery + stick_size), // Bottom
- QPointF(centerx + stick_size / sqrt(2), centery + stick_size / sqrt(2)), // Bottom Right
- QPointF(centerx + stick_size, centery), // Right
- QPointF(centerx + stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Right
- QPointF(centerx, centery - stick_size), // Top
- QPointF(centerx - stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Left
- QPointF(centerx - stick_size, centery), // Left
- QPointF(centerx - stick_size / sqrt(2), centery + stick_size / sqrt(2)) // Bottom Left
- }};
+ const float dot_radius = 2;
QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing, true);
- p.setRenderHint(QPainter::SmoothPixmapTransform, true);
+ p.translate(width() / 2, height() / 2);
- // Draw maximum values
+ // Bounding box.
p.setBrush(Qt::white);
- p.setPen(Qt::black);
- p.drawRect(centerx - max_size, centery - max_size, max_size * 2, max_size * 2);
+ p.setPen(Qt::gray);
+ p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
- // Draw radius
- p.setBrush(c_stick ? Qt::yellow : Qt::darkGray);
- p.drawPolygon(radius_octagon.data(), static_cast<int>(radius_octagon.size()));
+ // UI y-axis is opposite that of stick.
+ p.scale(1.0, -1.0);
- // Draw deadzone
- p.setBrush(c_stick ? Qt::darkYellow : Qt::lightGray);
- p.drawEllipse(centerx - deadzone * stick_size, centery - deadzone * stick_size,
- deadzone * stick_size * 2, deadzone * stick_size * 2);
-
- // Draw stick
- p.setBrush(Qt::black);
- p.drawEllipse(centerx - 4 + curx * max_size, centery - 4 + cury * max_size, 8, 8);
+ // Enable AA after drawing bounding box.
+ p.setRenderHint(QPainter::Antialiasing, true);
+ p.setRenderHint(QPainter::SmoothPixmapTransform, true);
- // Draw virtual stick
- p.setBrush(Qt::red);
- p.drawEllipse(centerx - 4 + virt_curx * max_size * radius,
- centery - 4 + virt_cury * max_size * radius, 8, 8);
+ // Input gate. (i.e. the octagon shape)
+ p.setPen(gate_pen_color);
+ p.setBrush(gate_brush_color);
+ p.drawPolygon(GetPolygonFromRadiusGetter(
+ [&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }, scale));
+
+ // Deadzone.
+ p.setPen(Qt::darkGray);
+ p.setBrush(QBrush(Qt::darkGray, Qt::BDiagPattern));
+ p.drawPolygon(GetPolygonFromRadiusGetter(
+ [&stick](double ang) { return stick.GetDeadzoneRadiusAtAngle(ang); }, scale));
+
+ // Input shape.
+ p.setPen(QPen(Qt::darkGray, 1.0, Qt::DashLine));
+ p.setBrush(Qt::NoBrush);
+ p.drawPolygon(GetPolygonFromRadiusGetter(
+ [&stick](double ang) { return stick.GetInputRadiusAtAngle(ang); }, scale));
+
+ // Raw stick position.
+ p.setPen(Qt::NoPen);
+ p.setBrush(Qt::darkGray);
+ p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, dot_radius, dot_radius);
+
+ // Adjusted stick position.
+ if (adj_coord.x || adj_coord.y)
+ {
+ p.setPen(Qt::NoPen);
+ p.setBrush(Qt::red);
+ p.drawEllipse(QPointF{adj_coord.x, adj_coord.y} * scale, dot_radius, dot_radius);
+ }
}
void MappingIndicator::DrawMixedTriggers()
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
index 62baf47250..f3bff86798 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
@@ -25,7 +25,6 @@ public:
private:
void BindCursorControls(bool tilt);
- void BindStickControls();
void BindMixedTriggersControls();
void DrawCursor(bool tilt);
@@ -35,16 +34,6 @@ private:
void paintEvent(QPaintEvent*) override;
ControllerEmu::ControlGroup* m_group;
- // Stick settings
- ControlReference* m_stick_up;
- ControlReference* m_stick_down;
- ControlReference* m_stick_left;
- ControlReference* m_stick_right;
- ControlReference* m_stick_modifier;
-
- ControllerEmu::NumericSetting* m_stick_radius;
- ControllerEmu::NumericSetting* m_stick_deadzone;
-
// Cursor settings
ControlReference* m_cursor_up;
ControlReference* m_cursor_down;
diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt
index a3ef2722d8..ece9bbbde8 100644
--- a/Source/Core/InputCommon/CMakeLists.txt
+++ b/Source/Core/InputCommon/CMakeLists.txt
@@ -2,6 +2,7 @@ add_library(inputcommon
InputConfig.cpp
InputProfile.cpp
ControllerEmu/ControllerEmu.cpp
+ ControllerEmu/StickGate.cpp
ControllerEmu/Control/Control.cpp
ControllerEmu/Control/Input.cpp
ControllerEmu/Control/Output.cpp
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp
index c38e5f1d3a..9db29909fd 100644
--- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp
@@ -4,11 +4,10 @@
#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
-#include <algorithm>
#include <cmath>
-#include <memory>
#include "Common/Common.h"
+#include "Common/MathUtil.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
@@ -18,54 +17,121 @@
namespace ControllerEmu
{
-AnalogStick::AnalogStick(const char* const name_, ControlState default_radius)
- : AnalogStick(name_, name_, default_radius)
+AnalogStick::AnalogStick(const char* const name_, std::unique_ptr<StickGate>&& stick_gate)
+ : AnalogStick(name_, name_, std::move(stick_gate))
{
}
AnalogStick::AnalogStick(const char* const name_, const char* const ui_name_,
- ControlState default_radius)
- : ControlGroup(name_, ui_name_, GroupType::Stick)
+ std::unique_ptr<StickGate>&& stick_gate)
+ : ControlGroup(name_, ui_name_, GroupType::Stick), m_stick_gate(std::move(stick_gate))
{
for (auto& named_direction : named_directions)
controls.emplace_back(std::make_unique<Input>(Translate, named_direction));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Modifier")));
+
+ // Set default input radius to that of the gate radius (no resizing)
+ // Allow radius greater than 1.0 for definitions of rounded squares
+ // This is ideal for Xbox controllers (and probably others)
+ numeric_settings.emplace_back(
+ std::make_unique<NumericSetting>(_trans("Input Radius"), GetGateRadiusAtAngle(0.0), 0, 140));
+ // Set default input shape to an octagon (no reshaping)
numeric_settings.emplace_back(
- std::make_unique<NumericSetting>(_trans("Radius"), default_radius, 0, 100));
+ std::make_unique<NumericSetting>(_trans("Input Shape"), 0.0, 0, 50));
numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50));
}
-AnalogStick::StateData AnalogStick::GetState()
+AnalogStick::StateData AnalogStick::GetState(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 radius = numeric_settings[SETTING_RADIUS]->GetValue();
- const ControlState deadzone = numeric_settings[SETTING_DEADZONE]->GetValue();
- const ControlState m = controls[4]->control_ref->State();
+ // Return raw values. (used in UI)
+ if (!adjusted)
+ return {x, y};
- const ControlState ang = atan2(y, x);
- const ControlState ang_sin = sin(ang);
- const ControlState ang_cos = cos(ang);
+ // TODO: make the AtAngle functions work with negative angles:
+ const ControlState ang = std::atan2(y, x) + MathUtil::TAU;
- ControlState dist = sqrt(x * x + y * y);
+ const ControlState gate_max_dist = GetGateRadiusAtAngle(ang);
+ const ControlState input_max_dist = GetInputRadiusAtAngle(ang);
- // dead zone code
- dist = std::max(0.0, dist - deadzone);
- dist /= (1 - deadzone);
+ // If input radius is zero we apply no scaling.
+ // This is useful when mapping native controllers without knowing intimate radius details.
+ const ControlState max_dist = input_max_dist ? input_max_dist : gate_max_dist;
- // radius
- dist *= radius;
+ ControlState dist = std::sqrt(x * x + y * y) / max_dist;
- // The modifier halves the distance by 50%, which is useful
- // for keyboard controls.
- if (m)
+ // 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%.
+ const ControlState modifier = controls[4]->control_ref->State();
+ if (modifier)
+ {
+ // TODO: Modifier's range setting gets reset to 100% when the clear button is clicked.
+ // This causes the modifier to not behave how a user might suspect.
+ // Retaining the old scale-by-50% behavior until range is fixed to clear to 50%.
dist *= 0.5;
+ // dist *= modifier;
+ }
+
+ // Apply deadzone as a percentage of the user-defined radius/shape:
+ const ControlState deadzone = GetDeadzoneRadiusAtAngle(ang);
+ dist = std::max(0.0, dist - deadzone) / (1.0 - deadzone);
- y = std::max(-1.0, std::min(1.0, ang_sin * dist));
- x = std::max(-1.0, std::min(1.0, ang_cos * dist));
+ // Scale to the gate shape/radius:
+ dist = dist *= gate_max_dist;
+ x = MathUtil::Clamp(std::cos(ang) * dist, -1.0, 1.0);
+ y = MathUtil::Clamp(std::sin(ang) * dist, -1.0, 1.0);
return {x, y};
}
+
+ControlState AnalogStick::GetGateRadiusAtAngle(double ang) const
+{
+ return m_stick_gate->GetRadiusAtAngle(ang);
+}
+
+ControlState AnalogStick::GetDeadzoneRadiusAtAngle(double ang) const
+{
+ return CalculateInputShapeRadiusAtAngle(ang) * numeric_settings[SETTING_DEADZONE]->GetValue();
+}
+
+ControlState AnalogStick::GetInputRadiusAtAngle(double ang) const
+{
+ const ControlState radius =
+ CalculateInputShapeRadiusAtAngle(ang) * numeric_settings[SETTING_INPUT_RADIUS]->GetValue();
+ // Clamp within the -1 to +1 square as input radius may be greater than 1.0:
+ return std::min(radius, SquareStickGate(1).GetRadiusAtAngle(ang));
+}
+
+ControlState AnalogStick::CalculateInputShapeRadiusAtAngle(double ang) const
+{
+ const auto shape = numeric_settings[SETTING_INPUT_SHAPE]->GetValue() * 4.0;
+
+ if (shape < 1.0)
+ {
+ // Between 0 and 25 return a shape between octagon and circle
+ const auto amt = shape;
+ return OctagonStickGate(1).GetRadiusAtAngle(ang) * (1 - amt) + amt;
+ }
+ else
+ {
+ // Between 25 and 50 return a shape between circle and square
+ const auto amt = shape - 1.0;
+ return (1 - amt) + SquareStickGate(1).GetRadiusAtAngle(ang) * amt;
+ }
+}
+
+OctagonAnalogStick::OctagonAnalogStick(const char* name, ControlState gate_radius)
+ : OctagonAnalogStick(name, name, gate_radius)
+{
+}
+
+OctagonAnalogStick::OctagonAnalogStick(const char* name, const char* ui_name,
+ ControlState gate_radius)
+ : AnalogStick(name, ui_name, std::make_unique<ControllerEmu::OctagonStickGate>(gate_radius))
+{
+}
+
} // namespace ControllerEmu
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h
index d8d8e4ddea..2351189f74 100644
--- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h
@@ -5,6 +5,7 @@
#pragma once
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
+#include "InputCommon/ControllerEmu/StickGate.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ControllerEmu
@@ -14,7 +15,8 @@ class AnalogStick : public ControlGroup
public:
enum
{
- SETTING_RADIUS,
+ SETTING_INPUT_RADIUS,
+ SETTING_INPUT_SHAPE,
SETTING_DEADZONE,
};
@@ -24,10 +26,28 @@ public:
ControlState y{};
};
- // The GameCube controller and Wiimote attachments have a different default radius
- AnalogStick(const char* name, ControlState default_radius);
- AnalogStick(const char* name, const char* ui_name, ControlState default_radius);
+ AnalogStick(const char* name, std::unique_ptr<StickGate>&& stick_gate);
+ AnalogStick(const char* name, const char* ui_name, std::unique_ptr<StickGate>&& stick_gate);
- StateData GetState();
+ StateData GetState(bool adjusted = true);
+
+ // Angle is in radians and should be non-negative
+ ControlState GetGateRadiusAtAngle(double ang) const;
+ ControlState GetDeadzoneRadiusAtAngle(double ang) const;
+ ControlState GetInputRadiusAtAngle(double ang) const;
+
+private:
+ ControlState CalculateInputShapeRadiusAtAngle(double ang) const;
+
+ std::unique_ptr<StickGate> m_stick_gate;
+};
+
+// An AnalogStick with an OctagonStickGate
+class OctagonAnalogStick : public AnalogStick
+{
+public:
+ OctagonAnalogStick(const char* name, ControlState gate_radius);
+ OctagonAnalogStick(const char* name, const char* ui_name, ControlState gate_radius);
};
+
} // namespace ControllerEmu
diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.cpp b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp
new file mode 100644
index 0000000000..c0c2a668a3
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/StickGate.cpp
@@ -0,0 +1,47 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "InputCommon/ControllerEmu/StickGate.h"
+
+#include <cmath>
+
+#include "Common/MathUtil.h"
+
+namespace ControllerEmu
+{
+OctagonStickGate::OctagonStickGate(ControlState radius) : m_radius(radius)
+{
+}
+
+ControlState OctagonStickGate::GetRadiusAtAngle(double ang) const
+{
+ constexpr int sides = 8;
+ constexpr double sum_int_angles = (sides - 2) * MathUtil::PI;
+ constexpr double half_int_angle = sum_int_angles / sides / 2;
+
+ ang = std::fmod(ang, MathUtil::TAU / sides);
+ // Solve ASA triangle using The Law of Sines:
+ return m_radius / std::sin(MathUtil::PI - ang - half_int_angle) * std::sin(half_int_angle);
+}
+
+RoundStickGate::RoundStickGate(ControlState radius) : m_radius(radius)
+{
+}
+
+ControlState RoundStickGate::GetRadiusAtAngle(double) const
+{
+ return m_radius;
+}
+
+SquareStickGate::SquareStickGate(ControlState half_width) : m_half_width(half_width)
+{
+}
+
+ControlState SquareStickGate::GetRadiusAtAngle(double ang) const
+{
+ constexpr double section_ang = MathUtil::TAU / 4;
+ return m_half_width / std::cos(std::fmod(ang + section_ang / 2, section_ang) - section_ang / 2);
+}
+
+} // namespace ControllerEmu
diff --git a/Source/Core/InputCommon/ControllerEmu/StickGate.h b/Source/Core/InputCommon/ControllerEmu/StickGate.h
new file mode 100644
index 0000000000..decb15d234
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/StickGate.h
@@ -0,0 +1,55 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "InputCommon/ControlReference/ControlReference.h"
+
+namespace ControllerEmu
+{
+// An abstract class representing the plastic shell that limits an analog stick's movement.
+class StickGate
+{
+public:
+ // Angle is in radians and should be non-negative
+ virtual ControlState GetRadiusAtAngle(double ang) const = 0;
+
+ virtual ~StickGate() = default;
+};
+
+// An octagon-shaped stick gate is found on most Nintendo GC/Wii analog sticks.
+class OctagonStickGate : public StickGate
+{
+public:
+ // Radius of circumscribed circle
+ explicit OctagonStickGate(ControlState radius);
+ ControlState GetRadiusAtAngle(double ang) const override final;
+
+private:
+ const ControlState m_radius;
+};
+
+// A round-shaped stick gate. Possibly found on 3rd-party accessories.
+class RoundStickGate : public StickGate
+{
+public:
+ explicit RoundStickGate(ControlState radius);
+ ControlState GetRadiusAtAngle(double ang) const override final;
+
+private:
+ const ControlState m_radius;
+};
+
+// A square-shaped stick gate. e.g. keyboard input.
+class SquareStickGate : public StickGate
+{
+public:
+ explicit SquareStickGate(ControlState half_width);
+ ControlState GetRadiusAtAngle(double ang) const override final;
+
+private:
+ const ControlState m_half_width;
+};
+
+} // namespace ControllerEmu
diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj
index 0b5dc08018..2bef816726 100644
--- a/Source/Core/InputCommon/InputCommon.vcxproj
+++ b/Source/Core/InputCommon/InputCommon.vcxproj
@@ -37,6 +37,7 @@
<PropertyGroup Label="UserMacros" />
<ItemGroup>
<ClCompile Include="ControllerEmu\ControllerEmu.cpp" />
+ <ClCompile Include="ControllerEmu\StickGate.cpp" />
<ClCompile Include="ControllerEmu\Control\Control.cpp" />
<ClCompile Include="ControllerEmu\Control\Input.cpp" />
<ClCompile Include="ControllerEmu\Control\Output.cpp" />
@@ -75,6 +76,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="ControllerEmu\ControllerEmu.h" />
+ <ClInclude Include="ControllerEmu\StickGate.h" />
<ClInclude Include="ControllerEmu\Control\Control.h" />
<ClInclude Include="ControllerEmu\Control\Input.h" />
<ClInclude Include="ControllerEmu\Control\Output.h" />
@@ -119,4 +121,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project> \ No newline at end of file
+</Project>
diff --git a/Source/Core/InputCommon/InputCommon.vcxproj.filters b/Source/Core/InputCommon/InputCommon.vcxproj.filters
index c67333497c..01c5c371b6 100644
--- a/Source/Core/InputCommon/InputCommon.vcxproj.filters
+++ b/Source/Core/InputCommon/InputCommon.vcxproj.filters
@@ -32,6 +32,9 @@
<ClCompile Include="ControllerEmu\ControllerEmu.cpp">
<Filter>ControllerEmu</Filter>
</ClCompile>
+ <ClCompile Include="ControllerEmu\StickGate.cpp">
+ <Filter>ControllerEmu</Filter>
+ </ClCompile>
<ClCompile Include="ControllerEmu\Control\Control.cpp">
<Filter>ControllerEmu\Control</Filter>
</ClCompile>
@@ -119,6 +122,9 @@
<ClInclude Include="ControllerEmu\ControllerEmu.h">
<Filter>ControllerEmu</Filter>
</ClInclude>
+ <ClInclude Include="ControllerEmu\StickGate.h">
+ <Filter>ControllerEmu</Filter>
+ </ClInclude>
<ClInclude Include="ControllerEmu\Control\Control.h">
<Filter>ControllerEmu\Control</Filter>
</ClInclude>