From 4d34f861214612b8b3ef88a0291a89c783734144 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 4 Oct 2022 21:24:33 +0200 Subject: [PATCH] DolphinQt: Add MotionPlus support to TAS input Will manually controlling both an accelerometer and a gyroscope at the same time be reasonable to do? No idea. Was this easy to implement thanks to the input override system? Yes. Fixes https://bugs.dolphin-emu.org/issues/12443. --- .../Core/HW/WiimoteEmu/EmuSubroutines.cpp | 5 + Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp | 22 +-- Source/Core/Core/HW/WiimoteEmu/MotionPlus.h | 39 ++++-- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h | 1 + Source/Core/DolphinQt/TAS/TASCheckBox.cpp | 2 +- Source/Core/DolphinQt/TAS/TASControlState.cpp | 10 +- Source/Core/DolphinQt/TAS/TASControlState.h | 12 +- Source/Core/DolphinQt/TAS/TASInputWindow.cpp | 22 +-- Source/Core/DolphinQt/TAS/TASInputWindow.h | 16 +-- Source/Core/DolphinQt/TAS/TASSpinBox.cpp | 4 +- .../Core/DolphinQt/TAS/WiiTASInputWindow.cpp | 125 ++++++++++++------ Source/Core/DolphinQt/TAS/WiiTASInputWindow.h | 6 +- 12 files changed, 163 insertions(+), 101 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index f5f3d98ab9..15a4589436 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -601,4 +601,9 @@ ExtensionNumber Wiimote::GetActiveExtensionNumber() const return m_active_extension; } +bool Wiimote::IsMotionPlusAttached() const +{ + return m_is_motion_plus_attached; +} + } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp index 11441a0ae2..f2cae61ff3 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp @@ -530,20 +530,6 @@ void MotionPlus::Update(const DesiredExtensionState& target_state) MotionPlus::DataFormat::Data MotionPlus::GetGyroscopeData(const Common::Vec3& angular_velocity) { - // Conversion from radians to the calibrated values in degrees. - constexpr float VALUE_SCALE = - (CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) / float(MathUtil::TAU) * - 360; - - constexpr float SLOW_SCALE = VALUE_SCALE / CALIBRATION_SLOW_SCALE_DEGREES; - constexpr float FAST_SCALE = VALUE_SCALE / CALIBRATION_FAST_SCALE_DEGREES; - - static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1), - "SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values."); - - constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1); - constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE; - // Slow (high precision) scaling can be used if it fits in the sensor range. const float yaw = angular_velocity.z; const bool yaw_slow = (std::abs(yaw) < SLOW_MAX_RAD_PER_SEC); @@ -557,9 +543,11 @@ MotionPlus::DataFormat::Data MotionPlus::GetGyroscopeData(const Common::Vec3& an const bool pitch_slow = (std::abs(pitch) < SLOW_MAX_RAD_PER_SEC); const s32 pitch_value = pitch * (pitch_slow ? SLOW_SCALE : FAST_SCALE); - const u16 clamped_yaw_value = u16(std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE)); - const u16 clamped_roll_value = u16(std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE)); - const u16 clamped_pitch_value = u16(std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE)); + const u16 clamped_yaw_value = u16(std::llround(std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE))); + const u16 clamped_roll_value = + u16(std::llround(std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE))); + const u16 clamped_pitch_value = + u16(std::llround(std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE))); return MotionPlus::DataFormat::Data{ MotionPlus::DataFormat::GyroRawValue{MotionPlus::DataFormat::GyroType( diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h index 28386f9107..02ddfb7ec2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h @@ -6,6 +6,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/MathUtil.h" #include "Common/Swap.h" #include "Core/HW/WiimoteEmu/Dynamics.h" #include "Core/HW/WiimoteEmu/ExtensionPort.h" @@ -116,6 +117,32 @@ public: static constexpr u8 ACTIVE_DEVICE_ADDR = 0x52; static constexpr u8 PASSTHROUGH_MODE_OFFSET = 0xfe; + static constexpr int CALIBRATION_BITS = 16; + + static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1); + // Values are similar to that of a typical real M+. + static constexpr u16 CALIBRATION_SCALE_OFFSET = 0x4400; + static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0; + static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e; + + static constexpr int BITS_OF_PRECISION = 14; + static constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION); + static constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1; + + static constexpr u16 VALUE_SCALE = + (CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)); + static constexpr float VALUE_SCALE_DEGREES = VALUE_SCALE / float(MathUtil::TAU) * 360; + + static constexpr float SLOW_SCALE = VALUE_SCALE_DEGREES / CALIBRATION_SLOW_SCALE_DEGREES; + static constexpr float FAST_SCALE = VALUE_SCALE_DEGREES / CALIBRATION_FAST_SCALE_DEGREES; + + static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1), + "SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values."); + + static constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1); + static constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE; + static constexpr float FAST_MAX_RAD_PER_SEC = SENSOR_RANGE / FAST_SCALE; + MotionPlus(); void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; @@ -214,18 +241,6 @@ private: #pragma pack(pop) static_assert(0x100 == sizeof(Register), "Wrong size"); - static constexpr int CALIBRATION_BITS = 16; - - static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1); - // Values are similar to that of a typical real M+. - static constexpr u16 CALIBRATION_SCALE_OFFSET = 0x4400; - static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0; - static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e; - - static constexpr int BITS_OF_PRECISION = 14; - static constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION); - static constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1; - void Activate(); void Deactivate(); void OnPassthroughModeWrite(); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index 0394e21e6e..d14c61d729 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -162,6 +162,7 @@ public: // Active extension number is exposed for TAS. ExtensionNumber GetActiveExtensionNumber() const; + bool IsMotionPlusAttached() const; static Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec, diff --git a/Source/Core/DolphinQt/TAS/TASCheckBox.cpp b/Source/Core/DolphinQt/TAS/TASCheckBox.cpp index 32d1b3b0b0..bd3865e689 100644 --- a/Source/Core/DolphinQt/TAS/TASCheckBox.cpp +++ b/Source/Core/DolphinQt/TAS/TASCheckBox.cpp @@ -58,7 +58,7 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event) void TASCheckBox::OnUIValueChanged(int new_value) { - m_state.OnUIValueChanged(static_cast(new_value)); + m_state.OnUIValueChanged(new_value); } void TASCheckBox::ApplyControllerValueChange() diff --git a/Source/Core/DolphinQt/TAS/TASControlState.cpp b/Source/Core/DolphinQt/TAS/TASControlState.cpp index fd0c209aa6..a961d8e437 100644 --- a/Source/Core/DolphinQt/TAS/TASControlState.cpp +++ b/Source/Core/DolphinQt/TAS/TASControlState.cpp @@ -7,7 +7,7 @@ #include "Common/CommonTypes.h" -u16 TASControlState::GetValue() const +int TASControlState::GetValue() const { const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); @@ -16,7 +16,7 @@ u16 TASControlState::GetValue() const .value; } -bool TASControlState::OnControllerValueChanged(u16 new_value) +bool TASControlState::OnControllerValueChanged(int new_value) { const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); @@ -26,13 +26,13 @@ bool TASControlState::OnControllerValueChanged(u16 new_value) return false; } - const State new_state{static_cast(cpu_thread_state.version + 1), new_value}; + const State new_state{static_cast(cpu_thread_state.version + 1), new_value}; m_cpu_thread_state.store(new_state, std::memory_order_relaxed); return true; } -void TASControlState::OnUIValueChanged(u16 new_value) +void TASControlState::OnUIValueChanged(int new_value) { const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); @@ -40,7 +40,7 @@ void TASControlState::OnUIValueChanged(u16 new_value) m_ui_thread_state.store(new_state, std::memory_order_relaxed); } -u16 TASControlState::ApplyControllerValueChange() +int TASControlState::ApplyControllerValueChange() { const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); diff --git a/Source/Core/DolphinQt/TAS/TASControlState.h b/Source/Core/DolphinQt/TAS/TASControlState.h index b53c7f95f9..701bf01169 100644 --- a/Source/Core/DolphinQt/TAS/TASControlState.h +++ b/Source/Core/DolphinQt/TAS/TASControlState.h @@ -12,15 +12,15 @@ class TASControlState public: // Call this from the CPU thread to get the current value. (This function can also safely be // called from the UI thread, but you're effectively just getting the value the UI control has.) - u16 GetValue() const; + int GetValue() const; // Call this from the CPU thread when the controller state changes. // If the return value is true, queue up a call to ApplyControllerChangeValue on the UI thread. - bool OnControllerValueChanged(u16 new_value); + bool OnControllerValueChanged(int new_value); // Call this from the UI thread when the user changes the value using the UI. - void OnUIValueChanged(u16 new_value); + void OnUIValueChanged(int new_value); // Call this from the UI thread after OnControllerValueChanged returns true, // and set the state of the UI control to the return value. - u16 ApplyControllerValueChange(); + int ApplyControllerValueChange(); private: // A description of how threading is handled: The UI thread can update its copy of the state @@ -34,8 +34,8 @@ private: struct State { - u16 version = 0; - u16 value = 0; + int version = 0; + int value = 0; }; std::atomic m_ui_thread_state; diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp index d74a84997f..87e783e42a 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp @@ -94,8 +94,8 @@ TASCheckBox* TASInputWindow::CreateButton(const QString& text, std::string_view } QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_view group_name, - InputOverrider* overrider, u16 min_x, u16 min_y, - u16 max_x, u16 max_y, Qt::Key x_shortcut_key, + InputOverrider* overrider, int min_x, int min_y, + int max_x, int max_y, Qt::Key x_shortcut_key, Qt::Key y_shortcut_key) { const QKeySequence x_shortcut_key_sequence = QKeySequence(Qt::ALT | x_shortcut_key); @@ -153,7 +153,7 @@ QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_vi QBoxLayout* TASInputWindow::CreateSliderValuePairLayout( const QString& text, std::string_view group_name, std::string_view control_name, - InputOverrider* overrider, u16 zero, int default_, u16 min, u16 max, Qt::Key shortcut_key, + InputOverrider* overrider, int zero, int default_, int min, int max, Qt::Key shortcut_key, QWidget* shortcut_widget, std::optional scale) { const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key); @@ -172,7 +172,7 @@ QBoxLayout* TASInputWindow::CreateSliderValuePairLayout( TASSpinBox* TASInputWindow::CreateSliderValuePair( std::string_view group_name, std::string_view control_name, InputOverrider* overrider, - QBoxLayout* layout, u16 zero, int default_, u16 min, u16 max, + QBoxLayout* layout, int zero, int default_, int min, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget, std::optional scale) { @@ -200,7 +200,7 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair( // The shortcut_widget argument needs to specify the container widget that will be hidden/shown. // This is done to avoid ambigous shortcuts -TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max, +TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget) @@ -244,24 +244,24 @@ std::optional TASInputWindow::GetButton(TASCheckBox* checkbox, return checkbox->GetValue() ? 1.0 : 0.0; } -std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max, +std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero, int min, int max, ControlState controller_state) { - const u16 controller_value = - ControllerEmu::EmulatedController::MapFloat(controller_state, zero, 0, max); + const int controller_value = + ControllerEmu::EmulatedController::MapFloat(controller_state, zero, 0, max); if (m_use_controller->isChecked()) spin->OnControllerValueChanged(controller_value); - return ControllerEmu::EmulatedController::MapToFloat(spin->GetValue(), zero, + return ControllerEmu::EmulatedController::MapToFloat(spin->GetValue(), zero, min, max); } -std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, +std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero, ControlState controller_state, ControlState scale) { - const u16 controller_value = static_cast(std::llround(controller_state * scale + zero)); + const int controller_value = static_cast(std::llround(controller_state * scale + zero)); if (m_use_controller->isChecked()) spin->OnControllerValueChanged(controller_value); diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.h b/Source/Core/DolphinQt/TAS/TASInputWindow.h index 45d84ba014..34cc843633 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.h @@ -51,20 +51,20 @@ protected: TASCheckBox* CreateButton(const QString& text, std::string_view group_name, std::string_view control_name, InputOverrider* overrider); QGroupBox* CreateStickInputs(const QString& text, std::string_view group_name, - InputOverrider* overrider, u16 min_x, u16 min_y, u16 max_x, - u16 max_y, Qt::Key x_shortcut_key, Qt::Key y_shortcut_key); + InputOverrider* overrider, int min_x, int min_y, int max_x, + int max_y, Qt::Key x_shortcut_key, Qt::Key y_shortcut_key); QBoxLayout* CreateSliderValuePairLayout(const QString& text, std::string_view group_name, std::string_view control_name, InputOverrider* overrider, - u16 zero, int default_, u16 min, u16 max, + int zero, int default_, int min, int max, Qt::Key shortcut_key, QWidget* shortcut_widget, std::optional scale = {}); TASSpinBox* CreateSliderValuePair(std::string_view group_name, std::string_view control_name, - InputOverrider* overrider, QBoxLayout* layout, u16 zero, - int default_, u16 min, u16 max, + InputOverrider* overrider, QBoxLayout* layout, int zero, + int default_, int min, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget, std::optional scale = {}); - TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max, + TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget); @@ -75,8 +75,8 @@ protected: private: std::optional GetButton(TASCheckBox* checkbox, ControlState controller_state); - std::optional GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max, + std::optional GetSpinBox(TASSpinBox* spin, int zero, int min, int max, ControlState controller_state); - std::optional GetSpinBox(TASSpinBox* spin, u16 zero, ControlState controller_state, + std::optional GetSpinBox(TASSpinBox* spin, int zero, ControlState controller_state, ControlState scale); }; diff --git a/Source/Core/DolphinQt/TAS/TASSpinBox.cpp b/Source/Core/DolphinQt/TAS/TASSpinBox.cpp index 30c3618b26..8621656050 100644 --- a/Source/Core/DolphinQt/TAS/TASSpinBox.cpp +++ b/Source/Core/DolphinQt/TAS/TASSpinBox.cpp @@ -17,13 +17,13 @@ int TASSpinBox::GetValue() const void TASSpinBox::OnControllerValueChanged(int new_value) { - if (m_state.OnControllerValueChanged(static_cast(new_value))) + if (m_state.OnControllerValueChanged(new_value)) QueueOnObject(this, &TASSpinBox::ApplyControllerValueChange); } void TASSpinBox::OnUIValueChanged(int new_value) { - m_state.OnUIValueChanged(static_cast(new_value)); + m_state.OnUIValueChanged(new_value); } void TASSpinBox::ApplyControllerValueChange() diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp index b6e54fb6da..b9bfc2802c 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp @@ -22,6 +22,7 @@ #include "Core/HW/WiimoteEmu/Extension/Classic.h" #include "Core/HW/WiimoteEmu/Extension/Extension.h" #include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" +#include "Core/HW/WiimoteEmu/MotionPlus.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" @@ -40,8 +41,8 @@ using namespace WiimoteCommon; WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(parent), m_num(num) { - const QKeySequence ir_x_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_F); - const QKeySequence ir_y_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_G); + const QKeySequence ir_x_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_X); + const QKeySequence ir_y_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_C); m_ir_box = new QGroupBox(QStringLiteral("%1 (%2/%3)") .arg(tr("IR"), @@ -86,7 +87,7 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( m_nunchuk_stick_box = CreateStickInputs(tr("Nunchuk Stick"), WiimoteEmu::Nunchuk::STICK_GROUP, &m_nunchuk_overrider, - 0, 0, 255, 255, Qt::Key_X, Qt::Key_Y); + 0, 0, 255, 255, Qt::Key_F, Qt::Key_G); m_classic_left_stick_box = CreateStickInputs(tr("Left Stick"), WiimoteEmu::Classic::LEFT_STICK_GROUP, @@ -101,71 +102,111 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( m_ir_box->setMinimumWidth(20); m_nunchuk_stick_box->setMinimumWidth(20); - m_remote_orientation_box = new QGroupBox(tr("Wii Remote Orientation")); - auto* top_layout = new QHBoxLayout; top_layout->addWidget(m_ir_box); top_layout->addWidget(m_nunchuk_stick_box); top_layout->addWidget(m_classic_left_stick_box); top_layout->addWidget(m_classic_right_stick_box); + m_remote_accelerometer_box = new QGroupBox(tr("Wii Remote Accelerometer")); + constexpr u16 ACCEL_ZERO_G = WiimoteEmu::Wiimote::ACCEL_ZERO_G << 2; constexpr u16 ACCEL_ONE_G = WiimoteEmu::Wiimote::ACCEL_ONE_G << 2; constexpr u16 ACCEL_MIN = 0; constexpr u16 ACCEL_MAX = (1 << 10) - 1; constexpr double ACCEL_SCALE = (ACCEL_ONE_G - ACCEL_ZERO_G) / MathUtil::GRAVITY_ACCELERATION; - auto* remote_orientation_x_layout = + auto* remote_accelerometer_x_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("X"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, &m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_Q, m_remote_orientation_box, ACCEL_SCALE); - auto* remote_orientation_y_layout = + ACCEL_MAX, Qt::Key_Q, m_remote_accelerometer_box, ACCEL_SCALE); + auto* remote_accelerometer_y_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("Y"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, &m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_W, m_remote_orientation_box, ACCEL_SCALE); - auto* remote_orientation_z_layout = + ACCEL_MAX, Qt::Key_W, m_remote_accelerometer_box, ACCEL_SCALE); + auto* remote_accelerometer_z_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("Z"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE, &m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ONE_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_E, m_remote_orientation_box, ACCEL_SCALE); + ACCEL_MAX, Qt::Key_E, m_remote_accelerometer_box, ACCEL_SCALE); - auto* remote_orientation_layout = new QVBoxLayout; - remote_orientation_layout->addLayout(remote_orientation_x_layout); - remote_orientation_layout->addLayout(remote_orientation_y_layout); - remote_orientation_layout->addLayout(remote_orientation_z_layout); - m_remote_orientation_box->setLayout(remote_orientation_layout); + auto* remote_accelerometer_layout = new QVBoxLayout; + remote_accelerometer_layout->addLayout(remote_accelerometer_x_layout); + remote_accelerometer_layout->addLayout(remote_accelerometer_y_layout); + remote_accelerometer_layout->addLayout(remote_accelerometer_z_layout); + m_remote_accelerometer_box->setLayout(remote_accelerometer_layout); - m_nunchuk_orientation_box = new QGroupBox(tr("Nunchuk Orientation")); + m_remote_gyroscope_box = new QGroupBox(tr("Wii Remote Gyroscope")); - auto* nunchuk_orientation_x_layout = + // MotionPlus can report values using either a slow scale (greater precision) or a fast scale + // (greater range). To ensure the user can select every possible value, TAS input uses the + // precision of the slow scale and the range of the fast scale. This does mean TAS input has more + // selectable values than MotionPlus has reportable values, but that's not too big of a problem. + constexpr double GYRO_STRETCH = + static_cast(WiimoteEmu::MotionPlus::CALIBRATION_FAST_SCALE_DEGREES) / + WiimoteEmu::MotionPlus::CALIBRATION_SLOW_SCALE_DEGREES; + + constexpr u32 GYRO_MIN = 0; + constexpr u32 GYRO_MAX = WiimoteEmu::MotionPlus::MAX_VALUE * GYRO_STRETCH; + constexpr u32 GYRO_ZERO = WiimoteEmu::MotionPlus::ZERO_VALUE * GYRO_STRETCH; + constexpr double GYRO_SCALE = GYRO_MAX / 2 / WiimoteEmu::MotionPlus::FAST_MAX_RAD_PER_SEC; + + auto* remote_gyroscope_x_layout = + // i18n: Refers to a 3D axis (used when mapping motion controls) + CreateSliderValuePairLayout(tr("X"), WiimoteEmu::Wiimote::GYROSCOPE_GROUP, + ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, + &m_wiimote_overrider, GYRO_ZERO, GYRO_ZERO, GYRO_MIN, GYRO_MAX, + Qt::Key_R, m_remote_gyroscope_box, GYRO_SCALE); + auto* remote_gyroscope_y_layout = + // i18n: Refers to a 3D axis (used when mapping motion controls) + CreateSliderValuePairLayout(tr("Y"), WiimoteEmu::Wiimote::GYROSCOPE_GROUP, + ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, + &m_wiimote_overrider, GYRO_ZERO, GYRO_ZERO, GYRO_MIN, GYRO_MAX, + Qt::Key_T, m_remote_gyroscope_box, GYRO_SCALE); + auto* remote_gyroscope_z_layout = + // i18n: Refers to a 3D axis (used when mapping motion controls) + CreateSliderValuePairLayout(tr("Z"), WiimoteEmu::Wiimote::GYROSCOPE_GROUP, + ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE, + &m_wiimote_overrider, GYRO_ZERO, GYRO_ZERO, GYRO_MIN, GYRO_MAX, + Qt::Key_Y, m_remote_gyroscope_box, GYRO_SCALE); + + auto* remote_gyroscope_layout = new QVBoxLayout; + remote_gyroscope_layout->addLayout(remote_gyroscope_x_layout); + remote_gyroscope_layout->addLayout(remote_gyroscope_y_layout); + remote_gyroscope_layout->addLayout(remote_gyroscope_z_layout); + m_remote_gyroscope_box->setLayout(remote_gyroscope_layout); + + m_nunchuk_accelerometer_box = new QGroupBox(tr("Nunchuk Accelerometer")); + + auto* nunchuk_accelerometer_x_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("X"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, &m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_I, m_nunchuk_orientation_box); - auto* nunchuk_orientation_y_layout = + ACCEL_MAX, Qt::Key_I, m_nunchuk_accelerometer_box); + auto* nunchuk_accelerometer_y_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("Y"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, &m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_O, m_nunchuk_orientation_box); - auto* nunchuk_orientation_z_layout = + ACCEL_MAX, Qt::Key_O, m_nunchuk_accelerometer_box); + auto* nunchuk_accelerometer_z_layout = // i18n: Refers to a 3D axis (used when mapping motion controls) CreateSliderValuePairLayout(tr("Z"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP, ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE, &m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ONE_G, ACCEL_MIN, - ACCEL_MAX, Qt::Key_P, m_nunchuk_orientation_box); + ACCEL_MAX, Qt::Key_P, m_nunchuk_accelerometer_box); - auto* nunchuk_orientation_layout = new QVBoxLayout; - nunchuk_orientation_layout->addLayout(nunchuk_orientation_x_layout); - nunchuk_orientation_layout->addLayout(nunchuk_orientation_y_layout); - nunchuk_orientation_layout->addLayout(nunchuk_orientation_z_layout); - m_nunchuk_orientation_box->setLayout(nunchuk_orientation_layout); + auto* nunchuk_accelerometer_layout = new QVBoxLayout; + nunchuk_accelerometer_layout->addLayout(nunchuk_accelerometer_x_layout); + nunchuk_accelerometer_layout->addLayout(nunchuk_accelerometer_y_layout); + nunchuk_accelerometer_layout->addLayout(nunchuk_accelerometer_z_layout); + m_nunchuk_accelerometer_box->setLayout(nunchuk_accelerometer_layout); m_triggers_box = new QGroupBox(tr("Triggers")); auto* l_trigger_layout = CreateSliderValuePairLayout( @@ -294,8 +335,9 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( auto* layout = new QVBoxLayout; layout->addLayout(top_layout); - layout->addWidget(m_remote_orientation_box); - layout->addWidget(m_nunchuk_orientation_box); + layout->addWidget(m_remote_accelerometer_box); + layout->addWidget(m_remote_gyroscope_box); + layout->addWidget(m_nunchuk_accelerometer_box); layout->addWidget(m_triggers_box); layout->addWidget(m_remote_buttons_box); layout->addWidget(m_nunchuk_buttons_box); @@ -307,13 +349,16 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( if (Core::IsRunning()) { m_active_extension = GetWiimote()->GetActiveExtensionNumber(); + m_is_motion_plus_attached = GetWiimote()->IsMotionPlusAttached(); } else { IniFile ini; ini.Load(File::GetUserPath(D_CONFIG_IDX) + "WiimoteNew.ini"); + const std::string section_name = "Wiimote" + std::to_string(num + 1); + std::string extension; - ini.GetIfExists("Wiimote" + std::to_string(num + 1), "Extension", &extension); + ini.GetIfExists(section_name, "Extension", &extension); if (extension == "Nunchuk") m_active_extension = WiimoteEmu::ExtensionNumber::NUNCHUK; @@ -321,6 +366,9 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( m_active_extension = WiimoteEmu::ExtensionNumber::CLASSIC; else m_active_extension = WiimoteEmu::ExtensionNumber::NONE; + + m_is_motion_plus_attached = true; + ini.GetIfExists(section_name, "Extension/Attach MotionPlus", &m_is_motion_plus_attached); } UpdateExt(); } @@ -351,8 +399,9 @@ void WiiTASInputWindow::UpdateExt() m_nunchuk_stick_box->show(); m_classic_right_stick_box->hide(); m_classic_left_stick_box->hide(); - m_remote_orientation_box->show(); - m_nunchuk_orientation_box->show(); + m_remote_accelerometer_box->show(); + m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached); + m_nunchuk_accelerometer_box->show(); m_triggers_box->hide(); m_nunchuk_buttons_box->show(); m_remote_buttons_box->show(); @@ -365,8 +414,9 @@ void WiiTASInputWindow::UpdateExt() m_nunchuk_stick_box->hide(); m_classic_right_stick_box->show(); m_classic_left_stick_box->show(); - m_remote_orientation_box->hide(); - m_nunchuk_orientation_box->hide(); + m_remote_accelerometer_box->hide(); + m_remote_gyroscope_box->hide(); + m_nunchuk_accelerometer_box->hide(); m_triggers_box->show(); m_remote_buttons_box->hide(); m_nunchuk_buttons_box->hide(); @@ -379,8 +429,9 @@ void WiiTASInputWindow::UpdateExt() m_nunchuk_stick_box->hide(); m_classic_right_stick_box->hide(); m_classic_left_stick_box->hide(); - m_remote_orientation_box->show(); - m_nunchuk_orientation_box->hide(); + m_remote_accelerometer_box->show(); + m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached); + m_nunchuk_accelerometer_box->hide(); m_triggers_box->hide(); m_remote_buttons_box->show(); m_nunchuk_buttons_box->hide(); diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h index 63ce0fb4df..a77c38bb4c 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h @@ -42,6 +42,7 @@ private: void UpdateExt(); WiimoteEmu::ExtensionNumber m_active_extension; + bool m_is_motion_plus_attached; int m_num; InputOverrider m_wiimote_overrider; @@ -78,8 +79,9 @@ private: TASCheckBox* m_classic_right_button; TASSpinBox* m_ir_x_value; TASSpinBox* m_ir_y_value; - QGroupBox* m_remote_orientation_box; - QGroupBox* m_nunchuk_orientation_box; + QGroupBox* m_remote_accelerometer_box; + QGroupBox* m_remote_gyroscope_box; + QGroupBox* m_nunchuk_accelerometer_box; QGroupBox* m_ir_box; QGroupBox* m_nunchuk_stick_box; QGroupBox* m_classic_left_stick_box;