diff --git a/Source/Core/Common/Matrix.cpp b/Source/Core/Common/Matrix.cpp index 9f5ff40cb3..06891edf1e 100644 --- a/Source/Core/Common/Matrix.cpp +++ b/Source/Core/Common/Matrix.cpp @@ -4,6 +4,8 @@ #include "Common/Matrix.h" +#include "Common/MathUtil.h" + #include #include @@ -121,6 +123,32 @@ Vec3 operator*(const Quaternion& lhs, const Vec3& rhs) return Vec3(result.data.x, result.data.y, result.data.z); } +Vec3 FromQuaternionToEuler(const Quaternion& q) +{ + Vec3 result; + + const float qx = q.data.x; + const float qy = q.data.y; + const float qz = q.data.z; + const float qw = q.data.w; + + const float sinr_cosp = 2 * (qw * qx + qy * qz); + const float cosr_cosp = 1 - 2 * (qx * qx + qy * qy); + result.x = std::atan2(sinr_cosp, cosr_cosp); + + const float sinp = 2 * (qw * qy - qz * qx); + if (std::abs(sinp) >= 1) + result.y = std::copysign(MathUtil::PI / 2, sinp); // use 90 degrees if out of range + else + result.y = std::asin(sinp); + + const float siny_cosp = 2 * (qw * qz + qx * qy); + const float cosy_cosp = 1 - 2 * (qy * qy + qz * qz); + result.z = std::atan2(siny_cosp, cosy_cosp); + + return result; +} + Matrix33 Matrix33::Identity() { Matrix33 mtx = {}; diff --git a/Source/Core/Common/Matrix.h b/Source/Core/Common/Matrix.h index a1c9f088c6..c3341a9bba 100644 --- a/Source/Core/Common/Matrix.h +++ b/Source/Core/Common/Matrix.h @@ -359,6 +359,8 @@ public: Quaternion operator*(Quaternion lhs, const Quaternion& rhs); Vec3 operator*(const Quaternion& lhs, const Vec3& rhs); +Vec3 FromQuaternionToEuler(const Quaternion& q); + class Matrix33 { public: diff --git a/Source/Core/Core/FreeLookManager.cpp b/Source/Core/Core/FreeLookManager.cpp index dece2214fe..13acb58635 100644 --- a/Source/Core/Core/FreeLookManager.cpp +++ b/Source/Core/Core/FreeLookManager.cpp @@ -12,6 +12,7 @@ #include "Core/FreeLookConfig.h" #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" +#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h" #include "InputCommon/InputConfig.h" #include "VideoCommon/FreeLookCamera.h" @@ -60,6 +61,19 @@ enum FieldOfViewButtons DecreaseY, }; } + +namespace GyroButtons +{ +enum GyroButtons +{ + PitchUp, + PitchDown, + RollLeft, + RollRight, + YawLeft, + YawRight, +}; +} } // namespace FreeLookController::FreeLookController(const unsigned int index) : m_index(index) @@ -89,6 +103,9 @@ FreeLookController::FreeLookController(const unsigned int index) : m_index(index m_fov_buttons->AddInput(ControllerEmu::Translate, _trans("Decrease X")); m_fov_buttons->AddInput(ControllerEmu::Translate, _trans("Increase Y")); m_fov_buttons->AddInput(ControllerEmu::Translate, _trans("Decrease Y")); + + groups.emplace_back(m_rotation_gyro = new ControllerEmu::IMUGyroscope( + _trans("Incremental Rotation"), _trans("Incremental Rotation"))); } std::string FreeLookController::GetName() const @@ -125,6 +142,35 @@ void FreeLookController::LoadDefaults(const ControllerInterface& ciface) hotkey_string({"Shift", "`Axis Z+`"})); m_fov_buttons->SetControlExpression(FieldOfViewButtons::DecreaseY, hotkey_string({"Shift", "`Axis Z-`"})); + +#if defined HAVE_X11 && HAVE_X11 + m_rotation_gyro->SetControlExpression(GyroButtons::PitchUp, + "if(`Click 3`,`RelativeMouse Y-` * 0.10, 0)"); + m_rotation_gyro->SetControlExpression(GyroButtons::PitchDown, + "if(`Click 3`,`RelativeMouse Y+` * 0.10, 0)"); +#else + m_rotation_gyro->SetControlExpression(GyroButtons::PitchUp, + "if(`Click 1`,`RelativeMouse Y-` * 0.10, 0)"); + m_rotation_gyro->SetControlExpression(GyroButtons::PitchDown, + "if(`Click 1`,`RelativeMouse Y+` * 0.10, 0)"); +#endif + + m_rotation_gyro->SetControlExpression(GyroButtons::RollLeft, + "if(`Click 2`,`RelativeMouse X-` * 0.10, 0)"); + m_rotation_gyro->SetControlExpression(GyroButtons::RollRight, + "if(`Click 2`,`RelativeMouse X+` * 0.10, 0)"); + +#if defined HAVE_X11 && HAVE_X11 + m_rotation_gyro->SetControlExpression(GyroButtons::YawLeft, + "if(`Click 3`,`RelativeMouse X-` * 0.10, 0)"); + m_rotation_gyro->SetControlExpression(GyroButtons::YawRight, + "if(`Click 3`,`RelativeMouse X+` * 0.10, 0)"); +#else + m_rotation_gyro->SetControlExpression(GyroButtons::YawLeft, + "if(`Click 1`,`RelativeMouse X-` * 0.10, 0)"); + m_rotation_gyro->SetControlExpression(GyroButtons::YawRight, + "if(`Click 1`,`RelativeMouse X+` * 0.10, 0)"); +#endif } ControllerEmu::ControlGroup* FreeLookController::GetGroup(FreeLookGroup group) const @@ -139,6 +185,8 @@ ControllerEmu::ControlGroup* FreeLookController::GetGroup(FreeLookGroup group) c return m_fov_buttons; case FreeLookGroup::Other: return m_other_buttons; + case FreeLookGroup::Rotation: + return m_rotation_gyro; default: return nullptr; } @@ -151,41 +199,63 @@ void FreeLookController::Update() const auto lock = GetStateLock(); + float dt = 1.0; + if (m_last_free_look_rotate_time) + { + using seconds = std::chrono::duration>; + dt = std::chrono::duration_cast(std::chrono::steady_clock::now() - + *m_last_free_look_rotate_time) + .count(); + } + m_last_free_look_rotate_time = std::chrono::steady_clock::now(); + + const auto gyro_motion_rad_velocity = + m_rotation_gyro->GetState() ? *m_rotation_gyro->GetState() : Common::Vec3{}; + + // Due to gyroscope implementation we need to swap the yaw and roll values + // and because of the different axis used for Wii and the PS3 motion directions, + // we need to invert the yaw and roll as well + const Common::Vec3 gyro_motion_rad_velocity_converted{ + gyro_motion_rad_velocity.x, gyro_motion_rad_velocity.z * -1, gyro_motion_rad_velocity.y * -1}; + const auto gyro_motion_quat = + Common::Quaternion::RotateXYZ(gyro_motion_rad_velocity_converted * dt); + + g_freelook_camera.Rotate(gyro_motion_quat); if (m_move_buttons->controls[MoveButtons::Up]->GetState()) - g_freelook_camera.MoveVertical(-g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveVertical(-g_freelook_camera.GetSpeed() * dt); if (m_move_buttons->controls[MoveButtons::Down]->GetState()) - g_freelook_camera.MoveVertical(g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveVertical(g_freelook_camera.GetSpeed() * dt); if (m_move_buttons->controls[MoveButtons::Left]->GetState()) - g_freelook_camera.MoveHorizontal(g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveHorizontal(g_freelook_camera.GetSpeed() * dt); if (m_move_buttons->controls[MoveButtons::Right]->GetState()) - g_freelook_camera.MoveHorizontal(-g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveHorizontal(-g_freelook_camera.GetSpeed() * dt); if (m_move_buttons->controls[MoveButtons::Forward]->GetState()) - g_freelook_camera.MoveForward(g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveForward(g_freelook_camera.GetSpeed() * dt); if (m_move_buttons->controls[MoveButtons::Backward]->GetState()) - g_freelook_camera.MoveForward(-g_freelook_camera.GetSpeed()); + g_freelook_camera.MoveForward(-g_freelook_camera.GetSpeed() * dt); if (m_fov_buttons->controls[FieldOfViewButtons::IncreaseX]->GetState()) - g_freelook_camera.IncreaseFovX(g_freelook_camera.GetFovStepSize()); + g_freelook_camera.IncreaseFovX(g_freelook_camera.GetFovStepSize() * dt); if (m_fov_buttons->controls[FieldOfViewButtons::DecreaseX]->GetState()) - g_freelook_camera.IncreaseFovX(-1.0f * g_freelook_camera.GetFovStepSize()); + g_freelook_camera.IncreaseFovX(-1.0f * g_freelook_camera.GetFovStepSize() * dt); if (m_fov_buttons->controls[FieldOfViewButtons::IncreaseY]->GetState()) - g_freelook_camera.IncreaseFovY(g_freelook_camera.GetFovStepSize()); + g_freelook_camera.IncreaseFovY(g_freelook_camera.GetFovStepSize() * dt); if (m_fov_buttons->controls[FieldOfViewButtons::DecreaseY]->GetState()) - g_freelook_camera.IncreaseFovY(-1.0f * g_freelook_camera.GetFovStepSize()); + g_freelook_camera.IncreaseFovY(-1.0f * g_freelook_camera.GetFovStepSize() * dt); if (m_speed_buttons->controls[SpeedButtons::Decrease]->GetState()) - g_freelook_camera.ModifySpeed(1.0f / 1.1f); + g_freelook_camera.ModifySpeed(g_freelook_camera.GetSpeed() * -0.9 * dt); if (m_speed_buttons->controls[SpeedButtons::Increase]->GetState()) - g_freelook_camera.ModifySpeed(1.1f); + g_freelook_camera.ModifySpeed(g_freelook_camera.GetSpeed() * 1.1 * dt); if (m_speed_buttons->controls[SpeedButtons::Reset]->GetState()) g_freelook_camera.ResetSpeed(); diff --git a/Source/Core/Core/FreeLookManager.h b/Source/Core/Core/FreeLookManager.h index a74d26271f..ec809461c2 100644 --- a/Source/Core/Core/FreeLookManager.h +++ b/Source/Core/Core/FreeLookManager.h @@ -4,6 +4,9 @@ #pragma once +#include +#include + #include "Common/CommonTypes.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" @@ -13,6 +16,7 @@ namespace ControllerEmu { class ControlGroup; class Buttons; +class IMUGyroscope; } // namespace ControllerEmu enum class FreeLookGroup @@ -20,7 +24,8 @@ enum class FreeLookGroup Move, Speed, FieldOfView, - Other + Other, + Rotation, }; namespace FreeLook @@ -52,6 +57,8 @@ private: ControllerEmu::Buttons* m_speed_buttons; ControllerEmu::Buttons* m_fov_buttons; ControllerEmu::Buttons* m_other_buttons; + ControllerEmu::IMUGyroscope* m_rotation_gyro; const unsigned int m_index; + std::optional m_last_free_look_rotate_time; }; diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index 348a16339b..ff349c64bf 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -655,6 +655,7 @@ void UpdateDevices() // Update inputs at the rate of SI // Typically 120hz but is variable + g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::SerialInterface); g_controller_interface.UpdateInput(); // Update channels and set the status bit if there's new data diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp index 9958030fbd..3440417cce 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp @@ -339,6 +339,7 @@ void BluetoothEmuDevice::Update() if (now - m_last_ticks > interval) { + g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth); g_controller_interface.UpdateInput(); for (auto& wiimote : m_wiimotes) wiimote->UpdateInput(); diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index ad248b320d..41b24a9c26 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -122,6 +122,8 @@ add_executable(dolphin-emu Config/LogWidget.h Config/Mapping/FreeLookGeneral.cpp Config/Mapping/FreeLookGeneral.h + Config/Mapping/FreeLookRotation.cpp + Config/Mapping/FreeLookRotation.h Config/Mapping/GCKeyboardEmu.cpp Config/Mapping/GCKeyboardEmu.h Config/Mapping/GCMicrophone.cpp diff --git a/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.cpp b/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.cpp new file mode 100644 index 0000000000..c01e9d1241 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.cpp @@ -0,0 +1,62 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Mapping/FreeLookRotation.h" + +#include +#include +#include +#include +#include + +#include "Core/FreeLookManager.h" +#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h" +#include "InputCommon/InputConfig.h" + +FreeLookRotation::FreeLookRotation(MappingWindow* window) : MappingWidget(window) +{ + CreateMainLayout(); +} + +void FreeLookRotation::CreateMainLayout() +{ + m_main_layout = new QGridLayout; + + auto* alternate_input_layout = new QHBoxLayout(); + auto* note_label = new QLabel( + tr("Note: motion input may require configuring alternate input sources before use.")); + note_label->setWordWrap(true); + auto* alternate_input_sources_button = new QPushButton(tr("Alternate Input Sources")); + alternate_input_layout->addWidget(note_label, 1); + alternate_input_layout->addWidget(alternate_input_sources_button, 0, Qt::AlignRight); + connect(alternate_input_sources_button, &QPushButton::clicked, this, [this] { + ControllerInterfaceWindow* window = new ControllerInterfaceWindow(this); + window->setAttribute(Qt::WA_DeleteOnClose, true); + window->setWindowModality(Qt::WindowModality::WindowModal); + window->show(); + }); + m_main_layout->addLayout(alternate_input_layout, 0, 0, 1, -1); + + m_main_layout->addWidget( + CreateGroupBox(tr("Incremental Rotation (rad/sec)"), + FreeLook::GetInputGroup(GetPort(), FreeLookGroup::Rotation)), + 1, 0); + + setLayout(m_main_layout); +} + +InputConfig* FreeLookRotation::GetConfig() +{ + return FreeLook::GetInputConfig(); +} + +void FreeLookRotation::LoadSettings() +{ + FreeLook::LoadInputConfig(); +} + +void FreeLookRotation::SaveSettings() +{ + FreeLook::GetInputConfig()->SaveConfig(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.h b/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.h new file mode 100644 index 0000000000..de87aba59e --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/FreeLookRotation.h @@ -0,0 +1,26 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "DolphinQt/Config/Mapping/MappingWidget.h" + +class QGridLayout; + +class FreeLookRotation final : public MappingWidget +{ + Q_OBJECT +public: + explicit FreeLookRotation(MappingWindow* window); + + InputConfig* GetConfig() override; + +private: + void LoadSettings() override; + void SaveSettings() override; + void CreateMainLayout(); + + // Main + QGridLayout* m_main_layout; +}; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 976b289d44..ae9b2e0cc5 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -23,6 +23,7 @@ #include "Common/StringUtil.h" #include "DolphinQt/Config/Mapping/FreeLookGeneral.h" +#include "DolphinQt/Config/Mapping/FreeLookRotation.h" #include "DolphinQt/Config/Mapping/GCKeyboardEmu.h" #include "DolphinQt/Config/Mapping/GCMicrophone.h" #include "DolphinQt/Config/Mapping/GCPadEmu.h" @@ -436,6 +437,7 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) { widget = new FreeLookGeneral(this); AddWidget(tr("General"), widget); + AddWidget(tr("Rotation"), new FreeLookRotation(this)); setWindowTitle(tr("Free Look Controller %1").arg(GetPort() + 1)); } break; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 3a21497eef..18a838896e 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -80,6 +80,7 @@ + @@ -254,6 +255,7 @@ + diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 3b81e99a1d..7ac02353cb 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -139,10 +139,14 @@ void HotkeyScheduler::Run() while (!m_stop_requested.IsSet()) { - Common::SleepCurrentThread(1000 / 60); + Common::SleepCurrentThread(5); - if (Core::GetState() == Core::State::Uninitialized || Core::GetState() == Core::State::Paused) - g_controller_interface.UpdateInput(); + g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::FreeLook); + g_controller_interface.UpdateInput(); + FreeLook::UpdateInput(); + + g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Host); + g_controller_interface.UpdateInput(); if (!HotkeyManagerEmu::IsEnabled()) continue; @@ -546,8 +550,6 @@ void HotkeyScheduler::Run() OSD::AddMessage(StringFromFormat("Free Look: %s", new_value ? "Enabled" : "Disabled")); } - FreeLook::UpdateInput(); - // Savestates for (u32 i = 0; i < State::NUM_STATES; i++) { diff --git a/Source/Core/DolphinQt/HotkeyScheduler.h b/Source/Core/DolphinQt/HotkeyScheduler.h index f104abb44d..4717eb4492 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.h +++ b/Source/Core/DolphinQt/HotkeyScheduler.h @@ -8,6 +8,7 @@ #include +#include "Common/CommonTypes.h" #include "Common/Flag.h" #include "InputCommon/InputProfile.h" diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 6be674526a..7347fa3ef8 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -32,9 +32,7 @@ #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" -#include "VideoCommon/FreeLookCamera.h" #include "VideoCommon/RenderBase.h" -#include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoConfig.h" RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent) @@ -180,11 +178,6 @@ bool RenderWidget::event(QEvent* event) break; } - case QEvent::MouseMove: - if (g_freelook_camera.IsActive()) - OnFreeLookMouseMove(static_cast(event)); - [[fallthrough]]; - case QEvent::MouseButtonPress: if (!Settings::Instance().GetHideCursor() && isActiveWindow()) { @@ -237,23 +230,6 @@ bool RenderWidget::event(QEvent* event) return QWidget::event(event); } -void RenderWidget::OnFreeLookMouseMove(QMouseEvent* event) -{ - const auto mouse_move = event->pos() - m_last_mouse; - m_last_mouse = event->pos(); - - if (event->buttons() & Qt::RightButton) - { - // Camera Pitch and Yaw: - g_freelook_camera.Rotate(Common::Vec3{mouse_move.y() / 200.f, mouse_move.x() / 200.f, 0.f}); - } - else if (event->buttons() & Qt::MiddleButton) - { - // Camera Roll: - g_freelook_camera.Rotate({0.f, 0.f, mouse_move.x() / 200.f}); - } -} - void RenderWidget::PassEventToImGui(const QEvent* event) { if (!Core::IsRunningAndStarted()) diff --git a/Source/Core/DolphinQt/RenderWidget.h b/Source/Core/DolphinQt/RenderWidget.h index 650ba2d1dc..975b8399f1 100644 --- a/Source/Core/DolphinQt/RenderWidget.h +++ b/Source/Core/DolphinQt/RenderWidget.h @@ -33,7 +33,6 @@ private: void HandleCursorTimer(); void OnHideCursorChanged(); void OnKeepOnTopChanged(bool top); - void OnFreeLookMouseMove(QMouseEvent* event); void PassEventToImGui(const QEvent* event); void SetImGuiKeyMap(); void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp index ef0e10d168..b5408cfe34 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp @@ -4,6 +4,7 @@ #include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h" +#include #include #include "Common/Common.h" @@ -124,7 +125,8 @@ auto IMUGyroscope::GetRawState() const -> StateData std::optional IMUGyroscope::GetState() const { - if (controls[0]->control_ref->BoundCount() == 0) + if (std::all_of(controls.begin(), controls.end(), + [](const auto& control) { return control->control_ref->BoundCount() == 0; })) { // Set calibration to zero. m_calibration = {}; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 1d198ca494..fe176ce7f7 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -37,6 +37,8 @@ ControllerInterface g_controller_interface; +static thread_local ciface::InputChannel tls_input_channel = ciface::InputChannel::Host; + void ControllerInterface::Initialize(const WindowSystemInfo& wsi) { if (m_is_init) @@ -274,6 +276,16 @@ void ControllerInterface::UpdateInput() } } +void ControllerInterface::SetCurrentInputChannel(ciface::InputChannel input_channel) +{ + tls_input_channel = input_channel; +} + +ciface::InputChannel ControllerInterface::GetCurrentInputChannel() +{ + return tls_input_channel; +} + void ControllerInterface::SetAspectRatioAdjustment(float value) { m_aspect_ratio_adjustment = value; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 254516fb6e..12d17614da 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -32,6 +32,23 @@ #endif #define CIFACE_USE_DUALSHOCKUDPCLIENT +namespace ciface +{ +// A thread local "input channel" is maintained to handle the state of relative inputs. +// This allows simultaneous use of relative inputs across different input contexts. +// e.g. binding relative mouse movements to both GameCube controllers and FreeLook. +// These operate at different rates and processing one would break the other without separate state. +enum class InputChannel +{ + Host, + SerialInterface, + Bluetooth, + FreeLook, + Count, +}; + +} // namespace ciface + // // ControllerInterface // @@ -66,6 +83,9 @@ public: void UnregisterDevicesChangedCallback(const HotplugCallbackHandle& handle); void InvokeDevicesChangedCallbacks() const; + static void SetCurrentInputChannel(ciface::InputChannel); + static ciface::InputChannel GetCurrentInputChannel(); + private: std::list> m_devices_changed_callbacks; mutable std::mutex m_callbacks_mutex; @@ -75,4 +95,37 @@ private: std::atomic m_aspect_ratio_adjustment = 1; }; +namespace ciface +{ +template +class RelativeInputState +{ +public: + void Update() + { + const auto channel = int(ControllerInterface::GetCurrentInputChannel()); + + m_value[channel] = m_delta[channel]; + m_delta[channel] = {}; + } + + T GetValue() const + { + const auto channel = int(ControllerInterface::GetCurrentInputChannel()); + + return m_value[channel]; + } + + void Move(T delta) + { + for (auto& d : m_delta) + d += delta; + } + +private: + std::array m_value; + std::array m_delta; +}; +} // namespace ciface + extern ControllerInterface g_controller_interface; diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h index 2612f59627..c368578949 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -93,6 +93,12 @@ public: virtual bool IsChild(const Input*) const { return false; } }; + class RelativeInput : public Input + { + public: + bool IsDetectable() const override { return false; } + }; + // // Output // diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp index e5e10802cc..ebe2e84286 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp @@ -2,11 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" + #include +#include + #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/DInput/DInput.h" -#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" // (lower would be more sensitive) user can lower sensitivity by setting range // seems decent here ( at 8 ), I don't think anyone would need more sensitive than this @@ -19,6 +22,30 @@ namespace ciface::DInput { +class RelativeMouseAxis final : public Core::Device::RelativeInput +{ +public: + std::string GetName() const override + { + return fmt::format("RelativeMouse {}{}", char('X' + m_index), (m_scale > 0) ? '+' : '-'); + } + + RelativeMouseAxis(u8 index, bool positive, const RelativeMouseState* state) + : m_state(*state), m_index(index), m_scale(positive * 2 - 1) + { + } + + ControlState GetState() const override + { + return ControlState(m_state.GetValue().data[m_index] * m_scale); + } + +private: + const RelativeMouseState& m_state; + const u8 m_index; + const s8 m_scale; +}; + static const struct { const BYTE code; @@ -108,9 +135,17 @@ KeyboardMouse::KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device, AddInput(new Axis(i, ax, (2 == i) ? -1 : -MOUSE_AXIS_SENSITIVITY)); AddInput(new Axis(i, ax, -(2 == i) ? 1 : MOUSE_AXIS_SENSITIVITY)); } + // cursor, with a hax for-loop for (unsigned int i = 0; i < 4; ++i) AddInput(new Cursor(!!(i & 2), (&m_state_in.cursor.x)[i / 2], !!(i & 1))); + + // Raw relative mouse movement. + for (unsigned int i = 0; i != mouse_caps.dwAxes; ++i) + { + AddInput(new RelativeMouseAxis(i, false, &m_state_in.relative_mouse)); + AddInput(new RelativeMouseAxis(i, true, &m_state_in.relative_mouse)); + } } void KeyboardMouse::UpdateCursorInput() @@ -147,6 +182,8 @@ void KeyboardMouse::UpdateInput() { // set axes to zero m_state_in.mouse = {}; + m_state_in.relative_mouse = {}; + // skip this input state m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse); } @@ -162,14 +199,17 @@ void KeyboardMouse::UpdateInput() if (DIERR_INPUTLOST == mo_hr || DIERR_NOTACQUIRED == mo_hr) m_mo_device->Acquire(); - if (SUCCEEDED(kb_hr) && SUCCEEDED(mo_hr)) + if (SUCCEEDED(mo_hr)) { + m_state_in.relative_mouse.Move({tmp_mouse.lX, tmp_mouse.lY, tmp_mouse.lZ}); + m_state_in.relative_mouse.Update(); + // need to smooth out the axes, otherwise it doesn't work for shit for (unsigned int i = 0; i < 3; ++i) ((&m_state_in.mouse.lX)[i] += (&tmp_mouse.lX)[i]) /= 2; // copy over the buttons - memcpy(m_state_in.mouse.rgbButtons, tmp_mouse.rgbButtons, sizeof(m_state_in.mouse.rgbButtons)); + std::copy_n(tmp_mouse.rgbButtons, std::size(tmp_mouse.rgbButtons), m_state_in.mouse.rgbButtons); UpdateCursorInput(); } diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h index c3834df528..ceda59d02c 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h @@ -7,6 +7,7 @@ #include #include "Common/Matrix.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/CoreDevice.h" #include "InputCommon/ControllerInterface/DInput/DInput8.h" @@ -14,14 +15,23 @@ namespace ciface::DInput { void InitKeyboardMouse(IDirectInput8* const idi8, HWND hwnd); +using RelativeMouseState = RelativeInputState>; + class KeyboardMouse : public Core::Device { private: struct State { BYTE keyboard[256]; + + // Old smoothed relative mouse movement. DIMOUSESTATE2 mouse; + + // Normalized mouse cursor position. Common::TVec2 cursor; + + // Raw relative mouse movement. + RelativeMouseState relative_mouse; }; class Key : public Input @@ -53,6 +63,7 @@ private: public: Axis(u8 index, const LONG& axis, LONG range) : m_axis(axis), m_range(range), m_index(index) {} std::string GetName() const override; + bool IsDetectable() const override { return false; } ControlState GetState() const override; private: diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp index a60edc3282..996673becb 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp @@ -197,6 +197,11 @@ KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboar // Mouse Axis, X-/+ and Y-/+ for (int i = 0; i != 4; ++i) AddInput(new Axis(!!(i & 2), !!(i & 1), (i & 2) ? &m_state.axis.y : &m_state.axis.x)); + + // Relative Mouse, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new RelativeMouse(!!(i & 2), !!(i & 1), + (i & 2) ? &m_state.relative_mouse.y : &m_state.relative_mouse.x)); } KeyboardMouse::~KeyboardMouse() @@ -302,6 +307,9 @@ void KeyboardMouse::UpdateInput() XFreeEventData(m_display, &event.xcookie); } + m_state.relative_mouse.x = delta_x; + m_state.relative_mouse.y = delta_y; + // apply axis smoothing m_state.axis.x *= MOUSE_AXIS_SMOOTHING; m_state.axis.x += delta_x; @@ -391,8 +399,20 @@ KeyboardMouse::Axis::Axis(u8 index, bool positive, const float* axis) name = fmt::format("Axis {}{}", static_cast('X' + m_index), (m_positive ? '+' : '-')); } +KeyboardMouse::RelativeMouse::RelativeMouse(u8 index, bool positive, const float* axis) + : m_axis(axis), m_index(index), m_positive(positive) +{ + name = + fmt::format("RelativeMouse {}{}", static_cast('X' + m_index), (m_positive ? '+' : '-')); +} + ControlState KeyboardMouse::Axis::GetState() const { return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); } + +ControlState KeyboardMouse::RelativeMouse::GetState() const +{ + return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); +} } // namespace ciface::XInput2 diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index a9442b6332..71681a3e10 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -30,6 +30,7 @@ private: unsigned int buttons; Common::Vec2 cursor; Common::Vec2 axis; + Common::Vec2 relative_mouse; }; class Key : public Input @@ -91,6 +92,21 @@ private: std::string name; }; + class RelativeMouse : public Input + { + public: + std::string GetName() const override { return name; } + bool IsDetectable() const override { return false; } + RelativeMouse(u8 index, bool positive, const float* axis); + ControlState GetState() const override; + + private: + const float* m_axis; + const u8 m_index; + const bool m_positive; + std::string name; + }; + private: void SelectEventsForDevice(XIEventMask* mask, int deviceid); void UpdateCursor(); diff --git a/Source/Core/VideoCommon/FreeLookCamera.cpp b/Source/Core/VideoCommon/FreeLookCamera.cpp index 5526ff50f0..99c504db4d 100644 --- a/Source/Core/VideoCommon/FreeLookCamera.cpp +++ b/Source/Core/VideoCommon/FreeLookCamera.cpp @@ -102,15 +102,20 @@ public: void Rotate(const Common::Vec3& amt) override { + if (amt.Length() == 0) + return; + m_rotation += amt; using Common::Quaternion; - const auto quat = + m_rotate_quat = (Quaternion::RotateX(m_rotation.x) * Quaternion::RotateY(m_rotation.y)).Normalized(); - Rotate(quat); } - void Rotate(const Common::Quaternion& quat) override { m_rotate_quat = quat; } + void Rotate(const Common::Quaternion& quat) override + { + Rotate(Common::FromQuaternionToEuler(quat)); + } void Reset() override { @@ -153,15 +158,20 @@ public: void Rotate(const Common::Vec3& amt) override { + if (amt.Length() == 0) + return; + m_rotation += amt; using Common::Quaternion; - const auto quat = + m_rotate_quat = (Quaternion::RotateX(m_rotation.x) * Quaternion::RotateY(m_rotation.y)).Normalized(); - Rotate(quat); } - void Rotate(const Common::Quaternion& quat) override { m_rotate_quat = quat; } + void Rotate(const Common::Quaternion& quat) override + { + Rotate(Common::FromQuaternionToEuler(quat)); + } void Reset() override { @@ -246,21 +256,27 @@ void FreeLookCamera::Rotate(const Common::Vec3& amt) m_dirty = true; } +void FreeLookCamera::Rotate(const Common::Quaternion& amt) +{ + m_camera_controller->Rotate(amt); + m_dirty = true; +} + void FreeLookCamera::IncreaseFovX(float fov) { m_fov_x += fov; - m_fov_x = std::clamp(m_fov_x, m_fov_step_size, m_fov_x); + m_fov_x = std::clamp(m_fov_x, m_min_fov_multiplier, m_fov_x); } void FreeLookCamera::IncreaseFovY(float fov) { m_fov_y += fov; - m_fov_y = std::clamp(m_fov_y, m_fov_step_size, m_fov_y); + m_fov_y = std::clamp(m_fov_y, m_min_fov_multiplier, m_fov_y); } float FreeLookCamera::GetFovStepSize() const { - return m_fov_step_size; + return 1.5f; } void FreeLookCamera::Reset() @@ -271,14 +287,15 @@ void FreeLookCamera::Reset() m_dirty = true; } -void FreeLookCamera::ModifySpeed(float multiplier) +void FreeLookCamera::ModifySpeed(float amt) { - m_speed *= multiplier; + m_speed += amt; + m_speed = std::clamp(m_speed, 0.0f, m_speed); } void FreeLookCamera::ResetSpeed() { - m_speed = 1.0f; + m_speed = 60.0f; } float FreeLookCamera::GetSpeed() const diff --git a/Source/Core/VideoCommon/FreeLookCamera.h b/Source/Core/VideoCommon/FreeLookCamera.h index f4a3242905..cbd28fb8ca 100644 --- a/Source/Core/VideoCommon/FreeLookCamera.h +++ b/Source/Core/VideoCommon/FreeLookCamera.h @@ -52,6 +52,7 @@ public: void MoveForward(float amt); void Rotate(const Common::Vec3& amt); + void Rotate(const Common::Quaternion& amt); void IncreaseFovX(float fov); void IncreaseFovY(float fov); @@ -77,8 +78,8 @@ private: std::optional m_current_type; std::unique_ptr m_camera_controller; - float m_fov_step_size = 0.025f; - float m_speed = 1.0f; + float m_min_fov_multiplier = 0.025f; + float m_speed = 60.0f; }; extern FreeLookCamera g_freelook_camera;