From d208c24c0cf88ce6c1ffb4266edb365116a6cbf3 Mon Sep 17 00:00:00 2001
From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
Date: Wed, 14 Aug 2024 22:35:02 +0300
Subject: [PATCH] Implement controller gyroscope in SDL

---
 CMakeLists.txt                     |  1 +
 include/panda_sdl/frontend_sdl.hpp |  2 ++
 include/sdl_gyro.hpp               | 20 ++++++++++++++++++++
 include/services/hid.hpp           |  2 ++
 src/core/services/hid.cpp          |  1 -
 src/panda_sdl/frontend_sdl.cpp     | 29 +++++++++++++++++++++++++++++
 6 files changed, 54 insertions(+), 1 deletion(-)
 create mode 100644 include/sdl_gyro.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b55e2390..2865a3f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -260,6 +260,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
                  include/audio/miniaudio_device.hpp include/ring_buffer.hpp include/bitfield.hpp include/audio/dsp_shared_mem.hpp
                  include/audio/hle_core.hpp include/capstone.hpp include/audio/aac.hpp include/PICA/pica_frag_config.hpp
                  include/PICA/pica_frag_uniforms.hpp include/PICA/shader_gen_types.hpp include/PICA/shader_decompiler.hpp
+                 include/sdl_gyro.hpp
 )
 
 cmrc_add_resource_library(
diff --git a/include/panda_sdl/frontend_sdl.hpp b/include/panda_sdl/frontend_sdl.hpp
index 07038962..cbd0b88e 100644
--- a/include/panda_sdl/frontend_sdl.hpp
+++ b/include/panda_sdl/frontend_sdl.hpp
@@ -37,4 +37,6 @@ class FrontendSDL {
 	// And so the user can still use the keyboard to control the analog
 	bool keyboardAnalogX = false;
 	bool keyboardAnalogY = false;
+
+	void setupControllerSensors(SDL_GameController* controller);
 };
\ No newline at end of file
diff --git a/include/sdl_gyro.hpp b/include/sdl_gyro.hpp
new file mode 100644
index 00000000..17faab94
--- /dev/null
+++ b/include/sdl_gyro.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <glm/glm.hpp>
+#include <numbers>
+
+#include "services/hid.hpp"
+
+namespace Gyro::SDL {
+	// Convert the rotation data we get from SDL sensor events to rotation data we can feed right to HID
+	// Returns [pitch, roll, yaw]
+	static glm::vec3 convertRotation(glm::vec3 rotation) { 
+		// Flip axes
+		glm::vec3 ret = -rotation;
+		// Convert from radians/s to deg/s and scale by the gyroscope coefficient from the HID service
+		ret *= 180.f / std::numbers::pi;
+		ret *= HIDService::gyroscopeCoeff;
+
+		return ret;
+	}
+}  // namespace Gyro::SDL
\ No newline at end of file
diff --git a/include/services/hid.hpp b/include/services/hid.hpp
index 86a55479..bce2cc1b 100644
--- a/include/services/hid.hpp
+++ b/include/services/hid.hpp
@@ -88,6 +88,8 @@ class HIDService {
 	}
 
   public:
+	static constexpr float gyroscopeCoeff = 14.375f;  // Same as retail 3DS
+
 	HIDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
 	void reset();
 	void handleSyncRequest(u32 messagePointer);
diff --git a/src/core/services/hid.cpp b/src/core/services/hid.cpp
index ef6cbb41..aa13096c 100644
--- a/src/core/services/hid.cpp
+++ b/src/core/services/hid.cpp
@@ -103,7 +103,6 @@ void HIDService::getGyroscopeLowCalibrateParam(u32 messagePointer) {
 void HIDService::getGyroscopeCoefficient(u32 messagePointer) {
 	log("HID::GetGyroscopeLowRawToDpsCoefficient\n");
 
-	constexpr float gyroscopeCoeff = 14.375f; // Same as retail 3DS
 	mem.write32(messagePointer, IPC::responseHeader(0x15, 2, 0));
 	mem.write32(messagePointer + 4, Result::Success);
 	mem.write32(messagePointer + 8, Helpers::bit_cast<u32, float>(gyroscopeCoeff));
diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp
index 77b1f55f..703fb1c7 100644
--- a/src/panda_sdl/frontend_sdl.cpp
+++ b/src/panda_sdl/frontend_sdl.cpp
@@ -2,6 +2,8 @@
 
 #include <glad/gl.h>
 
+#include "sdl_gyro.hpp"
+
 FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMappings()) {
 	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
 		Helpers::panic("Failed to initialize SDL2");
@@ -20,6 +22,8 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp
 			SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController);
 			gameControllerID = SDL_JoystickInstanceID(stick);
 		}
+
+		setupControllerSensors(gameController);
 	}
 
 	const EmulatorConfig& config = emu.getConfig();
@@ -200,6 +204,8 @@ void FrontendSDL::run() {
 					if (gameController == nullptr) {
 						gameController = SDL_GameControllerOpen(event.cdevice.which);
 						gameControllerID = event.cdevice.which;
+
+						setupControllerSensors(gameController);
 					}
 					break;
 
@@ -280,6 +286,21 @@ void FrontendSDL::run() {
 					}
 					break;
 				}
+									
+				case SDL_CONTROLLERSENSORUPDATE: {
+					if (event.csensor.sensor == SDL_SENSOR_GYRO) {
+						glm::vec3 rotation = Gyro::SDL::convertRotation({
+							event.csensor.data[0],
+							event.csensor.data[1],
+							event.csensor.data[2],
+						});
+
+						hid.setPitch(s16(rotation.x));
+						hid.setRoll(s16(rotation.y));
+						hid.setYaw(s16(rotation.z));
+					}
+					break;
+				}
 
 				case SDL_DROPFILE: {
 					char* droppedDir = event.drop.file;
@@ -342,3 +363,11 @@ void FrontendSDL::run() {
 		SDL_GL_SwapWindow(window);
 	}
 }
+
+void FrontendSDL::setupControllerSensors(SDL_GameController* controller) {
+	bool haveGyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
+
+	if (haveGyro) {
+		SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
+	}
+}
\ No newline at end of file