From 61df6a74d52656f46b8585b98db36794bd6076c7 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Tue, 28 Mar 2023 23:06:34 -0300 Subject: [PATCH] Gyro aiming for Switch controllers on SDL < 2.0.14 dkms-hid-nintendo can expose the IMU sensors of a Switch controller as a "second joystick", which makes possible to use gyro aiming on a system without SDL 2.0.14 available (the minimum required to read controller sensors like gyro and accelerometer). This commit makes both "sensor" and "joystick reading" to coexist. "Sensor" is still the preferred method when available. --- src/client/input/sdl.c | 127 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 9 deletions(-) diff --git a/src/client/input/sdl.c b/src/client/input/sdl.c index 0ff9fa8a..d3effd64 100644 --- a/src/client/input/sdl.c +++ b/src/client/input/sdl.c @@ -167,12 +167,25 @@ static qboolean gyro_active = false; // Gyro calibration static float gyro_accum[3]; -static unsigned int num_samples; - static cvar_t *gyro_calibration_x; static cvar_t *gyro_calibration_y; static cvar_t *gyro_calibration_z; +#if SDL_VERSION_ATLEAST(2, 0, 14) // support for controller sensors (gyro, accelerometer) + +static unsigned int num_samples; +#define NATIVE_SDL_GYRO 1 // uses SDL_CONTROLLERSENSORUPDATE to read gyro + +#else // for SDL < 2.0.14, gyro can be read as a "secondary joystick" exposed by dkms-hid-nintendo + +static unsigned int num_samples[3]; +static SDL_Joystick *imu_joystick = NULL; // gyro "joystick" +#define IMU_JOY_AXIS_GYRO_ROLL 3 +#define IMU_JOY_AXIS_GYRO_PITCH 4 +#define IMU_JOY_AXIS_GYRO_YAW 5 + +#endif + // To ignore SDL_JOYDEVICEADDED at game init. Allows for hot plugging of game controller afterwards. static qboolean first_init = true; @@ -766,7 +779,7 @@ IN_Update(void) break; } -#if SDL_VERSION_ATLEAST(2, 0, 16) // support for controller sensors (gyro, accelerometer) +#ifdef NATIVE_SDL_GYRO // controller sensors' reading supported (gyro, accelerometer) case SDL_CONTROLLERSENSORUPDATE: if (event.csensor.sensor != SDL_SENSOR_GYRO) { @@ -781,9 +794,39 @@ IN_Update(void) break; } +#else // gyro read as "secondary joystick" + case SDL_JOYAXISMOTION: + if ( !imu_joystick || event.cdevice.which != SDL_JoystickInstanceID(imu_joystick) ) + { + break; // controller axes handled by SDL_CONTROLLERAXISMOTION + } + + int axis_value = event.caxis.value; + if (countdown_reason == REASON_GYROCALIBRATION && updates_countdown) + { + switch (event.caxis.axis) + { + case IMU_JOY_AXIS_GYRO_PITCH: + gyro_accum[0] += axis_value; + num_samples[0]++; + break; + case IMU_JOY_AXIS_GYRO_YAW: + gyro_accum[1] += axis_value; + num_samples[1]++; + break; + case IMU_JOY_AXIS_GYRO_ROLL: + gyro_accum[2] += axis_value; + num_samples[2]++; + } + break; + } + +#endif // NATIVE_SDL_GYRO + if (gyro_active && gyro_mode->value && !cl_paused->value && cls.key_dest == key_game) { +#ifdef NATIVE_SDL_GYRO if (!gyro_turning_axis->value) { gyro_yaw = event.csensor.data[1] - gyro_calibration_y->value; // yaw @@ -793,13 +836,31 @@ IN_Update(void) gyro_yaw = -(event.csensor.data[2] - gyro_calibration_z->value); // roll } gyro_pitch = event.csensor.data[0] - gyro_calibration_x->value; +#else // old "joystick" gyro + switch (event.caxis.axis) // inside "case SDL_JOYAXISMOTION" here + { + case IMU_JOY_AXIS_GYRO_PITCH: + gyro_pitch = -(axis_value - gyro_calibration_x->value); + break; + case IMU_JOY_AXIS_GYRO_YAW: + if (!gyro_turning_axis->value) + { + gyro_yaw = axis_value - gyro_calibration_y->value; + } + break; + case IMU_JOY_AXIS_GYRO_ROLL: + if (gyro_turning_axis->value) + { + gyro_yaw = axis_value - gyro_calibration_z->value; + } + } +#endif // NATIVE_SDL_GYRO } else { gyro_yaw = gyro_pitch = 0; } break; -#endif // SDL_VERSION_ATLEAST(2, 0, 16) case SDL_CONTROLLERDEVICEREMOVED: if (!controller) @@ -889,10 +950,23 @@ IN_Update(void) case REASON_GYROCALIBRATION: // finish and save calibration { +#ifdef NATIVE_SDL_GYRO const float inverseSamples = 1.f / num_samples; Cvar_SetValue("gyro_calibration_x", gyro_accum[0] * inverseSamples); Cvar_SetValue("gyro_calibration_y", gyro_accum[1] * inverseSamples); Cvar_SetValue("gyro_calibration_z", gyro_accum[2] * inverseSamples); +#else + if (!num_samples[0] || !num_samples[1] || !num_samples[2]) + { + Com_Printf("Calibration failed, please retry inside a level after having moved your controller a little.\n"); + } + else + { + Cvar_SetValue("gyro_calibration_x", gyro_accum[0] / num_samples[0]); + Cvar_SetValue("gyro_calibration_y", gyro_accum[1] / num_samples[1]); + Cvar_SetValue("gyro_calibration_z", gyro_accum[2] / num_samples[2]); + } +#endif Com_Printf("Calibration results:\n X=%f Y=%f Z=%f\n", gyro_calibration_x->value, gyro_calibration_y->value, gyro_calibration_z->value); CalibrationFinishedCallback(); @@ -1269,7 +1343,11 @@ IN_Move(usercmd_t *cmd) // // For movement this is not needed, as those are absolute values independent of framerate float joyViewFactor = cls.rframetime/0.01666f; +#ifdef NATIVE_SDL_GYRO float gyroViewFactor = (1.0f / M_PI) * joyViewFactor; +#else + float gyroViewFactor = (1.0f / 2560.0f) * joyViewFactor; // normalized for Switch gyro +#endif if (joystick_yaw) { @@ -1915,10 +1993,14 @@ Controller_Rumble(const char *name, vec3_t source, qboolean from_player, void StartCalibration(void) { +#ifdef NATIVE_SDL_GYRO + num_samples = 0; +#else + num_samples[0] = num_samples[1] = num_samples[2] = 0; +#endif gyro_accum[0] = 0.0; gyro_accum[1] = 0.0; gyro_accum[2] = 0.0; - num_samples = 0; updates_countdown = 300; countdown_reason = REASON_GYROCALIBRATION; } @@ -2033,9 +2115,26 @@ IN_Controller_Init(qboolean notify_user) // Ugly hack to detect IMU-only devices - works for Switch controllers at least if (name_len > 4 && !strncmp(joystick_name + name_len - 4, " IMU", 4)) { - Com_Printf ("Skipping IMU device.\n"); SDL_JoystickClose(joystick); joystick = NULL; +#ifdef NATIVE_SDL_GYRO + Com_Printf ("Skipping IMU device.\n"); +#else // if it's not a Left JoyCon, use it as Gyro + Com_Printf ("IMU device found.\n"); + if ( !imu_joystick && name_len > 16 && strncmp(joystick_name + name_len - 16, "Left Joy-Con IMU", 16) != 0 ) + { + imu_joystick = SDL_JoystickOpen(i); + if (imu_joystick) + { + show_gyro = true; + Com_Printf ("Using this device as Gyro sensor.\n"); + } + else + { + Com_Printf ("Couldn't open IMU: %s.\n", SDL_GetError()); + } + } +#endif continue; } @@ -2059,7 +2158,7 @@ IN_Controller_Init(qboolean notify_user) SDL_JoystickClose(joystick); joystick = NULL; - if (is_controller) + if (is_controller && !controller) { controller = SDL_GameControllerOpen(i); if (!controller) @@ -2071,7 +2170,7 @@ IN_Controller_Init(qboolean notify_user) show_gamepad = true; Com_Printf("Enabled as Game Controller, settings:\n%s\n", SDL_GameControllerMapping(controller)); -#if SDL_VERSION_ATLEAST(2, 0, 16) // support for controller sensors +#ifdef NATIVE_SDL_GYRO if ( SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE) ) @@ -2090,7 +2189,7 @@ IN_Controller_Init(qboolean notify_user) SDL_GameControllerSetLED(controller, 0, 80, 0); // green light } -#endif // SDL_VERSION_ATLEAST(2, 0, 16) +#endif // NATIVE_SDL_GYRO joystick_haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(controller)); @@ -2124,7 +2223,9 @@ IN_Controller_Init(qboolean notify_user) Com_Printf("Controller doesn't support rumble.\n"); } +#ifdef NATIVE_SDL_GYRO // "native" exits when finding a single working controller break; +#endif } } } @@ -2236,6 +2337,14 @@ IN_Controller_Shutdown(qboolean notify_user) show_gamepad = show_gyro = show_haptic = false; joystick_left_x = joystick_left_y = joystick_right_x = joystick_right_y = 0; gyro_yaw = gyro_pitch = 0; + +#ifndef NATIVE_SDL_GYRO + if (imu_joystick) + { + SDL_JoystickClose(imu_joystick); + imu_joystick = NULL; + } +#endif } /*