From 551751df3c931a6913a545e219479a1610aab52a Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:55:41 +0200 Subject: [PATCH] Emulate motion controls with a mouse (#3122) * Rework framework to allow for more types of mouse-to-something emulation and hook up gyro to it * Remove the unnecessary null check now that deltatime is handled differently * Fix toggle key * Basic gyro emulation working for two out of the three dimensions * clang * Added bindable key to hold for switching from looking to the sides to rolling * documentation --- src/core/libraries/pad/pad.cpp | 27 ++++++++--------- src/input/input_handler.cpp | 4 +++ src/input/input_handler.h | 2 ++ src/input/input_mouse.cpp | 55 ++++++++++++++++++++++++++++------ src/input/input_mouse.h | 14 +++++++-- src/qt_gui/kbm_help_dialog.h | 5 +++- src/sdl_window.cpp | 11 +++++-- 7 files changed, 88 insertions(+), 30 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 42582783b..59964fa58 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -447,21 +447,18 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { // Only do this on handle 1 for now if (engine && handle == 1) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); - if (gyro_poll_rate != 0.0f) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = std::chrono::duration_cast( - now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, - deltaTime, lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } + auto now = std::chrono::steady_clock::now(); + float deltaTime = + std::chrono::duration_cast(now - controller->GetLastUpdate()) + .count() / + 1000000.0f; + controller->SetLastUpdate(now); + Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); + Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; + GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, + lastOrientation, outputOrientation); + pData->orientation = outputOrientation; + controller->SetLastOrientation(outputOrientation); } pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index dc969fda9..cc6cf29d4 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -66,6 +66,7 @@ auto output_array = std::array{ ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerOutput(KEY_TOGGLE), + ControllerOutput(MOUSE_GYRO_ROLL_MODE), // Button mappings ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle @@ -534,6 +535,9 @@ void ControllerOutput::FinalizeUpdate() { // to do it, and it would be inconvenient to force it here, when AddUpdate does the job just // fine, and a toggle doesn't have to checked against every input that's bound to it, it's // enough that one is pressed + case MOUSE_GYRO_ROLL_MODE: + SetMouseGyroRollMode(new_button_state); + break; default: // is a normal key (hopefully) controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 797a8eff8..189970c12 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -35,6 +35,7 @@ #define BACK_BUTTON 0x00040000 #define KEY_TOGGLE 0x00200000 +#define MOUSE_GYRO_ROLL_MODE 0x00400000 #define SDL_UNMAPPED UINT32_MAX - 1 @@ -114,6 +115,7 @@ const std::map string_to_cbutton_map = { {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, + {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, }; const std::map string_to_axis_map = { diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 5eb0aab3e..3c718dbd5 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -3,6 +3,7 @@ #include +#include "common/assert.h" #include "common/types.h" #include "input/controller.h" #include "input_mouse.h" @@ -13,12 +14,19 @@ namespace Input { int mouse_joystick_binding = 0; float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250; +bool mouse_gyro_roll_mode = false; Uint32 mouse_polling_id = 0; -bool mouse_enabled = false; +MouseMode mouse_mode = MouseMode::Off; -// We had to go through 3 files of indirection just to update a flag -void ToggleMouseEnabled() { - mouse_enabled = !mouse_enabled; +// Switches mouse to a set mode or turns mouse emulation off if it was already in that mode. +// Returns whether the mode is turned on. +bool ToggleMouseModeTo(MouseMode m) { + if (mouse_mode == m) { + mouse_mode = MouseMode::Off; + } else { + mouse_mode = m; + } + return mouse_mode == m; } void SetMouseToJoystick(int joystick) { @@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) { mouse_speed_offset = mso; } -Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { - auto* controller = (GameController*)param; - if (!mouse_enabled) - return interval; +void SetMouseGyroRollMode(bool mode) { + mouse_gyro_roll_mode = mode; +} + +void EmulateJoystick(GameController* controller, u32 interval) { Axis axis_x, axis_y; switch (mouse_joystick_binding) { @@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { axis_y = Axis::RightY; break; default: - return interval; // no update needed + return; // no update needed } float d_x = 0, d_y = 0; @@ -67,7 +76,35 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0)); controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0)); } +} +constexpr float constant_down_accel[3] = {0.0f, 10.0f, 0.0f}; +void EmulateGyro(GameController* controller, u32 interval) { + // LOG_INFO(Input, "todo gyro"); + float d_x = 0, d_y = 0; + SDL_GetRelativeMouseState(&d_x, &d_y); + controller->Acceleration(1, constant_down_accel); + float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f}; + if (mouse_gyro_roll_mode) { + gyro_from_mouse[1] = 0.0f; + gyro_from_mouse[2] = -d_x / 100; + } + controller->Gyro(1, gyro_from_mouse); +} + +Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { + auto* controller = (GameController*)param; + switch (mouse_mode) { + case MouseMode::Joystick: + EmulateJoystick(controller, interval); + break; + case MouseMode::Gyro: + EmulateGyro(controller, interval); + break; + + default: + break; + } return interval; } diff --git a/src/input/input_mouse.h b/src/input/input_mouse.h index da18ee04e..a56ef2d8f 100644 --- a/src/input/input_mouse.h +++ b/src/input/input_mouse.h @@ -8,11 +8,21 @@ namespace Input { -void ToggleMouseEnabled(); +enum MouseMode { + Off = 0, + Joystick, + Gyro, +}; + +bool ToggleMouseModeTo(MouseMode m); void SetMouseToJoystick(int joystick); void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset); +void SetMouseGyroRollMode(bool mode); -// Polls the mouse for changes, and simulates joystick movement from it. +void EmulateJoystick(GameController* controller, u32 interval); +void EmulateGyro(GameController* controller, u32 interval); + +// Polls the mouse for changes Uint32 MousePolling(void* param, Uint32 id, Uint32 interval); } // namespace Input diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 1004bb04e..b1fe05417 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -60,7 +60,8 @@ A: -F12: Triggers Renderdoc capture -Ctrl F10: Open the debug menu -F9: Pauses emultor, if the debug menu is open -F8: Reparses the config file while in-game --F7: Toggles mouse capture and mouse input +-F7: Toggles mouse-to-joystick emulation +-F6: Toggles mouse-to-gyro emulation Q: How do I change between mouse and controller joystick input, and why is it even required? A: You can switch between them with F7, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back to the center), even if other input devices would update it. @@ -175,6 +176,8 @@ You can find these here, with detailed comments, examples and suggestions for mo Values go from 1 to 127 (no deadzone to max deadzone), first is the inner, second is the outer deadzone If you only want inner or outer deadzone, set the other to 1 or 127, respectively Devices: leftjoystick, rightjoystick, l2, r2 +'mouse_gyro_roll_mode': + Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active. )"; } }; \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index e369240c6..735f14639 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -474,11 +474,16 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); return; } - // Toggle mouse capture and movement input + // Toggle mouse capture and joystick input emulation else if (input_id == SDLK_F7) { - Input::ToggleMouseEnabled(); SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), - !SDL_GetWindowRelativeMouseMode(this->GetSDLWindow())); + Input::ToggleMouseModeTo(Input::MouseMode::Joystick)); + return; + } + // Toggle mouse capture and gyro input emulation + else if (input_id == SDLK_F6) { + SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), + Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); return; } // Toggle fullscreen