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
This commit is contained in:
kalaposfos13 2025-06-20 12:55:41 +02:00 committed by GitHub
parent be12305f65
commit 551751df3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 30 deletions

View file

@ -447,22 +447,19 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
// Only do this on handle 1 for now // Only do this on handle 1 for now
if (engine && handle == 1) { if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>( float deltaTime =
now - controller->GetLastUpdate()) std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
.count() / .count() /
1000000.0f; 1000000.0f;
controller->SetLastUpdate(now); controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
deltaTime, lastOrientation, outputOrientation); lastOrientation, outputOrientation);
pData->orientation = outputOrientation; pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation); controller->SetLastOrientation(outputOrientation);
} }
}
pData->touchData.touchNum = pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);

View file

@ -66,6 +66,7 @@ auto output_array = std::array{
ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE), ControllerOutput(KEY_TOGGLE),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// Button mappings // Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle 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 // 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 // fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed // enough that one is pressed
case MOUSE_GYRO_ROLL_MODE:
SetMouseGyroRollMode(new_button_state);
break;
default: // is a normal key (hopefully) default: // is a normal key (hopefully)
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;

View file

@ -35,6 +35,7 @@
#define BACK_BUTTON 0x00040000 #define BACK_BUTTON 0x00040000
#define KEY_TOGGLE 0x00200000 #define KEY_TOGGLE 0x00200000
#define MOUSE_GYRO_ROLL_MODE 0x00400000
#define SDL_UNMAPPED UINT32_MAX - 1 #define SDL_UNMAPPED UINT32_MAX - 1
@ -114,6 +115,7 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
{"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE},
}; };
const std::map<std::string, AxisMapping> string_to_axis_map = { const std::map<std::string, AxisMapping> string_to_axis_map = {

View file

@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include "common/assert.h"
#include "common/types.h" #include "common/types.h"
#include "input/controller.h" #include "input/controller.h"
#include "input_mouse.h" #include "input_mouse.h"
@ -13,12 +14,19 @@ namespace Input {
int mouse_joystick_binding = 0; int mouse_joystick_binding = 0;
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250; 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; 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 // Switches mouse to a set mode or turns mouse emulation off if it was already in that mode.
void ToggleMouseEnabled() { // Returns whether the mode is turned on.
mouse_enabled = !mouse_enabled; bool ToggleMouseModeTo(MouseMode m) {
if (mouse_mode == m) {
mouse_mode = MouseMode::Off;
} else {
mouse_mode = m;
}
return mouse_mode == m;
} }
void SetMouseToJoystick(int joystick) { void SetMouseToJoystick(int joystick) {
@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) {
mouse_speed_offset = mso; mouse_speed_offset = mso;
} }
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { void SetMouseGyroRollMode(bool mode) {
auto* controller = (GameController*)param; mouse_gyro_roll_mode = mode;
if (!mouse_enabled) }
return interval;
void EmulateJoystick(GameController* controller, u32 interval) {
Axis axis_x, axis_y; Axis axis_x, axis_y;
switch (mouse_joystick_binding) { switch (mouse_joystick_binding) {
@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
axis_y = Axis::RightY; axis_y = Axis::RightY;
break; break;
default: default:
return interval; // no update needed return; // no update needed
} }
float d_x = 0, d_y = 0; 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_x, GetAxis(-0x80, 0x7f, 0));
controller->Axis(0, axis_y, 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; return interval;
} }

View file

@ -8,11 +8,21 @@
namespace Input { namespace Input {
void ToggleMouseEnabled(); enum MouseMode {
Off = 0,
Joystick,
Gyro,
};
bool ToggleMouseModeTo(MouseMode m);
void SetMouseToJoystick(int joystick); void SetMouseToJoystick(int joystick);
void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset); 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); Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
} // namespace Input } // namespace Input

View file

@ -60,7 +60,8 @@ A: -F12: Triggers Renderdoc capture
-Ctrl F10: Open the debug menu -Ctrl F10: Open the debug menu
-F9: Pauses emultor, if the debug menu is open -F9: Pauses emultor, if the debug menu is open
-F8: Reparses the config file while in-game -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? 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. 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 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 If you only want inner or outer deadzone, set the other to 1 or 127, respectively
Devices: leftjoystick, rightjoystick, l2, r2 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.
)"; )";
} }
}; };

View file

@ -474,11 +474,16 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
return; return;
} }
// Toggle mouse capture and movement input // Toggle mouse capture and joystick input emulation
else if (input_id == SDLK_F7) { else if (input_id == SDLK_F7) {
Input::ToggleMouseEnabled();
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), 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; return;
} }
// Toggle fullscreen // Toggle fullscreen