engine: client: add joy_debug, tool that visualizes gamepad input

This commit is contained in:
Alibek Omarov
2026-04-08 11:12:30 +05:00
parent 2c3fdb5eee
commit 1c73d8d6f4
5 changed files with 191 additions and 7 deletions

View File

@@ -535,6 +535,7 @@ void V_PostRender( void )
SCR_DrawEnts();
SCR_DrawNetGraph();
SCR_DrawUserCmd();
Joy_DrawDebug();
SV_DrawOrthoTriangles();
CL_DrawDemoRecording();
CL_DrawHUD( CL_CHANGELEVEL );

View File

@@ -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();

View File

@@ -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

View File

@@ -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 );

View File

@@ -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