Merge branch 'joystick'

This give much better control over individual joystick axes. They now have
per-axis pre and post amplification, the linear controller mappings are
more intuitive, and axes can now bet setup as buttons using thresholds.

Many thanks to Johnny on Flame for his work on the "user interface".
This commit is contained in:
Bill Currie 2013-01-28 18:11:55 +09:00
commit 8314edce4d
7 changed files with 510 additions and 86 deletions

View file

@ -146,4 +146,9 @@ int Menu_KeyEvent (knum_t key, short unicode, qboolean down);
void Menu_Enter (void);
void Menu_Leave (void);
void Menu_Enter_f (void);
void Menu_Leave_f (void);
void Menu_Prev_f (void);
void Menu_Next_f (void);
#endif // __console_h

View file

@ -29,6 +29,7 @@
#define __QF_joystick_h_
#include <QF/qtypes.h>
#include "QF/quakeio.h"
#define JOY_MAX_AXES 8
#define JOY_MAX_BUTTONS 18
@ -36,17 +37,47 @@
extern struct cvar_s *joy_device; // Joystick device name
extern struct cvar_s *joy_enable; // Joystick enabling flag
extern qboolean joy_found; // Joystick present?
extern qboolean joy_active; // Joystick in use?
struct joy_axis {
struct cvar_s *axis;
int current;
struct joy_axis_button {
float threshold;
int key;
int state;
};
typedef enum {
js_none, // ignore axis
js_position, // linear delta
js_angles, // linear delta
js_button, // axis button
} js_dest_t;
typedef enum {
js_clear,
js_amp,
js_pre_amp,
js_deadzone,
js_offset,
js_type,
js_axis_button,
} js_opt_t;
struct joy_axis {
int current;
float amp;
float pre_amp;
int deadzone;
float offset;
js_dest_t dest;
int axis; // if linear delta
int num_buttons; // if axis button
struct joy_axis_button *axis_buttons; // if axis button
};
extern qboolean joy_found; // Joystick present?
extern qboolean joy_active; // Joystick in use?
struct joy_button {
int old;
int current;
int old;
int current;
};
extern struct joy_axis joy_axes[JOY_MAX_AXES];
@ -64,6 +95,7 @@ extern struct joy_button joy_buttons[JOY_MAX_BUTTONS];
joy_enable->int_val are zero.
*/
void JOY_Command (void);
void joy_clear_axis (int i);
/*
JOY_Move (usercmd_t *) // FIXME: Not anymore!
@ -111,4 +143,14 @@ void JOY_Close (void);
*/
void JOY_Read (void);
const char *JOY_GetOption_c (int i);
int JOY_GetOption_i (const char *c);
const char *JOY_GetDest_c (int i);
int JOY_GetDest_i (const char *c);
void Joy_WriteBindings (QFile *f);
#endif // __QF_joystick_h_

View file

@ -410,6 +410,42 @@ typedef enum {
QFJ_BUTTON31,
QFJ_BUTTON32,
//
// joystick axes (for button emulation without consuming buttons)
//
QFJ_AXIS1,
QFJ_AXIS2,
QFJ_AXIS3,
QFJ_AXIS4,
QFJ_AXIS5,
QFJ_AXIS6,
QFJ_AXIS7,
QFJ_AXIS8,
QFJ_AXIS9,
QFJ_AXIS10,
QFJ_AXIS11,
QFJ_AXIS12,
QFJ_AXIS13,
QFJ_AXIS14,
QFJ_AXIS15,
QFJ_AXIS16,
QFJ_AXIS17,
QFJ_AXIS18,
QFJ_AXIS19,
QFJ_AXIS20,
QFJ_AXIS21,
QFJ_AXIS22,
QFJ_AXIS23,
QFJ_AXIS24,
QFJ_AXIS25,
QFJ_AXIS26,
QFJ_AXIS27,
QFJ_AXIS28,
QFJ_AXIS29,
QFJ_AXIS30,
QFJ_AXIS31,
QFJ_AXIS32,
QFK_LAST
} knum_t;
@ -473,6 +509,7 @@ void Key_KeydestCallback (keydest_callback_t *callback);
const char *Key_KeynumToString (knum_t keynum);
int Key_StringToKeynum (const char *str);
struct progs_s;
void Key_Progs_Init (struct progs_s *pr);
#endif

View file

@ -452,6 +452,26 @@ bi_Menu_Enter (progs_t *pr)
}
}
static void
bi_Menu_Leave (progs_t *pr)
{
if (menu) {
if (menu->leave_hook) {
run_menu_pre ();
PR_ExecuteProgram (&menu_pr_state, menu->leave_hook);
run_menu_post ();
}
menu = menu->parent;
if (!menu) {
if (con_data.force_commandline) {
Key_SetKeyDest (key_console);
} else {
Key_SetKeyDest (key_game);
}
}
}
}
static void
togglemenu_f (void)
{
@ -517,9 +537,38 @@ static builtin_t builtins[] = {
{"Menu_Next", bi_Menu_Next, -1},
{"Menu_Prev", bi_Menu_Prev, -1},
{"Menu_Enter", bi_Menu_Enter, -1},
{"Menu_Leave", bi_Menu_Leave, -1},
{0},
};
void
Menu_Enter_f (void)
{
if (!Menu_KeyEvent(QFK_RETURN, '\0', true))
Menu_KeyEvent('y', 'y', true);
}
void
Menu_Leave_f (void)
{
Menu_Leave ();
}
void
Menu_Prev_f (void)
{
Menu_KeyEvent (QFK_UP, '\0', true);
}
void
Menu_Next_f (void)
{
Menu_KeyEvent (QFK_DOWN, '\0', true);
}
void
Menu_Init (void)
{
@ -549,6 +598,10 @@ Menu_Init (void)
"Toggle the display of the menu");
Cmd_RemoveCommand ("quit");
Cmd_AddCommand ("quit", quit_f, "Exit the program");
Cmd_AddCommand ("Menu_Enter", Menu_Enter_f, "Do menu action/move up in the menu tree.");
Cmd_AddCommand ("Menu_Leave", Menu_Leave_f, "Move down in the menu tree.");
Cmd_AddCommand ("Menu_Prev", Menu_Prev_f, "Move cursor up.");
Cmd_AddCommand ("Menu_Next", Menu_Next_f, "Move cursor up.");
}
void

