diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index 0a35b734..c2ec4265 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -535,6 +535,7 @@ void V_PostRender( void ) SCR_DrawEnts(); SCR_DrawNetGraph(); SCR_DrawUserCmd(); + Joy_DrawDebug(); SV_DrawOrthoTriangles(); CL_DrawDemoRecording(); CL_DrawHUD( CL_CHANGELEVEL ); diff --git a/engine/client/console.c b/engine/client/console.c index 2f561193..8cd5503f 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1573,7 +1573,7 @@ void Key_Console( int key ) } // command completion - if( key == K_TAB || key == K_L2_BUTTON ) + if( key == K_TAB || key == K_L2_BUTTON || key == K_LTRIGGER ) { Con_CompleteCommand( &con.input, true ); Con_Bottom(); diff --git a/engine/client/in_joy.c b/engine/client/in_joy.c index a213b9c0..5d515bb2 100644 --- a/engine/client/in_joy.c +++ b/engine/client/in_joy.c @@ -24,8 +24,6 @@ GNU General Public License for more details. #define SHRT_MAX 0x7FFF #endif -#define MAX_AXES JOY_AXIS_NULL - // index - axis num come from event // value - inner axis static engineAxis_t joyaxesmap[MAX_AXES] = @@ -42,7 +40,9 @@ static struct joy_axis_s { short val; short prevval; + short rawval; // value before deadzone zeroing, for debug display } joyaxis[MAX_AXES] = { 0 }; + static qboolean joy_initialized; static CVAR_DEFINE_AUTO( joy_pitch, "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick pitch sensitivity" ); @@ -65,9 +65,12 @@ static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether cu static CVAR_DEFINE_AUTO( joy_gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for looking up and down" ); static CVAR_DEFINE_AUTO( joy_gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for turning left and right" ); static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity when tilting the device sideways" ); +static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" ); // stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input static vec3_t joy_gyro_speed; +// last non-zero gyro speed, retained for debug display after joy_gyro_speed is cleared +static vec3_t joy_gyro_speed_display; /* ============ @@ -234,6 +237,8 @@ static void Joy_ProcessStick( const engineAxis_t engineAxis, short value ) break; } + joyaxis[engineAxis].rawval = value; + if( value < deadzone && value > -deadzone ) value = 0; // caught new event in deadzone, fill it with zero(no motion) @@ -263,12 +268,12 @@ Axis events */ void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value ) { - if( engineAxis >= JOY_AXIS_NULL ) + if( engineAxis >= MAX_AXES ) return; engineAxis = joyaxesmap[engineAxis]; - if( engineAxis >= JOY_AXIS_NULL ) + if( engineAxis >= MAX_AXES ) return; if( value == joyaxis[engineAxis].val ) @@ -290,6 +295,7 @@ Gyroscope events void Joy_GyroEvent( vec3_t data ) { VectorCopy( data, joy_gyro_speed ); + VectorCopy( data, joy_gyro_speed_display ); } /* @@ -319,7 +325,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) case 'p': joyaxesmap[i] = JOY_AXIS_PITCH; break; case 'r': joyaxesmap[i] = JOY_AXIS_RT; break; case 'l': joyaxesmap[i] = JOY_AXIS_LT; break; - default : joyaxesmap[i] = JOY_AXIS_NULL; break; + default : joyaxesmap[i] = MAX_AXES; break; } } @@ -345,6 +351,178 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) VectorClear( joy_gyro_speed ); } +/* +============= +Joy_DrawDebug + +Draw gamepad axes and buttons for debugging (joy_debug 1) +============= +*/ +void Joy_DrawDebug( void ) +{ + static const int buttons[] = + { + K_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON, 0, + K_L1_BUTTON, K_R1_BUTTON, 0, + K_L2_BUTTON, K_R2_BUTTON, 0, + K_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON, 0, + K_LSTICK, K_RSTICK, 0, + + // simulated buttons + K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT, 0, + K_LTRIGGER, K_RTRIGGER, 0, + + // additional buttons + K_C_BUTTON, K_Z_BUTTON, K_MISC_BUTTON, 0, + K_PADDLE1_BUTTON, K_PADDLE2_BUTTON, K_PADDLE3_BUTTON, K_PADDLE4_BUTTON, 0 + }; + static const char *const axisnames[MAX_AXES] = { "SIDE", "FWD", "PITCH", "YAW", "RT", "LT" }; + + // deadzone for sticks, threshold for triggers + static const convar_t *const axis_threshold[MAX_AXES] = + { + &joy_side_deadzone, + &joy_forward_deadzone, + &joy_pitch_deadzone, + &joy_yaw_deadzone, + &joy_rt_threshold, + &joy_lt_threshold, + }; + + cl_font_t *font = Con_GetCurFont(); + + if( !joy_debug.value || !Joy_IsActive( )) + return; + + float x = 8; + float y = 100; + const float bar_w = 100; + const float halfbar_w = bar_w * 0.5f; + const float bar_h = font->charHeight - 2; + const float center = x + halfbar_w; + + const rgba_t bar_backcolor = { 40, 40, 40, 180 }; + const rgba_t bar_fillcolor = { 100, 200, 100, 200 }; + + // draw axes as labeled bars + for( int i = 0; i < MAX_AXES; i++ ) + { + + qboolean is_trigger = ( i >= JOY_AXIS_RT ); + float fval = (float)joyaxis[i].val / SHRT_MAX; + float fprev = (float)joyaxis[i].prevval / SHRT_MAX; + float fthreshold = axis_threshold[i]->value / SHRT_MAX; + + CL_DrawString( x, y, axisnames[i], g_color_table[7], font, 0 ); + y += font->charHeight; + + // background + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] ); + + if( is_trigger ) + { + // fill bar from the left for triggers + float filled = fval * bar_w; + + if( filled > 0 ) + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, filled, bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] ); + + // fill threshold with red, and yellow edge marker + float thresh_x = x + fthreshold * bar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, thresh_x - x, bar_h, 180, 40, 40, 120 ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, thresh_x, y, 1, bar_h, 255, 200, 0, 220 ); + + // prevval marker + float prev_x = x + fprev * bar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, prev_x, y, 1, bar_h, 200, 200, 200, 180 ); + } + else + { + // for sticks bar is filled from the center + float filled = fval * halfbar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, center + Q_min( 0, filled ), y, fabs( filled ), bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] ); + + // fill deadzone with red, and yellow edge marker + float thresh_x = fthreshold * halfbar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, thresh_x * 2, bar_h, 180, 40, 40, 120 ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, center + thresh_x, y, 1, bar_h, 255, 200, 0, 220 ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, 1, bar_h, 255, 200, 0, 220 ); + + // mark the center + ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 ); + + // now draw the actual value + float raw_x = center + ((float)joyaxis[i].rawval / SHRT_MAX * halfbar_w ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, raw_x, y, 1, bar_h, 255, 255, 100, 200 ); + + // prevval marker + float prev_x = center + fprev * halfbar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, prev_x, y, 1, bar_h, 200, 200, 200, 180 ); + } + + y += bar_h + 2; + } + + // draw gyro if available + if( joy_have_gyro.value ) + { + static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" }; + + for( int i = 0; i < 3; i++ ) + { + // clamp to +-5 rad/s for display + float fval = joy_gyro_speed_display[i] / 5.0f; + + fval = bound( -1.0f, fval, 1.0f ); + + CL_DrawString( x, y, gyronames[i], g_color_table[7], font, 0 ); + y += font->charHeight; + + // background + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] ); + + // for gyro bar is filled from the center + float filled = fval * halfbar_w; + ref.dllFuncs.FillRGBA( kRenderTransTexture, center + Q_min( 0, filled ), y, fabs( filled ), bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] ); + + // mark the center + ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 ); + + // gyro doesn't have deadzones or prevval like axes + y += bar_h + 2; + } + } + + // draw buttons in a row + y += 4; + x = 8; + for( int i = 0; i < ARRAYSIZE( buttons ); i++ ) + { + if( !buttons[i] ) + { + x = 8; + y += font->charHeight; + continue; + } + + qboolean pressed = Key_IsDown( buttons[i] ); + int btn_w = 0; + rgba_t color; + string name; + + Q_strncpy( name, Key_KeynumToString( buttons[i] ), sizeof( name )); + + char *p = Q_strstr( name, "_BUTTON" ); + if( p ) + *p = 0; + + MakeRGBA( color, pressed ? 0 : 255, pressed ? 255 : 0, 0, 255 ); + CL_DrawString( x, y, name, color, font, 0 ); + CL_DrawStringLen( font, name, &btn_w, NULL, 0 ); + x += btn_w + 6; + } +} + static void Joy_CalibrateGyro_f( void ) { if( !joy_have_gyro.value ) @@ -394,6 +572,7 @@ void Joy_Init( void ) Cvar_RegisterVariable( &joy_gyro_pitch ); Cvar_RegisterVariable( &joy_gyro_yaw ); Cvar_RegisterVariable( &joy_gyro_roll ); + Cvar_RegisterVariable( &joy_debug ); // renamed from -nojoy to -noenginejoy to not conflict with // client.dll's joystick support diff --git a/engine/client/input.h b/engine/client/input.h index 93d83081..ef0f726a 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -119,7 +119,7 @@ typedef enum engineAxis_e JOY_AXIS_YAW, JOY_AXIS_RT, JOY_AXIS_LT, - JOY_AXIS_NULL + MAX_AXES, } engineAxis_t; typedef enum joy_calibration_state_s @@ -136,6 +136,7 @@ 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_DrawDebug( void ); void Joy_Init( void ); void Joy_Shutdown( void ); diff --git a/engine/keydefs.h b/engine/keydefs.h index 657d51c8..3e7a0340 100644 --- a/engine/keydefs.h +++ b/engine/keydefs.h @@ -84,6 +84,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define K_JOY3 205 #define K_JOY4 206 +#define K_LTRIGGER K_JOY1 +#define K_RTRIGGER K_JOY2 + // // aux keys are for multi-buttoned joysticks to generate so they can use // the normal binding process