Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #11638 from JosJuice/tas-input-motionplus
DolphinQt: Add MotionPlus support to TAS input
  • Loading branch information
lioncash committed Mar 10, 2023
2 parents 00a6f8c + 4d34f86 commit e4df388
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 101 deletions.
5 changes: 5 additions & 0 deletions Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp
Expand Up @@ -601,4 +601,9 @@ ExtensionNumber Wiimote::GetActiveExtensionNumber() const
return m_active_extension;
}

bool Wiimote::IsMotionPlusAttached() const
{
return m_is_motion_plus_attached;
}

} // namespace WiimoteEmu
22 changes: 5 additions & 17 deletions Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp
Expand Up @@ -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);
Expand All @@ -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(
Expand Down
39 changes: 27 additions & 12 deletions Source/Core/Core/HW/WiimoteEmu/MotionPlus.h
Expand Up @@ -6,6 +6,7 @@
#include <array>

#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
#include "Common/Swap.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
Expand Down Expand Up @@ -116,6 +117,32 @@ struct MotionPlus : public Extension
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;
Expand Down Expand Up @@ -214,18 +241,6 @@ struct MotionPlus : public Extension
#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();
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h
Expand Up @@ -162,6 +162,7 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::

// 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,
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/DolphinQt/TAS/TASCheckBox.cpp
Expand Up @@ -58,7 +58,7 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event)

void TASCheckBox::OnUIValueChanged(int new_value)
{
m_state.OnUIValueChanged(static_cast<u16>(new_value));
m_state.OnUIValueChanged(new_value);
}

void TASCheckBox::ApplyControllerValueChange()
Expand Down
10 changes: 5 additions & 5 deletions Source/Core/DolphinQt/TAS/TASControlState.cpp
Expand Up @@ -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);
Expand All @@ -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);

Expand All @@ -26,21 +26,21 @@ bool TASControlState::OnControllerValueChanged(u16 new_value)
return false;
}

const State new_state{static_cast<u16>(cpu_thread_state.version + 1), new_value};
const State new_state{static_cast<int>(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);

const State new_state{ui_thread_state.version, 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);
Expand Down
12 changes: 6 additions & 6 deletions Source/Core/DolphinQt/TAS/TASControlState.h
Expand Up @@ -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
Expand All @@ -34,8 +34,8 @@ class TASControlState

struct State
{
u16 version = 0;
u16 value = 0;
int version = 0;
int value = 0;
};

std::atomic<State> m_ui_thread_state;
Expand Down
22 changes: 11 additions & 11 deletions Source/Core/DolphinQt/TAS/TASInputWindow.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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<ControlState> scale)
{
const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key);
Expand All @@ -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<ControlState> scale)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -244,24 +244,24 @@ std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
return checkbox->GetValue() ? 1.0 : 0.0;
}

std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max,
std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero, int min, int max,
ControlState controller_state)
{
const u16 controller_value =
ControllerEmu::EmulatedController::MapFloat<u16>(controller_state, zero, 0, max);
const int controller_value =
ControllerEmu::EmulatedController::MapFloat<int>(controller_state, zero, 0, max);

if (m_use_controller->isChecked())
spin->OnControllerValueChanged(controller_value);

return ControllerEmu::EmulatedController::MapToFloat<ControlState, u16>(spin->GetValue(), zero,
return ControllerEmu::EmulatedController::MapToFloat<ControlState, int>(spin->GetValue(), zero,
min, max);
}

std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero,
std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero,
ControlState controller_state,
ControlState scale)
{
const u16 controller_value = static_cast<u16>(std::llround(controller_state * scale + zero));
const int controller_value = static_cast<int>(std::llround(controller_state * scale + zero));

if (m_use_controller->isChecked())
spin->OnControllerValueChanged(controller_value);
Expand Down
16 changes: 8 additions & 8 deletions Source/Core/DolphinQt/TAS/TASInputWindow.h
Expand Up @@ -51,20 +51,20 @@ class TASInputWindow : public QDialog
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<ControlState> 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<ControlState> 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);

Expand All @@ -75,8 +75,8 @@ class TASInputWindow : public QDialog

private:
std::optional<ControlState> GetButton(TASCheckBox* checkbox, ControlState controller_state);
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max,
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, int zero, int min, int max,
ControlState controller_state);
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, u16 zero, ControlState controller_state,
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, int zero, ControlState controller_state,
ControlState scale);
};
4 changes: 2 additions & 2 deletions Source/Core/DolphinQt/TAS/TASSpinBox.cpp
Expand Up @@ -17,13 +17,13 @@ int TASSpinBox::GetValue() const

void TASSpinBox::OnControllerValueChanged(int new_value)
{
if (m_state.OnControllerValueChanged(static_cast<u16>(new_value)))
if (m_state.OnControllerValueChanged(new_value))
QueueOnObject(this, &TASSpinBox::ApplyControllerValueChange);
}

void TASSpinBox::OnUIValueChanged(int new_value)
{
m_state.OnUIValueChanged(static_cast<u16>(new_value));
m_state.OnUIValueChanged(new_value);
}

void TASSpinBox::ApplyControllerValueChange()
Expand Down

0 comments on commit e4df388

Please sign in to comment.