Browse Source

Generalized the sensor coordinate transform for wraparound gamepads

Sam Lantinga 1 year ago
parent
commit
610c31c7b7
3 changed files with 66 additions and 35 deletions
  1. 14 28
      src/joystick/SDL_gamepad.c
  2. 51 7
      src/joystick/SDL_joystick.c
  3. 1 0
      src/joystick/SDL_sysjoystick.h

+ 14 - 28
src/joystick/SDL_gamepad.c

@@ -370,33 +370,19 @@ static void RecenterGamepad(SDL_Gamepad *gamepad)
     }
 }
 
-/* SDL defines sensor orientation for phones relative to the natural
-   orientation, and for gamepads relative to being held in front of you.
-   When a phone is being used as a gamepad, its orientation changes,
-   so adjust sensor axes to match.
+/* SDL defines sensor orientation relative to the device natural
+   orientation, so when it's changed orientation to be used as a
+   gamepad, change the sensor orientation to match.
  */
-static void AdjustSensorOrientation(float *src, float *dst)
-{
-    if (SDL_GetDisplayNaturalOrientation(SDL_GetPrimaryDisplay()) == SDL_ORIENTATION_LANDSCAPE) {
-        /* When a device in landscape orientation is laid flat, the axes change
-           orientation as follows:
-            -X to +X becomes -X to +X
-            -Y to +Y becomes +Z to -Z
-            -Z to +Z becomes -Y to +Y
-        */
-        dst[0] = src[0];
-        dst[1] = src[2];
-        dst[2] = -src[1];
-    } else {
-        /* When a device in portrait orientation is rotated left and laid flat,
-           the axes change orientation as follows:
-            -X to +X becomes +Z to -Z
-            -Y to +Y becomes +X to -X
-            -Z to +Z becomes -Y to +Y
-        */
-        dst[0] = -src[1];
-        dst[1] = src[2];
-        dst[2] = -src[0];
+static void AdjustSensorOrientation(SDL_Joystick *joystick, float *src, float *dst)
+{
+    unsigned int i, j;
+
+    for (i = 0; i < 3; ++i) {
+        dst[i] = 0.0f;
+        for (j = 0; j < 3; ++j) {
+            dst[i] += joystick->sensor_transform[i][j] * src[j];
+        }
     }
 }
 
@@ -480,12 +466,12 @@ static int SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)
         for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
             if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == event->sensor.which) {
                 float data[3];
-                AdjustSensorOrientation(event->sensor.data, data);
+                AdjustSensorOrientation(gamepad->joystick, event->sensor.data, data);
                 SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, event->sensor.sensor_timestamp, data, SDL_arraysize(data));
             }
             if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == event->sensor.which) {
                 float data[3];
-                AdjustSensorOrientation(event->sensor.data, data);
+                AdjustSensorOrientation(gamepad->joystick, event->sensor.data, data);
                 SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_GYRO, event->sensor.sensor_timestamp, data, SDL_arraysize(data));
             }
         }

+ 51 - 7
src/joystick/SDL_joystick.c

@@ -562,7 +562,7 @@ static SDL_bool IsROGAlly(SDL_Joystick *joystick)
     return SDL_FALSE;
 }
 
-static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick)
+static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick, SDL_bool *invert_sensors)
 {
     static Uint32 wraparound_gamepads[] = {
         MAKE_VIDPID(0x1532, 0x0709),    /* Razer Junglecat (L) */
@@ -578,6 +578,8 @@ static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick)
     int i;
     int hint;
 
+    *invert_sensors = SDL_FALSE;
+
     /* The SDL controller sensor API is only available for gamepads (at the moment) */
     if (!joystick->is_gamepad) {
         return SDL_FALSE;
@@ -607,17 +609,23 @@ static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick)
     }
 
     /* See if this is another known wraparound gamepad */
