From 5557bf0b09f2e98d3a56727b0158a81af0ae3afa Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Thu, 11 Nov 2021 15:51:47 +0900 Subject: [PATCH] [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. --- include/QF/input/binding.h | 30 +++++++--- include/QF/input/imt.h | 3 +- libs/input/in_axis.c | 6 ++ libs/input/in_binding.c | 68 ++++++++++++++++++---- libs/input/in_imt.c | 116 ++++++++++++++++++++++++++++++++++--- 5 files changed, 198 insertions(+), 25 deletions(-) diff --git a/include/QF/input/binding.h b/include/QF/input/binding.h index 1b8fadcc0..aeb06468a 100644 --- a/include/QF/input/binding.h +++ b/include/QF/input/binding.h @@ -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); diff --git a/include/QF/input/imt.h b/include/QF/input/imt.h index fc5192d9a..6d92576a1 100644 --- a/include/QF/input/imt.h +++ b/include/QF/input/imt.h @@ -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); diff --git a/libs/input/in_axis.c b/libs/input/in_axis.c index f945e2590..85489b8b7 100644 --- a/libs/input/in_axis.c +++ b/libs/input/in_axis.c @@ -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) { diff --git a/libs/input/in_binding.c b/libs/input/in_binding.c index bd7b1206a..6705de222 100644 --- a/libs/input/in_binding.c +++ b/libs/input/in_binding.c @@ -38,10 +38,13 @@ # include #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); diff --git a/libs/input/in_imt.c b/libs/input/in_imt.c index b47ae1b8a..ea7a7ebbf 100644 --- a/libs/input/in_imt.c +++ b/libs/input/in_imt.c @@ -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); }