diff --git a/engine/client/in_gyro.c b/engine/client/in_gyro.c new file mode 100644 index 0000000000..b5826b8c5a --- /dev/null +++ b/engine/client/in_gyro.c @@ -0,0 +1,120 @@ +/* +in_gyro.c - System gyroscope input code +Copyright (C) 2026 Xash3D FWGS contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "input.h" +#include "client.h" + +static CVAR_DEFINE_AUTO( gyro_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with built-in device gyroscope" ); +static CVAR_DEFINE_AUTO( gyro_available, "0", FCVAR_READ_ONLY, "tells whether system gyroscope hardware is available or not" ); +static CVAR_DEFINE_AUTO( gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for looking up and down" ); +static CVAR_DEFINE_AUTO( gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for turning left and right" ); +static CVAR_DEFINE_AUTO( gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity when tilting the device sideways" ); +static CVAR_DEFINE_AUTO( gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope pitch axis deadzone (deg/s)" ); +static CVAR_DEFINE_AUTO( gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope yaw axis deadzone (deg/s)" ); +static CVAR_DEFINE_AUTO( gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope roll axis deadzone (deg/s)" ); + +// stores the latest instantaneous rotation rates from built-in gyroscope +static vec3_t gyro_speed; + +/* +============== +IN_GyroInit + +============== +*/ +void IN_GyroInit( void ) +{ + Cvar_RegisterVariable( &gyro_enable ); + Cvar_RegisterVariable( &gyro_available ); + Cvar_RegisterVariable( &gyro_pitch ); + Cvar_RegisterVariable( &gyro_yaw ); + Cvar_RegisterVariable( &gyro_roll ); + Cvar_RegisterVariable( &gyro_pitch_deadzone ); + Cvar_RegisterVariable( &gyro_yaw_deadzone ); + Cvar_RegisterVariable( &gyro_roll_deadzone ); +} + +/* +============== +IN_GyroCheckAvailability + +One-time late check called after startup and configs +============== +*/ +void IN_GyroCheckAvailability( void ) +{ +#if XASH_SDL + if( gyro_available.value ) + return; + + if( SDLash_GyroIsAvailable() ) + { + Cvar_FullSet( "gyro_available", "1", FCVAR_READ_ONLY ); + } +#endif +} + +/* +============= +IN_GyroEvent + +System gyroscope events from platform +============= +*/ +void IN_GyroEvent( vec3_t data ) +{ + VectorCopy( data, gyro_speed ); +} + +/* +============= +IN_GyroFinalizeMove + +Apply gyro movement to view angles +============= +*/ +void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) +{ + platform_orientation_t orient; + float orient_scale = 1.0f; + + if( !gyro_enable.value || !gyro_available.value ) + return; + + orient = Platform_GetDisplayOrientation(); + if( orient == ORIENTATION_LANDSCAPE_FLIPPED ) + orient_scale = -1.0f; + + // In Landscape mode axes are swapped relative to natural (Portrait) orientation + // Y axis rotation becomes Pitch (up/down) + // X axis rotation becomes Yaw (left/right) + float pitch_speed = -orient_scale * gyro_speed[1] * ( 180.0f / M_PI ); + float yaw_speed = -orient_scale * gyro_speed[0] * ( 180.0f / M_PI ); + float roll_speed = orient_scale * gyro_speed[2] * ( 180.0f / M_PI ); + + if( fabs( pitch_speed ) < gyro_pitch_deadzone.value ) + pitch_speed = 0.0f; + if( fabs( yaw_speed ) < gyro_yaw_deadzone.value ) + yaw_speed = 0.0f; + if( fabs( roll_speed ) < gyro_roll_deadzone.value ) + roll_speed = 0.0f; + + *dpitch -= gyro_pitch.value * pitch_speed * host.realframetime; + *dyaw += gyro_yaw.value * yaw_speed * host.realframetime; + *dyaw += gyro_roll.value * roll_speed * host.realframetime; + + VectorClear( gyro_speed ); +} diff --git a/engine/client/input.c b/engine/client/input.c index 3f5d30ecb8..96dcad462f 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -445,6 +445,8 @@ void IN_Init( void ) { IN_StartupMouse( ); + IN_GyroInit(); + Joy_Init(); // common joystick support init Touch_Init(); @@ -560,6 +562,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y #endif } + IN_GyroFinalizeMove( forward, side, pitch, yaw ); Joy_FinalizeMove( forward, side, pitch, yaw ); Touch_GetMove( forward, side, pitch, yaw ); diff --git a/engine/client/input.h b/engine/client/input.h index ef0f726af7..2801198a12 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -39,6 +39,10 @@ void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); void IN_MouseRestorePos( void ); void IN_ToggleClientMouse( int newstate, int oldstate ); +void IN_GyroInit( void ); +void IN_GyroCheckAvailability( void ); +void IN_GyroEvent( vec3_t data ); +void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ); uint IN_CollectInputDevices( void ); void IN_LockInputDevices( qboolean lock ); diff --git a/engine/common/host.c b/engine/common/host.c index 9253aa742f..417c9a1ea6 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -1276,6 +1276,10 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline) SCR_CheckStartupVids(); // must be last +#ifndef XASH_DEDICATED + IN_GyroCheckAvailability(); +#endif + if( Sys_GetParmFromCmdLine( "-timedemo", demoname )) Cbuf_AddTextf( "timedemo %s\n", demoname ); diff --git a/engine/platform/platform.h b/engine/platform/platform.h index d478ed330c..88f065c727 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -40,6 +40,17 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren void Platform_SetStatus( const char *status ); qboolean Platform_DebuggerPresent( void ); +typedef enum +{ + ORIENTATION_UNKNOWN = 0, + ORIENTATION_LANDSCAPE, + ORIENTATION_LANDSCAPE_FLIPPED, + ORIENTATION_PORTRAIT, + ORIENTATION_PORTRAIT_FLIPPED +} platform_orientation_t; + +platform_orientation_t Platform_GetDisplayOrientation( void ); + // legacy iOS port functions #if XASH_IOS int IOS_GetArgs( char ***argv ); @@ -64,6 +75,7 @@ char *Posix_Input( void ); void SDLash_Init( void ); void SDLash_Shutdown( void ); void SDLash_NanoSleep( int nsec ); +qboolean SDLash_GyroIsAvailable( void ); #endif #if XASH_ANDROID diff --git a/engine/platform/sdl2/host_sdl2.c b/engine/platform/sdl2/host_sdl2.c index ee9a45d931..bc9fc54e4f 100644 --- a/engine/platform/sdl2/host_sdl2.c +++ b/engine/platform/sdl2/host_sdl2.c @@ -368,6 +368,11 @@ static void SDLash_EventHandler( SDL_Event *event ) #endif SDLash_HandleGameControllerEvent( event ); break; +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + case SDL_SENSORUPDATE: + SDLash_SensorUpdate( event->sensor ); + break; +#endif case SDL_WINDOWEVENT: if( event->window.windowID != SDL_GetWindowID( host.hWnd ) ) diff --git a/engine/platform/sdl2/platform_sdl2.h b/engine/platform/sdl2/platform_sdl2.h index c44fe74781..3670a9d7fb 100644 --- a/engine/platform/sdl2/platform_sdl2.h +++ b/engine/platform/sdl2/platform_sdl2.h @@ -35,5 +35,15 @@ void SDLash_FreeCursors( void ); // void SDLash_HandleGameControllerEvent( SDL_Event *ev ); +// +// sensor_sdl2.c +// +void SDLash_InitSensors( void ); +void SDLash_ShutdownSensors( void ); +qboolean SDLash_GyroIsAvailable( void ); +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) +void SDLash_SensorUpdate( SDL_SensorEvent sensor ); +#endif + #endif // XASH_SDL #endif // KEYWRAPPER_H diff --git a/engine/platform/sdl2/sensor_sdl2.c b/engine/platform/sdl2/sensor_sdl2.c new file mode 100644 index 0000000000..1e23773633 --- /dev/null +++ b/engine/platform/sdl2/sensor_sdl2.c @@ -0,0 +1,103 @@ +/* +sensor_sdl2.c - SDL2 sensor handling +Copyright (C) 2026 Xash3D FWGS contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "common.h" +#include "input.h" +#include "platform_sdl2.h" + +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) +static SDL_SensorID g_system_gyro_id = -1; +static SDL_Sensor *g_system_gyro; + +/* +============== +SDLash_InitSensors + +============== +*/ +void SDLash_InitSensors( void ) +{ + if( SDL_InitSubSystem( SDL_INIT_SENSOR ) == 0 ) + { + int num_sensors = SDL_NumSensors(); + + for( int i = 0; i < num_sensors; i++ ) + { + if( SDL_SensorGetDeviceType( i ) == SDL_SENSOR_GYRO ) + { + g_system_gyro = SDL_SensorOpen( i ); + if( g_system_gyro ) + { + g_system_gyro_id = SDL_SensorGetInstanceID( g_system_gyro ); + Con_Printf( "SDL: Opened built-in gyroscope: %s\n", SDL_SensorGetName( g_system_gyro ) ); + break; + } + } + } + } + else + { + Con_Reportf( S_ERROR "Failed to init SDL Sensor subsystem: %s\n", SDL_GetError() ); + } +} + +/* +============== +SDLash_ShutdownSensors + +============== +*/ +void SDLash_ShutdownSensors( void ) +{ + if( g_system_gyro ) + { + SDL_SensorClose( g_system_gyro ); + g_system_gyro = NULL; + g_system_gyro_id = -1; + } + + SDL_QuitSubSystem( SDL_INIT_SENSOR ); +} + +/* +============== +SDLash_GyroIsAvailable + +============== +*/ +qboolean SDLash_GyroIsAvailable( void ) +{ + return ( g_system_gyro != NULL ); +} + +/* +============== +SDLash_SensorUpdate + +============== +*/ +void SDLash_SensorUpdate( SDL_SensorEvent sensor ) +{ + if( sensor.which == g_system_gyro_id ) + { + IN_GyroEvent( sensor.data ); + } +} +#else +void SDLash_InitSensors( void ) { } +void SDLash_ShutdownSensors( void ) { } +qboolean SDLash_GyroIsAvailable( void ) { return false; } +#endif diff --git a/engine/platform/sdl2/sys_sdl2.c b/engine/platform/sdl2/sys_sdl2.c index 5371a35e21..2709e100a9 100644 --- a/engine/platform/sdl2/sys_sdl2.c +++ b/engine/platform/sdl2/sys_sdl2.c @@ -146,10 +146,12 @@ void SDLash_Init( void ) SDL_StopTextInput(); SDLash_InitCursors(); + SDLash_InitSensors(); } void SDLash_Shutdown( void ) { + SDLash_ShutdownSensors(); SDLash_FreeCursors(); SDL_Quit(); diff --git a/engine/platform/sdl2/vid_sdl2.c b/engine/platform/sdl2/vid_sdl2.c index 3129d54598..ec5e02c941 100644 --- a/engine/platform/sdl2/vid_sdl2.c +++ b/engine/platform/sdl2/vid_sdl2.c @@ -1204,3 +1204,29 @@ void VID_Info_f( void ) else Con_Printf( "Window display mode: " S_RED "fail: " S_DEFAULT "%s\n", SDL_GetError( )); } + +platform_orientation_t Platform_GetDisplayOrientation( void ) +{ + if( host.hWnd ) + { + int display_index = SDL_GetWindowDisplayIndex( host.hWnd ); + if( display_index >= 0 ) + { + switch( SDL_GetDisplayOrientation( display_index ) ) + { + case SDL_ORIENTATION_LANDSCAPE: + return ORIENTATION_LANDSCAPE; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + return ORIENTATION_LANDSCAPE_FLIPPED; + case SDL_ORIENTATION_PORTRAIT: + return ORIENTATION_PORTRAIT; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + return ORIENTATION_PORTRAIT_FLIPPED; + default: + return ORIENTATION_UNKNOWN; + } + } + } + + return ORIENTATION_UNKNOWN; +}