Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion code/camera/photomode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ void photo_mode_do_frame(float frame_time)
float bank = check_control_timef(BANK_LEFT) - check_control_timef(BANK_RIGHT);

int axis[Action::NUM_VALUES] = {0};
control_get_axes_readings(axis, flRealframetime);
control_get_axes_readings(axis, flRealframetime, false);
pitch += -f2fl(axis[Action::PITCH]);
heading += f2fl(axis[Action::HEADING]);
bank -= f2fl(axis[Action::BANK]);
Expand Down
141 changes: 112 additions & 29 deletions code/controlconfig/controlsconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2961,10 +2961,11 @@ int check_control(int id, int key)
}

/**
* Inverts the given raw axis value according to the action type
* Inverts the given raw axis value according to the input type
*
* @param[in] inv True for invert, False for noram
* @param[in] type Type of the axis value to invert, determines method of inversion
* @param[in] type Type of the input value (not of the action), determines method of inversion. Mouse deltas
* are always CC_TYPE_AXIS_REL
* @param[in,out] val raw axis value in, maybe inverted axis value out
*/
inline
Expand Down Expand Up @@ -2992,7 +2993,16 @@ void maybe_invert(bool inv, CC_type type, int &val)
}

/*!
* Scales, and maybe inverts, the input axis values
* Scale applied to mouse deltas, in fix units per mouse count
*/
static float mouse_axis_scale()
{
float scale = i2fl(Mouse_sensitivity) + 1.77f;
return scale * scale / 0.6f;
}

/*!
* Scales, and maybe inverts, the input axis values of a relative action
*
* @param[in] bind The control's binding to check
* @param[in] action index into axis_out of the action
Expand All @@ -3016,15 +3026,15 @@ void scale_invert(const CC_bind &bind,
const auto cid = bind.get_cid();
const auto btn = bind.get_btn();

factor = (float)Mouse_sensitivity + 1.77f;
factor = factor * factor / frame_time / 0.6f;
// mouse deltas become a rate, like the joystick's deflection
factor = mouse_axis_scale() / frame_time;

switch (cid) {
case CID_MOUSE:
if (!Use_mouse_to_fly) {
// Mouse is treated as mouse, get the axis values
dx = axis_in[MOUSE_ID][btn];
maybe_invert(bind.is_inverted(), type, dx);
maybe_invert(bind.is_inverted(), CC_TYPE_AXIS_REL, dx);
axis_out[action] += (int)((float)dx * factor);

} // else, Mouse is treated as joy, ignore and let CID_JOY0 case handle it on next call
Expand All @@ -3034,27 +3044,15 @@ void scale_invert(const CC_bind &bind,
if (Use_mouse_to_fly) {
// Mouse is treated as Joy0
dx = axis_in[MOUSE_ID][btn];
maybe_invert(bind.is_inverted(), type, dx);
maybe_invert(bind.is_inverted(), CC_TYPE_AXIS_REL, dx);
axis_out[action] += (int)((float)dx * factor);
}
FALLTHROUGH;

case CID_JOY1:
case CID_JOY2:
case CID_JOY3:
switch (type) {
case CC_TYPE_AXIS_ABS:
dx = joy_get_unscaled_reading(axis_in[cid][btn]);
break;

case CC_TYPE_AXIS_REL:
case CC_TYPE_AXIS_BTN_NEG:
case CC_TYPE_AXIS_BTN_POS:
default:
dx = joy_get_scaled_reading(axis_in[cid][btn]);
break;
}

dx = joy_get_scaled_reading(axis_in[cid][btn]);
maybe_invert(bind.is_inverted(), type, dx);
axis_out[action] += dx;
break;
Expand All @@ -3065,9 +3063,71 @@ void scale_invert(const CC_bind &bind,
}
}

void control_get_axes_readings(int *axis_v, float frame_time)
static abs_axis_state Abs_axis_state[Action::NUM_VALUES];
static CCB Abs_axis_bindings[Action::NUM_VALUES]; // the bindings each state was last updated with

void control_reset_axes()
{
for (auto &state : Abs_axis_state)
state.reset();
}

/*!
* Feeds the input of one binding of an absolute action into the action's state
*
* @param[in] bind The control's binding to check
* @param[in] source 0 for the first binding, 1 for the second
* @param[in] joy_connected[] Whether each joystick is connected
* @param[in] axis_in[][] Array of raw axis values
* @param[in,out] state The action's state
*/
static void update_abs_axis(const CC_bind &bind,
int source,
const bool (&joy_connected)[CID_JOY_MAX],
int (&axis_in)[CID_JOY_MAX + 1][JOY_NUM_AXES],
abs_axis_state &state)
{
const int MOUSE_ID = CID_JOY_MAX; // Joy axes go in front here, mouse gets tacked on the end
const auto cid = bind.get_cid();
const auto btn = bind.get_btn();
int dx = 0;

// The mouse reports deltas, so it nudges the value. Mouse is treated as Joy0 when flying with the mouse
if (cid == (Use_mouse_to_fly ? CID_JOY0 : CID_MOUSE))
{
dx = axis_in[MOUSE_ID][btn];
maybe_invert(bind.is_inverted(), CC_TYPE_AXIS_REL, dx);

if (dx != 0)
state.update_delta(i2fl(dx) * mouse_axis_scale());
}

// Joysticks report positions, so they set the value
switch (cid)
{
case CID_JOY0:
case CID_JOY1:
case CID_JOY2:
case CID_JOY3:
// a missing joystick reads as centered, which would look like a lever position
if (!joy_connected[cid])
break;

dx = joy_get_unscaled_reading(axis_in[cid][btn]);
maybe_invert(bind.is_inverted(), CC_TYPE_AXIS_ABS, dx);
state.update_position(source, dx);
break;

default:
// All others, ignore
break;
}
}

