From 1ef99a9130d9c9f79de4cfe3b8eef2d93f49b991 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Tue, 23 Nov 2021 22:04:17 +0900 Subject: [PATCH] [input] Separate absolute and relative axis inputs Combining absolute and relative inputs at the binding does not work well because absolute inputs generally update only when the physical input updates, so clearing the axis input each frame results in a brief pulse from the physical input, but relative inputs must be cleared each frame (where frame here is each time the axis is read) but must accumulate the relative updates between frames. Other than the axis mode being incorrect, this seems to work quite nicely. --- include/QF/input/binding.h | 74 ++++++++++++++++++++++++++++++++++++++ libs/input/in_imt.c | 10 ++---- nq/source/cl_input.c | 19 ++++------ qw/source/cl_input.c | 19 ++++------ 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/include/QF/input/binding.h b/include/QF/input/binding.h index 5c82d08c2..ddcb4c35a 100644 --- a/include/QF/input/binding.h +++ b/include/QF/input/binding.h @@ -31,6 +31,8 @@ #ifndef __QFCC__ +#include "QF/mathlib.h" + /*** Recipe for converting an axis to a floating point value. Absolute axes are converted to the 0..1 range for unbalanced axes, and @@ -81,6 +83,8 @@ typedef enum { typedef struct in_axis_s { float value; ///< converted value of the axis in_axis_mode mode; ///< method used for updating the destination + float abs_input; ///< input from an absolute axis (eg, joystick) + float rel_input; ///< input from a relative axis (eg, mouse) const char *name; const char *description; } in_axis_t; @@ -155,6 +159,7 @@ GNU89INLINE inline float IN_ButtonState (in_button_t *button); Both steady-state on, and brief clicks are detected. + \param button Pointer to the button being tested. \return True if the button is currently held or was pulsed on in the last frame. \note The edge transitions are cleared, so for each frame, this @@ -167,6 +172,7 @@ GNU89INLINE inline int IN_ButtonPressed (in_button_t *button); Valid only if the button is still released. A pulsed off does not count as being released as the button is still held. + \param button Pointer to the button being tested. \return True if the button is currently released and the release was in the last frame. \note The edge transitions are cleared, so for each frame, this @@ -174,6 +180,41 @@ GNU89INLINE inline int IN_ButtonPressed (in_button_t *button); */ GNU89INLINE inline int IN_ButtonReleased (in_button_t *button); +/*** Update the axis value based on its mode and clear its relative input. + + The absolute and relative inputs are separate because absolute inputs + usually get written when the input actually changes (and thus must not + be cleared each frame), while relative inputs indicate a per-frame delta + and thus must be cleared each frame. + + \param axis Pointer to the axis being updated. + \return The resulting output value of the axis. + \note The relative input (\a rel_input) is zeroed. +*/ +GNU89INLINE inline float IN_UpdateAxis (in_axis_t *axis); + +/*** Update and clamp the axis value (see IN_UpdateAxis()) + + Like IN_UpdateAxis(), but clamps the final output to the specified range. + This is most useful for \a ina_accumulate axes, but can be used to ensure + \a ina_set axes never exceed a given range. + + The absolute and relative inputs are separate because absolute inputs + usually get written when the input actually changes (and thus must not + be cleared each frame), while relative inputs indicate a per-frame delta + and thus must be cleared each frame. + + \param axis Pointer to the axis being updated. + \param minval The minimum value to which the axis output will be clamped. + \param minval The minimum value to which the axis output will be clamped. + \return The resulting output value of the axis. + \note The relative input (\a rel_input) is zeroed. The absolute + input is not affected by the clamping, only the output + \a value. +*/ +GNU89INLINE inline float IN_ClampAxis (in_axis_t *axis, + float minval, float maxval); + #ifndef IMPLEMENT_INPUT_Funcs GNU89INLINE inline #else @@ -227,6 +268,39 @@ IN_ButtonReleased (in_button_t *button) return (state & (inb_down | inb_edge_up)) == inb_edge_up; } +#ifndef IMPLEMENT_INPUT_Funcs +GNU89INLINE inline +#else +VISIBLE +#endif +float +IN_UpdateAxis (in_axis_t *axis) +{ + switch (axis->mode) { + case ina_set: + axis->value = axis->abs_input + axis->rel_input; + break; + case ina_accumulate: + axis->value += axis->abs_input + axis->rel_input; + break; + } + axis->rel_input = 0; + return axis->value; +} + +#ifndef IMPLEMENT_INPUT_Funcs +GNU89INLINE inline +#else +VISIBLE +#endif +float +IN_ClampAxis (in_axis_t *axis, float minval, float maxval) +{ + IN_UpdateAxis (axis); + axis->value = bound (minval, axis->value, maxval); + return axis->value; +} + void IN_ButtonAction (in_button_t *buttin, int id, int pressed); int IN_RegisterButton (in_button_t *button); diff --git a/libs/input/in_imt.c b/libs/input/in_imt.c index 4417126f3..828b3f36e 100644 --- a/libs/input/in_imt.c +++ b/libs/input/in_imt.c @@ -429,6 +429,7 @@ IMT_ProcessAxis (int axis, int value) if (recipe->curve != 1) { output = powf (output, recipe->curve); } + a->axis->rel_input += output; } else { int input = bound (minval, value, maxval); int range = maxval - minval; @@ -458,14 +459,7 @@ IMT_ProcessAxis (int axis, int value) 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; + a->axis->abs_input = output; } return true; } diff --git a/nq/source/cl_input.c b/nq/source/cl_input.c index 021b73bed..75029ae83 100644 --- a/nq/source/cl_input.c +++ b/nq/source/cl_input.c @@ -337,19 +337,12 @@ CL_BaseMove (usercmd_t *cmd) } */ - cmd->forwardmove -= viewdelta_position_forward.value * m_forward->value; - cmd->sidemove += viewdelta_position_side.value * m_side->value; - cmd->upmove -= viewdelta_position_up.value; - cl.viewstate.angles[PITCH] -= viewdelta_angles_pitch.value * m_pitch->value; - cl.viewstate.angles[YAW] -= viewdelta_angles_yaw.value * m_yaw->value; - cl.viewstate.angles[ROLL] -= viewdelta_angles_roll.value * m_pitch->value; - - viewdelta_angles_pitch.value = 0; - viewdelta_angles_yaw.value = 0; - viewdelta_angles_roll.value = 0; - viewdelta_position_forward.value = 0; - viewdelta_position_side.value = 0; - viewdelta_position_up.value = 0; + cmd->forwardmove -= IN_UpdateAxis (&viewdelta_position_forward) * m_forward->value; + cmd->sidemove += IN_UpdateAxis (&viewdelta_position_side) * m_side->value; + cmd->upmove -= IN_UpdateAxis (&viewdelta_position_up); + cl.viewstate.angles[PITCH] -= IN_UpdateAxis (&viewdelta_angles_pitch) * m_pitch->value; + cl.viewstate.angles[YAW] -= IN_UpdateAxis (&viewdelta_angles_yaw) * m_yaw->value; + cl.viewstate.angles[ROLL] -= IN_UpdateAxis (&viewdelta_angles_roll) * m_pitch->value; if (freelook && !(in_strafe.state & inb_down)) { cl.viewstate.angles[PITCH] diff --git a/qw/source/cl_input.c b/qw/source/cl_input.c index 77a799bef..513e5594d 100644 --- a/qw/source/cl_input.c +++ b/qw/source/cl_input.c @@ -354,19 +354,12 @@ CL_BaseMove (usercmd_t *cmd) } */ - cmd->forwardmove -= viewdelta_position_forward.value * m_forward->value; - cmd->sidemove += viewdelta_position_side.value * m_side->value; - cmd->upmove -= viewdelta_position_up.value; - cl.viewstate.angles[PITCH] -= viewdelta_angles_pitch.value * m_pitch->value; - cl.viewstate.angles[YAW] -= viewdelta_angles_yaw.value * m_yaw->value; - cl.viewstate.angles[ROLL] -= viewdelta_angles_roll.value * m_pitch->value; - - viewdelta_angles_pitch.value = 0; - viewdelta_angles_yaw.value = 0; - viewdelta_angles_roll.value = 0; - viewdelta_position_forward.value = 0; - viewdelta_position_side.value = 0; - viewdelta_position_up.value = 0; + cmd->forwardmove -= IN_UpdateAxis (&viewdelta_position_forward) * m_forward->value; + cmd->sidemove += IN_UpdateAxis (&viewdelta_position_side) * m_side->value; + cmd->upmove -= IN_UpdateAxis (&viewdelta_position_up); + cl.viewstate.angles[PITCH] -= IN_UpdateAxis (&viewdelta_angles_pitch) * m_pitch->value; + cl.viewstate.angles[YAW] -= IN_UpdateAxis (&viewdelta_angles_yaw) * m_yaw->value; + cl.viewstate.angles[ROLL] -= IN_UpdateAxis (&viewdelta_angles_roll) * m_pitch->value; if (freelook && !(in_strafe.state & inb_down)) { cl.viewstate.angles[PITCH]