[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:
Bill Currie 2021-11-11 15:51:47 +09:00
parent f77b4199c4
commit 5557bf0b09
5 changed files with 198 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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

View File

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