-    if (IsBackboneOne(joystick) || IsROGAlly(joystick)) {
+    if (IsBackboneOne(joystick)) {
+        return SDL_TRUE;
+    }
+    if (IsROGAlly(joystick)) {
+        /* I'm not sure if this is a Windows thing, or a quirk for ROG Ally,
+         * but we need to invert the sensor data on all axes.
+         */
+        *invert_sensors = SDL_TRUE;
         return SDL_TRUE;
     }
-
     return SDL_FALSE;
 }
 
-static void AttemptSensorFusion(SDL_Joystick *joystick)
+static void AttemptSensorFusion(SDL_Joystick *joystick, SDL_bool invert_sensors)
 {
     SDL_SensorID *sensors;
-    int i;
+    unsigned int i, j;
 
     if (SDL_InitSubSystem(SDL_INIT_SENSOR) < 0) {
         return;
@@ -646,6 +654,41 @@ static void AttemptSensorFusion(SDL_Joystick *joystick)
         SDL_free(sensors);
     }
     SDL_QuitSubSystem(SDL_INIT_SENSOR);
+
+    /* SDL defines sensor orientation for phones relative to the natural
+       orientation, and for gamepads relative to being held in front of you.
+       When a phone is being used as a gamepad, its orientation changes,
+       so adjust sensor axes to match.
+     */
+    if (SDL_GetDisplayNaturalOrientation(SDL_GetPrimaryDisplay()) == SDL_ORIENTATION_LANDSCAPE) {
+        /* When a device in landscape orientation is laid flat, the axes change
+           orientation as follows:
+            -X to +X becomes -X to +X
+            -Y to +Y becomes +Z to -Z
+            -Z to +Z becomes -Y to +Y
+        */
+        joystick->sensor_transform[0][0] = 1.0f;
+        joystick->sensor_transform[1][2] = 1.0f;
+        joystick->sensor_transform[2][1] = -1.0f;
+    } else {
+        /* When a device in portrait orientation is rotated left and laid flat,
+           the axes change orientation as follows:
+            -X to +X becomes +Z to -Z
+            -Y to +Y becomes +X to -X
+            -Z to +Z becomes -Y to +Y
+        */
+        joystick->sensor_transform[0][1] = -1.0f;
+        joystick->sensor_transform[1][2] = 1.0f;
+        joystick->sensor_transform[2][0] = -1.0f;
+    }
+
+    if (invert_sensors) {
+        for (i = 0; i < SDL_arraysize(joystick->sensor_transform); ++i) {
+            for (j = 0; j < SDL_arraysize(joystick->sensor_transform[i]); ++j) {
+                joystick->sensor_transform[i][j] *= -1.0f;
+            }
+        }
+    }
 }
 
 static void CleanupSensorFusion(SDL_Joystick *joystick)
@@ -690,6 +733,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     const char *joystickname = NULL;
     const char *joystickpath = NULL;
     SDL_JoystickPowerLevel initial_power_level;
+    SDL_bool invert_sensors = SDL_FALSE;
 
     SDL_LockJoysticks();
 
@@ -776,8 +820,8 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     joystick->is_gamepad = SDL_IsGamepad(instance_id);
 
     /* Use system gyro and accelerometer if the gamepad doesn't have built-in sensors */
-    if (ShouldAttemptSensorFusion(joystick)) {
-        AttemptSensorFusion(joystick);
+    if (ShouldAttemptSensorFusion(joystick, &invert_sensors)) {
+        AttemptSensorFusion(joystick, invert_sensors);
     }
 
     /* Add joystick to list */

+ 1 - 0
src/joystick/SDL_sysjoystick.h

@@ -117,6 +117,7 @@ struct SDL_Joystick
     SDL_Sensor *accel _guarded;
     SDL_SensorID gyro_sensor _guarded;
     SDL_Sensor *gyro _guarded;
+    float sensor_transform[3][3] _guarded;
 
     struct SDL_JoystickDriver *driver _guarded;