View file

@ -37,8 +37,10 @@
#include "QF/mathlib.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/cmd.h"
#include "compat.h"
#include <string.h>
cvar_t *joy_device; // Joystick device name
cvar_t *joy_enable; // Joystick enabling flag
@ -48,25 +50,59 @@ cvar_t *joy_pre_amp; // Joystick pre-amplification
qboolean joy_found = false;
qboolean joy_active = false;
typedef struct {
const char *name;
const char *string;
} ocvar_t;
ocvar_t joy_axes_cvar_init[JOY_MAX_AXES] = {
{"joyaxis1", "1"},
{"joyaxis2", "2"},
{"joyaxis3", "3"},
{"joyaxis4", "0"},
{"joyaxis5", "0"},
{"joyaxis6", "0"},
{"joyaxis7", "0"},
{"joyaxis8", "0"}
};
struct joy_axis joy_axes[JOY_MAX_AXES];
struct joy_button joy_buttons[JOY_MAX_BUTTONS];
void
joy_clear_axis (int i)
{
joy_axes[i].dest = js_none;
joy_axes[i].amp = 1;
joy_axes[i].pre_amp = 1;
joy_axes[i].deadzone = 12500;
joy_axes[i].num_buttons = 0;
if (joy_axes[i].axis_buttons) {
free (joy_axes[i].axis_buttons);
joy_axes[i].axis_buttons = NULL;
}
}
static void
joy_check_axis_buttons (struct joy_axis *ja, float value)
{
struct joy_axis_button *ab;
int pressed = -1;
int i;
// the axis button list is sorted in decending order of absolute threshold
for (i = 0; i < ja->num_buttons; i++) {
ab = &ja->axis_buttons[i];
if ((value < 0) == (ab->threshold < 0)
&& fabsf (value) >= fabsf (ab->threshold)) {
pressed = i;
break;
}
}
// make sure any buttons that are no longer active are "released"
for (i = 0; i < ja->num_buttons; i++) {
if (i == pressed)
continue;
ab = &ja->axis_buttons[i];
if (ab->state) {
Key_Event (ab->key, 0, 0);
ab->state = 0;
}
}
// press the active button if there is one
if (pressed >= 0) {
// FIXME support repeat?
if (!ab->state)
Key_Event (ab->key, 0, 1);
ab->state = 1;
}
}
VISIBLE void
JOY_Command (void)
@ -77,70 +113,36 @@ JOY_Command (void)
VISIBLE void
JOY_Move (void)
{
float mult_joy;
struct joy_axis *ja;
float value;
float amp = joy_amp->value * in_amp->value;
float pre = joy_pre_amp->value * in_pre_amp->value;
int i;
if (!joy_active || !joy_enable->int_val)
return;
mult_joy = (joy_amp->value * joy_pre_amp->value *
in_amp->value * in_pre_amp->value) / 200.0;
// Yes, mult_joy looks like a mess, but use of pre_amp values is useful in
// scripts, and *_pre_amp will matter once joystick filtering/acceleration
// is implemented
for (i = 0; i < JOY_MAX_AXES; i++) {
switch (joy_axes[i].axis->int_val) {
case 1:
if (joy_axes[i].current)
viewdelta.angles[YAW] -= joy_axes[i].current * mult_joy;
ja = &joy_axes[i];
value = ja->current * pre * ja->pre_amp;
if (fabs (value) < ja->deadzone)
value = -ja->offset;
value += ja->offset;
value *= amp * ja->amp / 100.0f;
switch (ja->dest) {
case js_none:
// ignore axis
break;
case -1:
if (joy_axes[i].current)
viewdelta.angles[YAW] += joy_axes[i].current * mult_joy;
case js_position:
if (ja->current)
viewdelta.position[(ja->axis) ? 2 : 0] += value;
break;
case 2:
if (joy_axes[i].current)
viewdelta.position[2] -= joy_axes[i].current * mult_joy;
case js_angles:
if (ja->current)
viewdelta.angles[(ja->axis) ? 1 : 0] -= value;
break;
case -2:
if (joy_axes[i].current)
viewdelta.position[2] += joy_axes[i].current * mult_joy;
break;
case 3:
if (joy_axes[i].current)
viewdelta.position[0] += joy_axes[i].current * mult_joy;
break;
case -3:
if (joy_axes[i].current)
viewdelta.position[0] -= joy_axes[i].current * mult_joy;
break;
case 4:
if (joy_axes[i].current)
viewdelta.angles[PITCH] -= joy_axes[i].current * mult_joy;
break;
case -4:
if (joy_axes[i].current)
viewdelta.angles[PITCH] += joy_axes[i].current * mult_joy;
break;
case 5:
if (joy_axes[i].current)
viewdelta.position[1] -= joy_axes[i].current * mult_joy;
break;
case -5:
if (joy_axes[i].current)
viewdelta.position[1] += joy_axes[i].current * mult_joy;
break;
// Futureproofing
case 6:
if (joy_axes[i].current)
viewdelta.angles[ROLL] += joy_axes[i].current * mult_joy;
break;
case -6:
if (joy_axes[i].current)
viewdelta.angles[ROLL] -= joy_axes[i].current * mult_joy;
break;
default:
case js_button:
joy_check_axis_buttons (ja, value);
break;
}
}
@ -149,7 +151,7 @@ JOY_Move (void)
VISIBLE void
JOY_Init (void)
{
int i;
int i;
if (JOY_Open () == -1) {
Sys_MaskPrintf (SYS_VID, "JOY: Joystick not found.\n");
@ -182,13 +184,235 @@ joyamp_f (cvar_t *var)
Cvar_Set (var, va ("%g", max (0.0001, var->value)));
}
typedef struct {
const char *name;
js_dest_t destnum;
} js_dests_t;
typedef struct {
const char *name;
js_dest_t optnum;
} js_opts_t;
js_dests_t js_dests[] = {
{"none", js_none}, // ignore axis
{"movement", js_position}, // linear delta
{"aim", js_angles}, // linear delta
{"button", js_button}, // axis button
{0, 0}
};
js_opts_t js_opts[] = {
{"clear", js_clear},
{"amp", js_amp},
{"pre_amp", js_pre_amp},
{"deadzone", js_deadzone},
{"offset", js_offset},
{"type", js_type},
{"button", js_axis_button},
{0, 0}
};
const char *
JOY_GetOption_c (int i)
{
js_opts_t *opt;
for (opt = &js_opts[0]; opt->name; opt++) {
if ((int) opt->optnum == i)
return opt->name;
}
return NULL;
}
int
JOY_GetOption_i (const char *c)
{
js_opts_t *opt;
for (opt = &js_opts[0]; opt->name; opt++) {
if (!strcmp (opt->name, c))
return opt->optnum;
}
return -1; // Failure code;
}
const char *
JOY_GetDest_c (int i)
{
js_dests_t *dest;
for (dest = &js_dests[0]; dest->name; dest++) {
if ((int) dest->destnum == i)
return dest->name;
}
return NULL;
}
int
JOY_GetDest_i (const char *c)
{
js_dests_t *dest;
for (dest = &js_dests[0]; dest->name; dest++) {
if (!strcmp (dest->name, c))
return dest->destnum;
}
return -1; // Failure code;
}
static void
in_joy_button_add_f (int ax, int index)
{
int n;
size_t size;
const char *key = Cmd_Argv (index);
int keynum;
const char *thrsh = Cmd_Argv (index + 1);
float threshold;
char *end = 0;
keynum = strtol (key, &end, 10) + QFJ_AXIS1;
if (*end || keynum < QFJ_AXIS1 || keynum > QFJ_AXIS32) {
// if the key is not valid, try a key name
keynum = Key_StringToKeynum (key);
}
if (keynum == -1) {
Sys_Printf ("\"%s\" isn't a valid key\n", key);
}
threshold = strtof (thrsh, &end);
if (*end) {
Sys_Printf ("invalid threshold: %s\n", thrsh);
keynum = -1;
}
if (keynum == -1)
return;
n = joy_axes[ax].num_buttons++;
size = n * sizeof (joy_axes[ax].axis_buttons);
joy_axes[ax].axis_buttons = realloc (joy_axes[ax].axis_buttons, size);
joy_axes[ax].axis_buttons[n].key = keynum;
joy_axes[ax].axis_buttons[n].threshold = threshold;
}
static void
in_joy_f (void)
{
const char *arg;
int i, ax, c = Cmd_Argc ();
if (c == 2) {
ax = JOY_GetOption_i (Cmd_Argv (1));
switch (ax) {
case js_clear:
Sys_Printf ("Clearing all joystick settings...\n");
for (i = 0; i < JOY_MAX_AXES; i++) {
joy_clear_axis (i);
}
break;
case js_amp:
Sys_Printf ("[...]<amp> [<#amp>]: Axis sensitivity\n");
break;
case js_pre_amp:
Sys_Printf ("[...]<pre_amp> [<#pre_amp>]: Axis sensitivity.\n");
break;
case js_deadzone:
Sys_Printf ("[...]<deadzone> [<#dz>]: Axis deadzone.\n");
break;
case js_offset:
Sys_Printf ("[...]<offset> [<#off>]: Axis initial position.\n");
break;
case js_type:
Sys_Printf ("[...]<type> [<act> <#act>].\n");
Sys_Printf ("Values for <act>:\n");
Sys_Printf ("none: #0\n");
Sys_Printf ("aim: #1..0\n");
Sys_Printf ("movement: #1..0\n");
break;
case js_axis_button:
/* TODO */
break;
default:
ax = strtol (Cmd_Argv (1), NULL, 0);
Sys_Printf ("<=====> AXIS %i <=====>\n", ax);
Sys_Printf ("amp: %.9g\n", joy_axes[ax].amp);
Sys_Printf ("pre_amp: %.9g\n", joy_axes[ax].pre_amp);
Sys_Printf ("deadzone: %i\n", joy_axes[ax].deadzone);
Sys_Printf ("offset: %.9g\n", joy_axes[ax].offset);
Sys_Printf ("type: %s\n",
JOY_GetDest_c (joy_axes[ax].dest));
Sys_Printf ("<====================>\n");
break;
}
return;
} else if (c < 4) {
if (JOY_GetOption_i (Cmd_Argv (2)) == js_clear) {
ax = strtol (Cmd_Argv (1), NULL, 0);
joy_clear_axis (ax);
return;
} else {
Sys_Printf ("in_joy <axis#> [<var> <value>]*\n"
" Configures the joystick behaviour\n");
return;
}
}
ax = strtol (Cmd_Argv (1), NULL, 0);
i = 2;
while (i < c) {
int var = JOY_GetOption_i (Cmd_Argv (i++));
switch (var) {
case js_amp:
joy_axes[ax].amp = strtof (Cmd_Argv (i++), NULL);
break;
case js_pre_amp:
joy_axes[ax].pre_amp = strtof (Cmd_Argv (i++), NULL);
break;
case js_deadzone:
joy_axes[ax].deadzone = strtol (Cmd_Argv (i++), NULL, 10);
break;
case js_offset:
joy_axes[ax].offset = strtol (Cmd_Argv (i++), NULL, 10);
break;
case js_type:
joy_axes[ax].dest = JOY_GetDest_i (Cmd_Argv (i++));
joy_axes[ax].axis = strtol (Cmd_Argv (i++), NULL, 10);
if (joy_axes[ax].axis > 1 || joy_axes[ax].axis < 0) {
joy_axes[ax].axis = 0;
Sys_Printf ("Invalid axis value.");
}
break;
case js_axis_button:
arg = Cmd_Argv (i++);
if (!strcmp ("add", arg)) {
in_joy_button_add_f (ax, i);
i += 2;
}
break;
default:
Sys_Printf ("Unknown option %s.\n", Cmd_Argv (i - 1));
break;
}
}
}
VISIBLE void
JOY_Init_Cvars (void)
{
int i;
joy_device = Cvar_Get ("joy_device", "/dev/input/js0", CVAR_NONE | CVAR_ROM, 0,
"Joystick device");
joy_device = Cvar_Get ("joy_device", "/dev/input/js0",
CVAR_NONE | CVAR_ROM, 0, "Joystick device");
joy_enable = Cvar_Get ("joy_enable", "1", CVAR_NONE | CVAR_ARCHIVE, 0,
"Joystick enable flag");
joy_amp = Cvar_Get ("joy_amp", "1", CVAR_NONE | CVAR_ARCHIVE, joyamp_f,
@ -196,10 +420,38 @@ JOY_Init_Cvars (void)
joy_pre_amp = Cvar_Get ("joy_pre_amp", "1", CVAR_NONE | CVAR_ARCHIVE,
joyamp_f, "Joystick pre-amplification");
Cmd_AddCommand ("in_joy", in_joy_f, "Configures the joystick behaviour");
for (i = 0; i < JOY_MAX_AXES; i++) {
joy_axes[i].axis = Cvar_Get (joy_axes_cvar_init[i].name,
joy_axes_cvar_init[i].string,
CVAR_ARCHIVE, 0, "Set joystick axes");
joy_axes[i].dest = js_none;
joy_axes[i].amp = 1;
joy_axes[i].pre_amp = 1;
joy_axes[i].deadzone = 500;
}
}
void
Joy_WriteBindings (QFile * f)
{
int i;
for (i = 0; i < JOY_MAX_AXES; i++) {
Qprintf (f, "in_joy %i amp %.9g pre_amp %.9g deadzone %i "
"offset %.9g type %s %i\n",
i, joy_axes[i].amp, joy_axes[i].pre_amp, joy_axes[i].deadzone,
joy_axes[i].offset, JOY_GetDest_c (joy_axes[i].dest),
joy_axes[i].axis);
if (joy_axes[i].num_buttons > 0) {
int n;
for (n = 0; n < joy_axes[i].num_buttons; n++) {
Qprintf (f, "in_joy %i button add %s %.9g\n", i,
Key_KeynumToString (joy_axes[i].axis_buttons[n].key),
joy_axes[i].axis_buttons[n].threshold);
}
}
}
}

View file

@ -443,6 +443,39 @@ keyname_t keynames[] = {
{ "J_BUTTON31", QFJ_BUTTON31 },
{ "J_BUTTON32", QFJ_BUTTON32 },
{ "J_AXIS1", QFJ_AXIS1 },
{ "J_AXIS2", QFJ_AXIS2 },
{ "J_AXIS3", QFJ_AXIS3 },
{ "J_AXIS4", QFJ_AXIS4 },
{ "J_AXIS5", QFJ_AXIS5 },
{ "J_AXIS6", QFJ_AXIS6 },
{ "J_AXIS7", QFJ_AXIS7 },
{ "J_AXIS8", QFJ_AXIS8 },
{ "J_AXIS9", QFJ_AXIS9 },
{ "J_AXIS10", QFJ_AXIS10 },
{ "J_AXIS11", QFJ_AXIS11 },
{ "J_AXIS12", QFJ_AXIS12 },
{ "J_AXIS13", QFJ_AXIS13 },
{ "J_AXIS14", QFJ_AXIS14 },
{ "J_AXIS15", QFJ_AXIS15 },
{ "J_AXIS16", QFJ_AXIS16 },
{ "J_AXIS17", QFJ_AXIS17 },
{ "J_AXIS18", QFJ_AXIS18 },
{ "J_AXIS19", QFJ_AXIS19 },
{ "J_AXIS20", QFJ_AXIS20 },
{ "J_AXIS21", QFJ_AXIS21 },
{ "J_AXIS22", QFJ_AXIS22 },
{ "J_AXIS23", QFJ_AXIS23 },
{ "J_AXIS24", QFJ_AXIS24 },
{ "J_AXIS25", QFJ_AXIS25 },
{ "J_AXIS26", QFJ_AXIS26 },
{ "J_AXIS27", QFJ_AXIS27 },
{ "J_AXIS28", QFJ_AXIS28 },
{ "J_AXIS29", QFJ_AXIS29 },
{ "J_AXIS30", QFJ_AXIS30 },
{ "J_AXIS31", QFJ_AXIS31 },
{ "J_AXIS32", QFJ_AXIS32 },
{NULL, 0}
};
@ -599,7 +632,7 @@ Key_Console (knum_t key, short unicode)
the given string. Single ascii characters return themselves, while
the QFK_* names are matched up.
*/
static int
VISIBLE int
Key_StringToKeynum (const char *str)
{
keyname_t *kn;

View file

@ -35,6 +35,7 @@
#include "QF/cvar.h"
#include "QF/draw.h"
#include "QF/input.h"
#include "QF/joystick.h"
#include "QF/keys.h"
#include "QF/msg.h"
#include "QF/qfplist.h"
@ -113,6 +114,7 @@ CL_WriteConfiguration (void)
Key_WriteBindings (f);
Cvar_WriteVariables (f);
Joy_WriteBindings (f);
Qclose (f);
}