void control_get_axes_readings(int *axis_v, float frame_time, bool update_abs_axes)
{
int axe[CID_JOY_MAX + 1][JOY_NUM_AXES] = {{0}};
bool joy_connected[CID_JOY_MAX];
const int MOUSE_ID = CID_JOY_MAX; // Joy axes go in front here, mouse gets tacked on the end

Assert(axis_v != nullptr);
Expand All @@ -3079,7 +3139,7 @@ void control_get_axes_readings(int *axis_v, float frame_time)

// Read raw sticks.
for (short j = CID_JOY0; j < CID_JOY_MAX; ++j) {
joystick_read_raw_axis(j, JOY_NUM_AXES, axe[j]);
joy_connected[j] = joystick_read_raw_axis(j, JOY_NUM_AXES, axe[j]) != 0;
}

// Read raw mouse
Expand All @@ -3091,14 +3151,37 @@ void control_get_axes_readings(int *axis_v, float frame_time)

// Assume actions are all axis actions, no need to check
// Assumes all axes are uniquely bound to an action
// Process first
if (!item.first.empty()) {
scale_invert(item.first, action, item.type, frame_time, axe, axis_v);
}
if (item.type == CC_TYPE_AXIS_ABS) {
auto &state = Abs_axis_state[action];

if (update_abs_axes) {
// A rebound or inverted lever reports a new position without having moved
if (Abs_axis_bindings[action] != item) {
state.clear_anchors();
Abs_axis_bindings[action] = item;
}

// Process second.
if (!item.second.empty()) {
scale_invert(item.second, action, item.type, frame_time, axe, axis_v);
if (!item.first.empty()) {
update_abs_axis(item.first, 0, joy_connected, axe, state);
}

if (!item.second.empty()) {
update_abs_axis(item.second, 1, joy_connected, axe, state);
}
}

axis_v[action] = state.value;

} else {
// Process first
if (!item.first.empty()) {
scale_invert(item.first, action, item.type, frame_time, axe, axis_v);
}

// Process second.
if (!item.second.empty()) {
scale_invert(item.second, action, item.type, frame_time, axe, axis_v);
}
}

//Call Lua hooks
Expand Down
53 changes: 50 additions & 3 deletions code/controlconfig/controlsconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,46 @@ enum {
};
}

/*!
* @brief Current value of an absolute axis action, combined from all of its inputs
*
* @details Joystick axes report positions and mouse axes report deltas, so they can't simply be summed. Instead, the
* input that moved last wins: a joystick sets the value outright, and the mouse nudges it from wherever it is. A
* joystick that has lost control must move past TAKEOVER_THRESHOLD to regain it, so that an idle lever's jitter
* doesn't fight the other inputs. A joystick's first reading only takes control if no other joystick has reported or
* is in control.
*/
struct abs_axis_state
{
static constexpr int NUM_POSITION_SOURCES = 2; // first and second binding
static constexpr int TAKEOVER_THRESHOLD = F1_0 / 80; // 1.25%, matching the analog throttle's keyboard override, which is 1.5% of its 120% throttle range

int value; // 0 to F1_0
int active; // position source in control, or -1
int anchor[NUM_POSITION_SOURCES];
bool anchored[NUM_POSITION_SOURCES];

abs_axis_state();

void reset();

/*!
* @brief Makes every joystick report again, keeping the value and the source in control. For when the bindings change.
*/
void clear_anchors();

/*!
* @param[in] source 0 for the first binding, 1 for the second
* @param[in] pos position from 0 to F1_0, already inverted if necessary
*/
void update_position(int source, int pos);

/*!
* @param[in] delta change in value, already scaled and inverted if necessary
*/
void update_delta(float delta);
};

/*!
* Where the preset is located in memory
*/
Expand Down Expand Up @@ -983,13 +1023,20 @@ int check_control(int id, int key = -1);
*
* @param[in] frametime Frametime used to scale the mouse axes
* @param[out] axis_v Output array of all control axes, must have size of JOY_NUM_AXIS_ACTIONS
* @param[in] update_abs_axes Whether the inputs should change the absolute controls, or only report them
*
* @details
* * Clamps output to stay within +/-JOY_AXIS_RANGE.
* * Should multiple axes be bound to a control, their values are blended with a simple sum. This is to save a bit of
* processing for speed.
* * Should multiple axes be bound to a relative control, their values are blended with a simple sum. This is to save a
* bit of processing for speed.
* * Absolute controls are combined by ::abs_axis_state instead.
*/
void control_get_axes_readings(int *axis_v, float frame_time, bool update_abs_axes = true);

/**
* @brief Resets the state of the absolute axis actions. Called at mission start.
*/
void control_get_axes_readings(int *axis_v, float frame_time);
void control_reset_axes();

/**
* @brief Marks the given control (by IoActionId) as used
Expand Down
65 changes: 65 additions & 0 deletions code/controlconfig/controlsconfigcommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,71 @@ bool CCI::is_axis() {
}
}

abs_axis_state::abs_axis_state()
{
reset();
}

void abs_axis_state::reset()
{
value = F1_0 / 2;
active = -1;

clear_anchors();
}

void abs_axis_state::clear_anchors()
{
for (int i = 0; i < NUM_POSITION_SOURCES; ++i)
{
anchor[i] = 0;
anchored[i] = false;
}
}

void abs_axis_state::update_position(int source, int pos)
{
Assertion((source >= 0) && (source < NUM_POSITION_SOURCES), "Invalid position source %d!", source);

if (source != active)
{
if (!anchored[source])
{
// a new input doesn't take control from one that is in control or has already reported
bool other_reported = (active >= 0);
for (bool other_anchored : anchored)
{
if (other_anchored)
other_reported = true;
}

if (other_reported)
{
anchor[source] = pos;
anchored[source] = true;
return;
}
}
// an idle input doesn't take control
else if (abs(pos - anchor[source]) <= TAKEOVER_THRESHOLD)
return;
}

active = source;
value = pos;
anchor[source] = pos;
anchored[source] = true;
}

void abs_axis_state::update_delta(float delta)
{
float new_value = i2fl(value) + delta;
CLAMP(new_value, 0.0f, i2fl(F1_0));

active = -1;
value = fl2i(new_value);
}

CCI_builder::CCI_builder(SCP_vector<CCI>& _ControlConfig) : ControlConfig(_ControlConfig) {
ControlConfig.resize(CCFG_MAX);
};
Expand Down
1 change: 1 addition & 0 deletions code/mission/missionload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ bool mission_load(const char* filename_ext)
ml_update_recent_missions(filename_ext); // update recently played missions list (save the csg later)

control_reset_hook();
control_reset_axes();

init_hud();

Expand Down
Loading
Loading