mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-22 10:21:21 +00:00
[input] Implement axis binding
Each axis binding has its own recipe (meaning the same input axis can be interpreted differently for each binding) Recipes are specified with field=value pairs after the axis name. Valid fields are minzone, maxzone, deadzone, curve and scale, with deadzone doubling as a balanced/unbalanced flag. The default recipe has no zones, is balanced, and curve and scale are 1.
This commit is contained in:
parent
f77b4199c4
commit
5557bf0b09
5 changed files with 198 additions and 25 deletions
|
@ -33,18 +33,36 @@
|
|||
|
||||
/*** Recipe for converting an axis to a floating point value.
|
||||
|
||||
Recipes apply only to absolute axes.
|
||||
Absolute axes are converted to the 0..1 range for unbalanced axes, and
|
||||
the -1..1 range for balanced axes, and then scaled.
|
||||
|
||||
Relative axes are simply converted to floating point and scaled as they
|
||||
have no fixed limits.
|
||||
|
||||
Relative axes should have \a minzone and \a maxzone set to 0, or weird
|
||||
things will happen.
|
||||
|
||||
\a min and \a max normally come from the system. \a min and \a max being
|
||||
equal indicates the axis is relative.
|
||||
|
||||
\a deadzone applies only to balanced axes, thus it doubles as a flag
|
||||
for balanced (>= 0) or unbalanced (< 0).
|
||||
for balanced (>= 0) or unbalanced (< 0). However, relative axes are always
|
||||
balanced and so \a deadzone < 0 is the same as 0 for relative axes.
|
||||
|
||||
\a curve is applied after the input has been converted
|
||||
\a curve is applied after the input has been converted to a float, and the
|
||||
0..1 or -1..1 ranges for absolute axes.
|
||||
|
||||
\a scale is applied after \a curve for absolute axes, but before \a curve
|
||||
for relative axes.
|
||||
*/
|
||||
typedef struct in_recipe_s {
|
||||
int min; ///< Axis minimum value (from system)
|
||||
int max; ///< Axis maximum value (from system)
|
||||
int minzone; ///< Size of deadzone near axis minimum
|
||||
int maxzone; ///< Size of deadzone near axis maximum
|
||||
int deadzone; ///< Size of deadzone near axis center (balanced)
|
||||
float curve; ///< Power factor for absolute axes
|
||||
float scale; ///< Final scale factor
|
||||
} in_recipe_t;
|
||||
|
||||
typedef enum {
|
||||
|
@ -97,10 +115,7 @@ typedef struct in_button_s {
|
|||
} in_button_t;
|
||||
|
||||
typedef struct in_axisbinding_s {
|
||||
union {
|
||||
in_recipe_t *recipe;///< for absolute axes
|
||||
float scale; ///< for relative axes
|
||||
};
|
||||
in_recipe_t *recipe;
|
||||
in_axis_t *axis;
|
||||
} in_axisbinding_t;
|
||||
|
||||
|
@ -217,6 +232,7 @@ void IN_ButtonAction (in_button_t *buttin, int id, int pressed);
|
|||
int IN_RegisterButton (in_button_t *button);
|
||||
int IN_RegisterAxis (in_axis_t *axis);
|
||||
in_button_t *IN_FindButton (const char *name);
|
||||
in_axis_t *IN_FindAxis (const char *name);
|
||||
|
||||
void IN_Binding_Init (void);
|
||||
|
||||
|
|
|
@ -65,7 +65,8 @@ void IMT_SetContextCbuf (int ctx, struct cbuf_s *cbuf);
|
|||
imt_t *IMT_FindIMT (const char *name);
|
||||
int IMT_CreateIMT (int context, const char *imt_name,
|
||||
const char *chain_imt_name);
|
||||
void IMT_BindAxis (imt_t *imt, int axis, const char *binding);
|
||||
void IMT_BindAxis (imt_t *imt, int axis_num, in_axis_t *axis,
|
||||
const in_recipe_t *recipe);
|
||||
void IMT_BindButton (imt_t *imt, int button, const char *binding);
|
||||
qboolean IMT_ProcessAxis (int axis, int value);
|
||||
qboolean IMT_ProcessButton (int button, int state);
|
||||
|
|
|
@ -69,6 +69,12 @@ IN_RegisterAxis (in_axis_t *axis)
|
|||
return 1;
|
||||
}
|
||||
|
||||
VISIBLE in_axis_t *
|
||||
IN_FindAxis (const char *name)
|
||||
{
|
||||
return Hash_Find (axis_tab, name);
|
||||
}
|
||||
|
||||
static void __attribute__((constructor))
|
||||
in_axis_init (void)
|
||||
{
|
||||
|
|
|
@ -38,10 +38,13 @@
|
|||
# include <strings.h>
|
||||
#endif
|
||||
|
||||
#include "QF/cexpr.h"
|
||||
#include "QF/cmd.h"
|
||||
#include "QF/cmem.h"
|
||||
#include "QF/hash.h"
|
||||
#include "QF/heapsort.h"
|
||||
#include "QF/input.h"
|
||||
#include "QF/plist.h"
|
||||
#include "QF/progs.h" // for PR_RESMAP
|
||||
#include "QF/sys.h"
|
||||
|
||||
|
@ -67,14 +70,14 @@ devid_cmp (const void *a, const void *b)
|
|||
return *(const int *)a - *(const int *)b;
|
||||
}
|
||||
|
||||
static int *
|
||||
static int * __attribute__ ((pure))
|
||||
in_find_devid (int devid)
|
||||
{
|
||||
return bsearch (&devid, known_devices.a, known_devices.size,
|
||||
sizeof (int), devid_cmp);
|
||||
}
|
||||
|
||||
static in_devbindings_t *
|
||||
static in_devbindings_t * __attribute__ ((pure))
|
||||
in_binding_find_connection (const char *devname, const char *id)
|
||||
{
|
||||
in_devbindings_t *db;
|
||||
|
@ -254,7 +257,7 @@ in_keyhelp_event_handler (const IE_event_t *ie_event, void *unused)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static in_devbindings_t *
|
||||
static in_devbindings_t * __attribute__ ((pure))
|
||||
in_binding_find_device (const char *name)
|
||||
{
|
||||
in_devbindings_t *db;
|
||||
|
@ -264,13 +267,14 @@ in_binding_find_device (const char *name)
|
|||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return db;
|
||||
}
|
||||
|
||||
static void
|
||||
in_bind_f (void)
|
||||
{
|
||||
if (Cmd_Argc () < 6) {
|
||||
int argc = Cmd_Argc ();
|
||||
if (argc < 6) {
|
||||
Sys_Printf ("in_bind imt device type number binding...\n");
|
||||
Sys_Printf (" imt: the name of the input mapping table in which the"
|
||||
" intput will be bound\n");
|
||||
|
@ -294,8 +298,6 @@ in_bind_f (void)
|
|||
const char *dev_name = Cmd_Argv (2);
|
||||
const char *type = Cmd_Argv (3);
|
||||
const char *number = Cmd_Argv (4);
|
||||
// the rest of the command line is the binding
|
||||
const char *binding = Cmd_Args (5);
|
||||
|
||||
imt_t *imt = IMT_FindIMT (imt_name);
|
||||
in_devbindings_t *dev = in_binding_find_device (dev_name);
|
||||
|
@ -321,8 +323,54 @@ in_bind_f (void)
|
|||
if (dev->axis_imt_id == -1) {
|
||||
dev->axis_imt_id = IMT_GetAxisBlock (dev->num_axes);
|
||||
}
|
||||
IMT_BindAxis (imt, dev->axis_imt_id + num, binding);
|
||||
const char *axis_name = Cmd_Argv (5);
|
||||
in_axis_t *axis = IN_FindAxis (axis_name);
|
||||
if (!axis) {
|
||||
Sys_Printf ("unknown axis: %s\n", axis_name);
|
||||
return;
|
||||
}
|
||||
in_axisinfo_t *axisinfo = &dev->axis_info[num];
|
||||
in_recipe_t recipe = {
|
||||
.min = axisinfo->min,
|
||||
.max = axisinfo->max,
|
||||
.curve = 1,
|
||||
.scale = 1,
|
||||
};
|
||||
exprsym_t var_syms[] = {
|
||||
{"minzone", &cexpr_int, &recipe.minzone},
|
||||
{"maxzone", &cexpr_int, &recipe.maxzone},
|
||||
{"deadzone", &cexpr_int, &recipe.deadzone},
|
||||
{"curve", &cexpr_float, &recipe.curve},
|
||||
{"scale", &cexpr_float, &recipe.scale},
|
||||
{}
|
||||
};
|
||||
exprtab_t vars_tab = { var_syms, 0 };
|
||||
exprctx_t exprctx = {
|
||||
.external_variables = &vars_tab,
|
||||
.memsuper = new_memsuper (),
|
||||
.messages = PL_NewArray (),
|
||||
};
|
||||
cexpr_init_symtab (&vars_tab, &exprctx);
|
||||
|
||||
int i;
|
||||
for (i = 6; i < argc; i++) {
|
||||
const char *arg = Cmd_Argv (i);
|
||||
if (!cexpr_eval_string (arg, &exprctx)) {
|
||||
PL_Message (exprctx.messages, 0, "error parsing recipe: %s",
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == argc) {
|
||||
IMT_BindAxis (imt, dev->axis_imt_id + num, axis, &recipe);
|
||||
}
|
||||
Hash_DelTable (vars_tab.tab);
|
||||
PL_Free (exprctx.messages);
|
||||
delete_memsuper (exprctx.memsuper);
|
||||
} else {
|
||||
// the rest of the command line is the binding
|
||||
const char *binding = Cmd_Args (5);
|
||||
|
||||
if (*end || num < 0 || num >= dev->num_buttons) {
|
||||
Sys_Printf ("invalid button number: %s\n", number);
|
||||
return;
|
||||
|
@ -376,7 +424,7 @@ in_unbind_f (void)
|
|||
Sys_Printf ("invalid axis number: %s\n", number);
|
||||
return;
|
||||
}
|
||||
IMT_BindAxis (imt, dev->axis_imt_id + num, 0);
|
||||
IMT_BindAxis (imt, dev->axis_imt_id + num, 0, 0);
|
||||
} else {
|
||||
if (*end || num < 0 || num >= dev->num_buttons) {
|
||||
Sys_Printf ("invalid button number: %s\n", number);
|
||||
|
@ -402,7 +450,7 @@ in_clear_f (void)
|
|||
continue;
|
||||
}
|
||||
for (size_t ind = 0; ind < imt->axis_bindings.size; ind++) {
|
||||
IMT_BindAxis (imt, ind, 0);
|
||||
IMT_BindAxis (imt, ind, 0, 0);
|
||||
}
|
||||
for (size_t ind = 0; ind < imt->button_bindings.size; ind++) {
|
||||
IMT_BindButton (imt, ind, 0);
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
#include "QF/cmd.h"
|
||||
#include "QF/cmem.h"
|
||||
#include "QF/hash.h"
|
||||
#include "QF/input.h"
|
||||
#include "QF/mathlib.h"
|
||||
#include "QF/sys.h"
|
||||
#include "QF/va.h"
|
||||
|
||||
|
@ -73,6 +75,40 @@ static in_contextset_t in_contexts = DARRAY_STATIC_INIT (8);
|
|||
static size_t imt_current_context;
|
||||
|
||||
static memsuper_t *binding_mem;
|
||||
static hashtab_t *recipe_tab;
|
||||
|
||||
static in_recipe_t *
|
||||
alloc_recipe (void)
|
||||
{
|
||||
return cmemalloc (binding_mem, sizeof (in_recipe_t));
|
||||
}
|
||||
|
||||
static void
|
||||
free_recipe (in_recipe_t *recipe)
|
||||
{
|
||||
if (!recipe) {
|
||||
return;
|
||||
}
|
||||
cmemfree (binding_mem, recipe);
|
||||
}
|
||||
|
||||
static void
|
||||
recipe_free (void *recipe, void *data)
|
||||
{
|
||||
free_recipe (recipe);
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
recipe_get_hash (const void *recipe, void *data)
|
||||
{
|
||||
return Hash_Buffer (recipe, sizeof (in_recipe_t));
|
||||
}
|
||||
|
||||
static int
|
||||
recipe_compare (const void *a, const void *b, void *data)
|
||||
{
|
||||
return !memcmp (a, b, sizeof (in_recipe_t));
|
||||
}
|
||||
|
||||
static in_axisbinding_t *
|
||||
alloc_axis_binding (void)
|
||||
|
@ -160,8 +196,9 @@ imt_get_button_block (int count)
|
|||
int
|
||||
IMT_GetAxisBlock (int num_axes)
|
||||
{
|
||||
int base = imt_get_axis_block (num_axes);
|
||||
imt_block_t *block = imt_get_block (&axis_blocks);
|
||||
block->base = imt_get_axis_block (num_axes);
|
||||
block->base = base;
|
||||
block->count = num_axes;
|
||||
return block - axis_blocks.a;
|
||||
}
|
||||
|
@ -169,8 +206,9 @@ IMT_GetAxisBlock (int num_axes)
|
|||
int
|
||||
IMT_GetButtonBlock (int num_buttons)
|
||||
{
|
||||
int base = imt_get_button_block (num_buttons);
|
||||
imt_block_t *block = imt_get_block (&button_blocks);
|
||||
block->base = imt_get_button_block (num_buttons);
|
||||
block->base = base;
|
||||
block->count = num_buttons;
|
||||
return block - button_blocks.a;
|
||||
}
|
||||
|
@ -299,18 +337,22 @@ IMT_CreateIMT (int context, const char *imt_name, const char *chain_imt_name)
|
|||
}
|
||||
|
||||
void
|
||||
IMT_BindAxis (imt_t *imt, int axis, const char *binding)
|
||||
IMT_BindAxis (imt_t *imt, int axis_num, in_axis_t *axis,
|
||||
const in_recipe_t *recipe)
|
||||
{
|
||||
if ((size_t) axis >= imt->axis_bindings.size)
|
||||
if ((size_t) axis_num >= imt->axis_bindings.size)
|
||||
return;
|
||||
|
||||
in_axisbinding_t **bind = &imt->axis_bindings.a[axis];
|
||||
in_axisbinding_t **bind = &imt->axis_bindings.a[axis_num];
|
||||
free_axis_binding ((*bind));
|
||||
(*bind) = 0;
|
||||
if (binding) {
|
||||
if (axis && recipe) {
|
||||
in_axisbinding_t *a = alloc_axis_binding ();
|
||||
(*bind) = a;
|
||||
*a = (in_axisbinding_t) {};
|
||||
if (!(a->recipe = Hash_FindElement (recipe_tab, recipe))) {
|
||||
*(a->recipe = alloc_recipe ()) = *recipe;
|
||||
Hash_AddElement (recipe_tab, a->recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,6 +387,64 @@ IMT_ProcessAxis (int axis, int value)
|
|||
while (imt) {
|
||||
in_axisbinding_t *a = imt->axis_bindings.a[axis];
|
||||
if (a) {
|
||||
in_recipe_t *recipe = a->recipe;
|
||||
int relative = recipe->min == recipe->max;
|
||||
int deadzone = recipe->deadzone;
|
||||
int minval = recipe->min + recipe->minzone;
|
||||
int maxval = recipe->max - recipe->maxzone;
|
||||
int input = bound (minval, value, maxval);
|
||||
float output;
|
||||
if (relative) {
|
||||
if (deadzone > 0) {
|
||||
if (input > deadzone) {
|
||||
input -= deadzone;
|
||||
} else if (input < -deadzone) {
|
||||
input += deadzone;
|
||||
} else {
|
||||
input = 0;
|
||||
}
|
||||
}
|
||||
output = input * recipe->scale;
|
||||
if (recipe->curve != 1) {
|
||||
output = powf (output, recipe->curve);
|
||||
}
|
||||
} else {
|
||||
int range = maxval - minval;
|
||||
int zero = minval;
|
||||
if (recipe->deadzone >= 0) {
|
||||
// balanced axis: -1..1
|
||||
int center = (recipe->min + recipe->max + 1) / 2;
|
||||
minval += deadzone - center;
|
||||
maxval -= deadzone + center;
|
||||
input -= center;
|
||||
if (input < -deadzone) {
|
||||
input += deadzone;
|
||||
} else if (input > deadzone) {
|
||||
input -= deadzone;
|
||||
} else {
|
||||
input = 0;
|
||||
}
|
||||
if (center - minval > maxval - center) {
|
||||
range = center - minval;
|
||||
} else {
|
||||
range = maxval - center;
|
||||
}
|
||||
zero = 0;
|
||||
}
|
||||
output = (float)(input - zero) / (range);
|
||||
if (recipe->curve != 1) {
|
||||
output = powf (output, recipe->curve);
|
||||
}
|
||||
output *= recipe->scale;
|
||||
}
|
||||
switch (a->axis->mode) {
|
||||
case ina_set:
|
||||
a->axis->value = output;
|
||||
break;
|
||||
case ina_accumulate:
|
||||
a->axis->value += output;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
imt = imt->chain;
|
||||
|
@ -529,6 +629,8 @@ void
|
|||
IMT_Init (void)
|
||||
{
|
||||
binding_mem = new_memsuper ();
|
||||
recipe_tab = Hash_NewTable (61, 0, recipe_free, 0, 0);
|
||||
Hash_SetHashCompare (recipe_tab, recipe_get_hash, recipe_compare);
|
||||
for (imtcmd_t *cmd = imt_commands; cmd->name; cmd++) {
|
||||
Cmd_AddCommand (cmd->name, cmd->func, cmd->desc);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue