diff --git a/engine/client/in_joy.c b/engine/client/in_joy.c index daabc588..e76aff4d 100644 --- a/engine/client/in_joy.c +++ b/engine/client/in_joy.c @@ -60,6 +60,8 @@ static CVAR_DEFINE_AUTO( joy_yaw_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | static CVAR_DEFINE_AUTO( joy_axis_binding, "sfpyrl", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "axis hardware id to engine inner axis binding, " "s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger" ); CVAR_DEFINE_AUTO( joy_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable joystick" ); +static CVAR_DEFINE_AUTO( joy_have_gyro, "0", FCVAR_READ_ONLY, "tells whether current active gamepad has gyroscope or not" ); +static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether current active gamepad gyroscope has been calibrated or not" ); /* ============ @@ -71,6 +73,29 @@ qboolean Joy_IsActive( void ) return joy_enable.value; } +/* +=========== +Joy_SetCapabilities +=========== +*/ +void Joy_SetCapabilities( qboolean have_gyro ) +{ + Cvar_FullSet( joy_have_gyro.name, have_gyro ? "1" : "0", joy_have_gyro.flags ); +} + +/* +=========== +Joy_SetCalibrationState +=========== +*/ +void Joy_SetCalibrationState( joy_calibration_state_t state ) +{ + if( (int)joy_calibrated.value == state ) + return; + + Cvar_FullSet( joy_calibrated.name, va( "%d", state ), joy_calibrated.flags ); +} + /* ============ Joy_HatMotionEvent @@ -244,6 +269,18 @@ void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value ) Joy_ProcessStick( engineAxis, value ); } +/* +============= +Joy_GyroEvent + +Gyroscope events +============= +*/ +void Joy_GyroEvent( vec3_t data ) +{ + +} + /* ============= Joy_FinalizeMove @@ -284,6 +321,17 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) *dyaw += joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; } +static void Joy_CalibrateGyro_f( void ) +{ + if( !joy_have_gyro.value ) + { + Con_Printf( "Current active gamepad doesn't have gyroscope\n" ); + return; + } + + Platform_CalibrateGamepadGyro(); +} + /* ============= Joy_Init @@ -293,6 +341,8 @@ Main init procedure */ void Joy_Init( void ) { + Cmd_AddRestrictedCommand( "joy_calibrate_gyro", Joy_CalibrateGyro_f, "calibrate gamepad gyroscope. You must to put gamepad on stationary surface" ); + Cvar_RegisterVariable( &joy_pitch ); Cvar_RegisterVariable( &joy_yaw ); Cvar_RegisterVariable( &joy_side ); @@ -313,6 +363,8 @@ void Joy_Init( void ) Cvar_RegisterVariable( &joy_axis_binding ); + Cvar_RegisterVariable( &joy_have_gyro ); + Cvar_RegisterVariable( &joy_calibrated ); Cvar_RegisterVariable( &joy_enable ); // renamed from -nojoy to -noenginejoy to not conflict with diff --git a/engine/client/input.h b/engine/client/input.h index ae844720..30a4d264 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -104,8 +104,19 @@ typedef enum engineAxis_e JOY_AXIS_NULL } engineAxis_t; +typedef enum joy_calibration_state_s +{ + JOY_NOT_CALIBRATED = 0, + JOY_CALIBRATING, + JOY_FAILED_TO_CALIBRATE, + JOY_CALIBRATED +} joy_calibration_state_t; + qboolean Joy_IsActive( void ); +void Joy_SetCapabilities( qboolean have_gyro ); +void Joy_SetCalibrationState( joy_calibration_state_t state ); void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value ); +void Joy_GyroEvent( vec3_t data ); void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ); void Joy_Init( void ); void Joy_Shutdown( void ); diff --git a/engine/platform/dos/in_dos.c b/engine/platform/dos/in_dos.c index 0ba1fd5d..ebef6bf9 100644 --- a/engine/platform/dos/in_dos.c +++ b/engine/platform/dos/in_dos.c @@ -91,16 +91,6 @@ void GAME_EXPORT Platform_SetMousePos(int x, int y) } -int Platform_JoyInit( void ) -{ - return 0; -} - -void Platform_JoyShutdown( void ) -{ - -} - void Platform_EnableTextInput( qboolean enable ) { keystate.chars = enable; diff --git a/engine/platform/linux/vid_fbdev.c b/engine/platform/linux/vid_fbdev.c index faab537c..9231868b 100644 --- a/engine/platform/linux/vid_fbdev.c +++ b/engine/platform/linux/vid_fbdev.c @@ -285,15 +285,4 @@ void Platform_Vibrate( float life, char flags ) { } - -int Platform_JoyInit( void ) -{ - return 0; -} - -void Platform_JoyShutdown( void ) -{ - -} - #endif diff --git a/engine/platform/platform.h b/engine/platform/platform.h index ab855aaa..fc7fdee0 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -210,8 +210,27 @@ void Platform_Vibrate2( float time, int low_freq, int high_freq, uint flags ); ============================================================================== */ // Gamepad support +#if XASH_SDL int Platform_JoyInit( void ); // returns number of connected gamepads, negative if error void Platform_JoyShutdown( void ); +void Platform_CalibrateGamepadGyro( void ); +#else +static inline int Platform_JoyInit( void ) +{ + return 0; +} + +static inline void Platform_JoyShutdown( void ) +{ + +} + +static inline void Platform_CalibrateGamepadGyro( void ) +{ + +} +#endif + // Text input void Platform_EnableTextInput( qboolean enable ); key_modifier_t Platform_GetKeyModifiers( void ); diff --git a/engine/platform/sdl/joy_sdl.c b/engine/platform/sdl/joy_sdl.c index b776da7b..c8fc91a1 100644 --- a/engine/platform/sdl/joy_sdl.c +++ b/engine/platform/sdl/joy_sdl.c @@ -38,7 +38,6 @@ static const int g_button_mapping[] = }; // Swap axis to follow default axis binding: -// LeftX, LeftY, RightX, RightY, TriggerRight, TriggerLeft static const engineAxis_t g_axis_mapping[] = { JOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX, @@ -54,6 +53,55 @@ static SDL_GameController *g_current_gamepad; static SDL_GameController **g_gamepads; static size_t g_num_gamepads; +#define CALIBRATION_TIME 10.0f + +static struct +{ + float time; + vec3_t data; + vec3_t calibrated_values; + int samples; +} gyrocal; + +static void SDLash_RestartCalibration( void ) +{ + Joy_SetCalibrationState( JOY_NOT_CALIBRATED ); + + memset( &gyrocal, 0, sizeof( gyrocal )); + + gyrocal.time = host.realtime + CALIBRATION_TIME; + + Con_Printf( "Starting gyroscope calibration...\n" ); +} + +static void SDLash_FinalizeCalibration( void ) +{ + float data_rate = 10.0f; // let's say we're polling at 10Hz? + int min_samples; + +#if SDL_VERSION_ATLEAST( 2, 0, 16 ) + data_rate = SDL_GameControllerGetSensorDataRate( g_current_gamepad, SDL_SENSOR_GYRO ); + if( !data_rate ) + data_rate = 10.0f; +#endif + + min_samples = Q_rint( CALIBRATION_TIME * data_rate * 0.75f ); + + // we waited for few seconds and got too few samples + if( gyrocal.samples <= min_samples ) + { + Joy_SetCalibrationState( JOY_FAILED_TO_CALIBRATE ); + return; + } + + VectorScale( gyrocal.data, 1.0f / gyrocal.samples, gyrocal.calibrated_values ); + Joy_SetCalibrationState( JOY_CALIBRATED ); + + Con_Printf( "Calibration done. Result: %f %f %f\n", gyrocal.calibrated_values[0], gyrocal.calibrated_values[1], gyrocal.calibrated_values[2] ); + + gyrocal.time = 0.0f; +} + static void SDLash_GameControllerAddMappings( const char *name ) { fs_offset_t len = 0; @@ -73,8 +121,42 @@ static void SDLash_GameControllerAddMappings( const char *name ) static void SDLash_SetActiveGameController( SDL_JoystickID id ) { + SDL_GameController *oldgc; + + if( g_current_gamepad_id == id ) + return; + g_current_gamepad_id = id; - g_current_gamepad = SDL_GameControllerFromInstanceID( id ); + + oldgc = g_current_gamepad; +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + SDL_GameControllerSetSensorEnabled( oldgc, SDL_SENSOR_GYRO, SDL_FALSE ); +#endif // SDL_VERSION_ATLEAST( 2, 0, 14 ) + + if( id < 0 ) + { + g_current_gamepad = NULL; + Joy_SetCapabilities( false ); + Joy_SetCalibrationState( JOY_NOT_CALIBRATED ); + } + else + { + qboolean have_gyro = false; + + g_current_gamepad = SDL_GameControllerFromInstanceID( id ); + +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + have_gyro = SDL_GameControllerHasSensor( g_current_gamepad, SDL_SENSOR_GYRO ); + + if( have_gyro ) + { + SDL_GameControllerSetSensorEnabled( g_current_gamepad, SDL_SENSOR_GYRO, SDL_TRUE ); + SDLash_RestartCalibration(); + } +#endif // SDL_VERSION_ATLEAST( 2, 0, 14 ) + + Joy_SetCapabilities( have_gyro ); + } } static void SDLash_GameControllerAdded( int device_index ) @@ -102,6 +184,8 @@ static void SDLash_GameControllerAdded( int device_index ) if( joy ) SDLash_SetActiveGameController( SDL_JoystickInstanceID( joy )); } + + Con_Printf( "Detected \"%s\" game controller.\nMapping string: %s\n", SDL_GameControllerName( gc ), SDL_GameControllerMapping( gc )); } static void SDLash_GameControllerRemoved( SDL_JoystickID id ) @@ -109,10 +193,7 @@ static void SDLash_GameControllerRemoved( SDL_JoystickID id ) size_t i; if( id == g_current_gamepad_id ) - { - g_current_gamepad_id = -1; - g_current_gamepad = NULL; - } + SDLash_SetActiveGameController( -1 ); // now close the device for( i = 0; i < g_num_gamepads; i++ ) @@ -130,42 +211,38 @@ static void SDLash_GameControllerRemoved( SDL_JoystickID id ) if( SDL_JoystickInstanceID( joy ) == id ) { + Con_Printf( "Game controller \"%s\" was disconnected\n", SDL_GameControllerName( gc )); + SDL_GameControllerClose( gc ); g_gamepads[i] = NULL; } } } -/* -============= -SDLash_JoyInit - -============= -*/ -static int SDLash_JoyInit( void ) +static void SDLash_GameControllerSensorUpdate( SDL_ControllerSensorEvent sensor ) { - int count, numJoysticks, i; + vec3_t data; - Con_Reportf( "Joystick: SDL GameController API\n" ); - if( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER && - SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER )) + if( sensor.which != g_current_gamepad_id ) + return; + + if( sensor.sensor != SDL_SENSOR_GYRO ) + return; + + if( gyrocal.time != 0.0f ) { - Con_Reportf( "Failed to initialize SDL GameController API: %s\n", SDL_GetError() ); - return 0; + if( host.realtime > gyrocal.time ) + SDLash_FinalizeCalibration(); + + VectorAdd( gyrocal.data, sensor.data, gyrocal.data ); + gyrocal.samples++; + + Joy_SetCalibrationState( JOY_CALIBRATING ); + return; } - SDLash_GameControllerAddMappings( "gamecontrollerdb.txt" ); // shipped in extras.pk3 - SDLash_GameControllerAddMappings( "controllermappings.txt" ); - - count = 0; - numJoysticks = SDL_NumJoysticks(); - for ( i = 0; i < numJoysticks; i++ ) - { - if( SDL_IsGameController( i )) - ++count; - } - - return count; + VectorSubtract( sensor.data, gyrocal.calibrated_values, data ); + Joy_GyroEvent( data ); } void SDLash_HandleGameControllerEvent( SDL_Event *ev ) @@ -193,9 +270,19 @@ void SDLash_HandleGameControllerEvent( SDL_Event *ev ) case SDL_CONTROLLERDEVICEADDED: SDLash_GameControllerAdded( ev->cdevice.which ); break; +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + case SDL_CONTROLLERSENSORUPDATE: + SDLash_GameControllerSensorUpdate( ev->csensor ); + break; +#endif } } +void Platform_CalibrateGamepadGyro( void ) +{ + SDLash_RestartCalibration(); +} + void Platform_Vibrate2( float time, int val1, int val2, uint flags ) { #if SDL_VERSION_ATLEAST( 2, 0, 9 ) @@ -235,7 +322,28 @@ Platform_JoyInit */ int Platform_JoyInit( void ) { - return SDLash_JoyInit(); + int count, numJoysticks, i; + + Con_Reportf( "Joystick: SDL GameController API\n" ); + if( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER && + SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER )) + { + Con_Reportf( "Failed to initialize SDL GameController API: %s\n", SDL_GetError( )); + return 0; + } + + SDLash_GameControllerAddMappings( "gamecontrollerdb.txt" ); // shipped in extras.pk3 + SDLash_GameControllerAddMappings( "controllermappings.txt" ); + + count = 0; + numJoysticks = SDL_NumJoysticks(); + for ( i = 0; i < numJoysticks; i++ ) + { + if( SDL_IsGameController( i )) + ++count; + } + + return count; } /* @@ -248,6 +356,8 @@ void Platform_JoyShutdown( void ) { size_t i; + SDLash_SetActiveGameController( -1 ); + for( i = 0; i < g_num_gamepads; i++ ) { if( !g_gamepads[i] ) @@ -261,9 +371,6 @@ void Platform_JoyShutdown( void ) g_gamepads = NULL; g_num_gamepads = 0; - g_current_gamepad = NULL; - g_current_gamepad_id = -1; - SDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER ); } #else // SDL_VERSION_ATLEAST( 2, 0, 0 )