From 4ae99d759657d12dc05e0ce40e96a918d673b3ea Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Fri, 1 Oct 2021 04:46:04 +0800 Subject: [PATCH] Add advanced deadzone configuration functions to the game controller class --- src/application.cpp | 3 + src/game/states/forage.cpp | 26 +++-- src/game/states/loading.cpp | 112 +++++++++++++++++++- src/input/control.cpp | 6 +- src/input/control.hpp | 20 ++-- src/input/game-controller.cpp | 188 +++++++++++++++++++++++++++++++++- src/input/game-controller.hpp | 90 +++++++++++++++- src/math/map.hpp | 43 ++++++++ src/math/math.hpp | 1 + 9 files changed, 456 insertions(+), 33 deletions(-) create mode 100644 src/math/map.hpp diff --git a/src/application.cpp b/src/application.cpp index 42f0223..acf0ebb 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -210,6 +210,9 @@ application::application(): keyboard->set_event_dispatcher(event_dispatcher); mouse = new input::mouse(); mouse->set_event_dispatcher(event_dispatcher); + + // Connect game controllers + translate_sdl_events(); // Setup frame scheduler frame_scheduler = new ::frame_scheduler(); diff --git a/src/game/states/forage.cpp b/src/game/states/forage.cpp index 57e40f1..a2fe7ec 100644 --- a/src/game/states/forage.cpp +++ b/src/game/states/forage.cpp @@ -271,22 +271,28 @@ void setup_controls(game::context* ctx) const float dolly_speed = 20.0f; const float truck_speed = dolly_speed; const float pedestal_speed = 30.0f; - float mouse_look_sensitivity = 1.0f; + float mouse_tilt_sensitivity = 1.0f; + float mouse_pan_sensitivity = 1.0f; bool mouse_invert_tilt = false; bool mouse_invert_pan = false; - float gamepad_look_sensitivity = 1.0f; + float gamepad_tilt_sensitivity = 1.0f; + float gamepad_pan_sensitivity = 1.0f; bool gamepad_invert_tilt = false; bool gamepad_invert_pan = false; - if (ctx->config->contains("mouse_look_sensitivity")) - mouse_look_sensitivity = math::radians((*ctx->config)["mouse_look_sensitivity"].get()); + if (ctx->config->contains("mouse_tilt_sensitivity")) + mouse_tilt_sensitivity = math::radians((*ctx->config)["mouse_tilt_sensitivity"].get()); + if (ctx->config->contains("mouse_pan_sensitivity")) + mouse_pan_sensitivity = math::radians((*ctx->config)["mouse_pan_sensitivity"].get()); if (ctx->config->contains("mouse_invert_tilt")) mouse_invert_tilt = math::radians((*ctx->config)["mouse_invert_tilt"].get()); if (ctx->config->contains("mouse_invert_pan")) mouse_invert_pan = math::radians((*ctx->config)["mouse_invert_pan"].get()); - if (ctx->config->contains("gamepad_look_sensitivity")) - gamepad_look_sensitivity = math::radians((*ctx->config)["gamepad_look_sensitivity"].get()); + if (ctx->config->contains("gamepad_tilt_sensitivity")) + gamepad_tilt_sensitivity = math::radians((*ctx->config)["gamepad_tilt_sensitivity"].get()); + if (ctx->config->contains("gamepad_pan_sensitivity")) + gamepad_pan_sensitivity = math::radians((*ctx->config)["gamepad_pan_sensitivity"].get()); if (ctx->config->contains("gamepad_invert_tilt")) gamepad_invert_tilt = math::radians((*ctx->config)["gamepad_invert_tilt"].get()); if (ctx->config->contains("gamepad_invert_pan")) @@ -296,10 +302,10 @@ void setup_controls(game::context* ctx) const input::control* move_fast = ctx->controls["move_fast"]; const input::control* mouse_rotate = ctx->controls["mouse_rotate"]; - float mouse_tilt_factor = mouse_look_sensitivity * (mouse_invert_tilt ? -1.0f : 1.0f); - float mouse_pan_factor = mouse_look_sensitivity * (mouse_invert_pan ? -1.0f : 1.0f); - float gamepad_tilt_factor = gamepad_look_sensitivity * (gamepad_invert_tilt ? -1.0f : 1.0f); - float gamepad_pan_factor = gamepad_look_sensitivity * (gamepad_invert_pan ? -1.0f : 1.0f); + float mouse_tilt_factor = mouse_tilt_sensitivity * (mouse_invert_tilt ? -1.0f : 1.0f); + float mouse_pan_factor = mouse_pan_sensitivity * (mouse_invert_pan ? -1.0f : 1.0f); + float gamepad_tilt_factor = gamepad_tilt_sensitivity * (gamepad_invert_tilt ? -1.0f : 1.0f); + float gamepad_pan_factor = gamepad_pan_sensitivity * (gamepad_invert_pan ? -1.0f : 1.0f); ctx->controls["dolly_forward"]->set_active_callback ( diff --git a/src/game/states/loading.cpp b/src/game/states/loading.cpp index cbfae52..fb85367 100644 --- a/src/game/states/loading.cpp +++ b/src/game/states/loading.cpp @@ -56,6 +56,8 @@ namespace loading { /// Creates or loads control configuration static void load_controls(game::context* ctx); +static input::game_controller_response_curve parse_response_curve(const std::string& curve); + /// Creates the universe and solar system. static void cosmogenesis(game::context* ctx); @@ -128,6 +130,11 @@ void load_controls(game::context* ctx) // Allocate known controls ctx->controls["toggle_fullscreen"] = new input::control(); ctx->controls["screenshot"] = new input::control(); + ctx->controls["menu_up"] = new input::control(); + ctx->controls["menu_down"] = new input::control(); + ctx->controls["menu_left"] = new input::control(); + ctx->controls["menu_right"] = new input::control(); + ctx->controls["menu_select"] = new input::control(); ctx->controls["menu_back"] = new input::control(); ctx->controls["dolly_forward"] = new input::control(); ctx->controls["dolly_backward"] = new input::control(); @@ -148,6 +155,13 @@ void load_controls(game::context* ctx) ctx->controls["tilt_down_mouse"] = new input::control(); ctx->controls["use_tool"] = new input::control(); + // Set activation threshold for menu navigation controls to mitigate drifting game controller axes + const float menu_activation_threshold = 0.1f; + ctx->controls["menu_up"]->set_activation_threshold(menu_activation_threshold); + ctx->controls["menu_down"]->set_activation_threshold(menu_activation_threshold); + ctx->controls["menu_left"]->set_activation_threshold(menu_activation_threshold); + ctx->controls["menu_right"]->set_activation_threshold(menu_activation_threshold); + // Get keyboard and mouse devices input::keyboard* keyboard = ctx->app->get_keyboard(); input::mouse* mouse = ctx->app->get_mouse(); @@ -365,10 +379,93 @@ void load_controls(game::context* ctx) } } - // Set all control deadzones to 0.15 - for (auto control: ctx->controls) + // Set gamepad deadzones + float gamepad_leftx_activation_min = 0.0f; + float gamepad_leftx_activation_max = 0.0f; + float gamepad_lefty_activation_min = 0.0f; + float gamepad_lefty_activation_max = 0.0f; + float gamepad_rightx_activation_min = 0.0f; + float gamepad_rightx_activation_max = 0.0f; + float gamepad_righty_activation_min = 0.0f; + float gamepad_righty_activation_max = 0.0f; + float gamepad_lefttrigger_activation_min = 0.0f; + float gamepad_lefttrigger_activation_max = 0.0f; + float gamepad_righttrigger_activation_min = 0.0f; + float gamepad_righttrigger_activation_max = 0.0f; + bool gamepad_left_deadzone_cross = true; + bool gamepad_right_deadzone_cross = true; + float gamepad_left_deadzone_roundness = 0.0f; + float gamepad_right_deadzone_roundness = 0.0f; + input::game_controller_response_curve gamepad_leftx_response_curve = input::game_controller_response_curve::linear; + input::game_controller_response_curve gamepad_lefty_response_curve = input::game_controller_response_curve::linear; + input::game_controller_response_curve gamepad_rightx_response_curve = input::game_controller_response_curve::linear; + input::game_controller_response_curve gamepad_righty_response_curve = input::game_controller_response_curve::linear; + input::game_controller_response_curve gamepad_lefttrigger_response_curve = input::game_controller_response_curve::linear; + input::game_controller_response_curve gamepad_righttrigger_response_curve = input::game_controller_response_curve::linear; + + if (ctx->config->contains("gamepad_leftx_activation_min")) + gamepad_leftx_activation_min = (*ctx->config)["gamepad_leftx_activation_min"].get(); + if (ctx->config->contains("gamepad_leftx_activation_max")) + gamepad_leftx_activation_max = (*ctx->config)["gamepad_leftx_activation_max"].get(); + if (ctx->config->contains("gamepad_lefty_activation_min")) + gamepad_lefty_activation_min = (*ctx->config)["gamepad_lefty_activation_min"].get(); + if (ctx->config->contains("gamepad_lefty_activation_max")) + gamepad_lefty_activation_max = (*ctx->config)["gamepad_lefty_activation_max"].get(); + if (ctx->config->contains("gamepad_rightx_activation_min")) + gamepad_rightx_activation_min = (*ctx->config)["gamepad_rightx_activation_min"].get(); + if (ctx->config->contains("gamepad_rightx_activation_max")) + gamepad_rightx_activation_max = (*ctx->config)["gamepad_rightx_activation_max"].get(); + if (ctx->config->contains("gamepad_righty_activation_min")) + gamepad_righty_activation_min = (*ctx->config)["gamepad_righty_activation_min"].get(); + if (ctx->config->contains("gamepad_righty_activation_max")) + gamepad_righty_activation_max = (*ctx->config)["gamepad_righty_activation_max"].get(); + if (ctx->config->contains("gamepad_lefttrigger_activation_min")) + gamepad_lefttrigger_activation_min = (*ctx->config)["gamepad_lefttrigger_activation_min"].get(); + if (ctx->config->contains("gamepad_lefttrigger_activation_max")) + gamepad_lefttrigger_activation_max = (*ctx->config)["gamepad_lefttrigger_activation_max"].get(); + if (ctx->config->contains("gamepad_righttrigger_activation_min")) + gamepad_righttrigger_activation_min = (*ctx->config)["gamepad_righttrigger_activation_min"].get(); + if (ctx->config->contains("gamepad_righttrigger_activation_max")) + gamepad_righttrigger_activation_max = (*ctx->config)["gamepad_righttrigger_activation_max"].get(); + if (ctx->config->contains("gamepad_left_deadzone_cross")) + gamepad_left_deadzone_cross = (*ctx->config)["gamepad_left_deadzone_cross"].get(); + if (ctx->config->contains("gamepad_right_deadzone_cross")) + gamepad_right_deadzone_cross = (*ctx->config)["gamepad_right_deadzone_cross"].get(); + if (ctx->config->contains("gamepad_left_deadzone_roundness")) + gamepad_left_deadzone_roundness = (*ctx->config)["gamepad_left_deadzone_roundness"].get(); + if (ctx->config->contains("gamepad_right_deadzone_roundness")) + gamepad_right_deadzone_roundness = (*ctx->config)["gamepad_right_deadzone_roundness"].get(); + if (ctx->config->contains("gamepad_leftx_response_curve")) + gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get()); + if (ctx->config->contains("gamepad_leftx_response_curve")) + gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get()); + if (ctx->config->contains("gamepad_rightx_response_curve")) + gamepad_rightx_response_curve = parse_response_curve((*ctx->config)["gamepad_rightx_response_curve"].get()); + if (ctx->config->contains("gamepad_leftx_response_curve")) + gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get()); + if (ctx->config->contains("gamepad_lefttrigger_response_curve")) + gamepad_lefttrigger_response_curve = parse_response_curve((*ctx->config)["gamepad_lefttrigger_response_curve"].get()); + if (ctx->config->contains("gamepad_righttrigger_response_curve")) + gamepad_righttrigger_response_curve = parse_response_curve((*ctx->config)["gamepad_righttrigger_response_curve"].get()); + + for (input::game_controller* gamepad: ctx->app->get_game_controllers()) { - control.second->set_deadzone(0.15f); + gamepad->set_activation_threshold(input::game_controller_axis::left_x, gamepad_leftx_activation_min, gamepad_leftx_activation_max); + gamepad->set_activation_threshold(input::game_controller_axis::left_y, gamepad_lefty_activation_min, gamepad_lefty_activation_max); + gamepad->set_activation_threshold(input::game_controller_axis::right_x, gamepad_rightx_activation_min, gamepad_rightx_activation_max); + gamepad->set_activation_threshold(input::game_controller_axis::right_y, gamepad_righty_activation_min, gamepad_righty_activation_max); + gamepad->set_activation_threshold(input::game_controller_axis::left_trigger, gamepad_lefttrigger_activation_min, gamepad_lefttrigger_activation_max); + gamepad->set_activation_threshold(input::game_controller_axis::right_trigger, gamepad_righttrigger_activation_min, gamepad_righttrigger_activation_max); + gamepad->set_left_deadzone_cross(gamepad_left_deadzone_cross); + gamepad->set_right_deadzone_cross(gamepad_right_deadzone_cross); + gamepad->set_left_deadzone_roundness(gamepad_left_deadzone_roundness); + gamepad->set_right_deadzone_roundness(gamepad_right_deadzone_roundness); + gamepad->set_response_curve(input::game_controller_axis::left_x, gamepad_leftx_response_curve); + gamepad->set_response_curve(input::game_controller_axis::left_y, gamepad_lefty_response_curve); + gamepad->set_response_curve(input::game_controller_axis::right_x, gamepad_rightx_response_curve); + gamepad->set_response_curve(input::game_controller_axis::right_y, gamepad_righty_response_curve); + gamepad->set_response_curve(input::game_controller_axis::left_trigger, gamepad_lefttrigger_response_curve); + gamepad->set_response_curve(input::game_controller_axis::right_trigger, gamepad_righttrigger_response_curve); } // Toggle fullscreen @@ -410,6 +507,15 @@ void load_controls(game::context* ctx) ); } +static input::game_controller_response_curve parse_response_curve(const std::string& curve) +{ + if (curve == "square") + return input::game_controller_response_curve::square; + else if (curve == "cube") + return input::game_controller_response_curve::cube; + return input::game_controller_response_curve::linear; +} + void cosmogenesis(game::context* ctx) { // Init time diff --git a/src/input/control.cpp b/src/input/control.cpp index 0ab9dbc..20fbaa2 100644 --- a/src/input/control.cpp +++ b/src/input/control.cpp @@ -22,7 +22,7 @@ namespace input { control::control(): - deadzone(0.0f), + activation_threshold(0.0f), current_value(0.0f), previous_value(0.0f), reset(false), @@ -97,9 +97,9 @@ void control::set_temporary_value(float value) reset = true; } -void control::set_deadzone(float value) +void control::set_activation_threshold(float threshold) { - deadzone = value; + activation_threshold = threshold; } void control::set_activated_callback(std::function callback) diff --git a/src/input/control.hpp b/src/input/control.hpp index f89a078..7b2c7f3 100644 --- a/src/input/control.hpp +++ b/src/input/control.hpp @@ -54,11 +54,11 @@ public: void set_temporary_value(float value); /** - * Sets the deadzone value. If the current value of the control is not greater than the deadzone value, the control will not be considered active. + * Sets the activation threshold. If the current value of the control is not greater than the activation threshold, the control will not be considered active. * - * @param value Deadzone value. + * @param threshold Activation threshold. */ - void set_deadzone(float value); + void set_activation_threshold(float threshold); /// Sets the callback for when the control is activated. void set_activated_callback(std::function callback); @@ -79,8 +79,8 @@ public: */ void set_callbacks_enabled(bool enabled); - /// Returns the deadzone value. The default value is 0.0. - float get_deadzone() const; + /// Returns the activation threshold. The default value is 0.0. + float get_activation_threshold() const; /// Returns the current value of the control. float get_current_value() const; @@ -95,7 +95,7 @@ public: bool was_active() const; private: - float deadzone; + float activation_threshold; float current_value; float previous_value; bool reset; @@ -111,9 +111,9 @@ inline void control::set_callbacks_enabled(bool enabled) this->callbacks_enabled = enabled; } -inline float control::get_deadzone() const +inline float control::get_activation_threshold() const { - return deadzone; + return activation_threshold; } inline float control::get_current_value() const @@ -128,12 +128,12 @@ inline float control::get_previous_value() const inline bool control::is_active() const { - return current_value > deadzone; + return current_value > activation_threshold; } inline bool control::was_active() const { - return previous_value > deadzone; + return previous_value > activation_threshold; } } // namespace input diff --git a/src/input/game-controller.cpp b/src/input/game-controller.cpp index 88c3e64..c593712 100644 --- a/src/input/game-controller.cpp +++ b/src/input/game-controller.cpp @@ -20,13 +20,58 @@ #include "game-controller.hpp" #include "event/input-events.hpp" #include "event/event-dispatcher.hpp" +#include "math/map.hpp" +#include #include namespace input { game_controller::game_controller(): - connected(true) -{} + connected(true), + left_deadzone_cross(true), + right_deadzone_cross(true), + left_deadzone_roundness(0.0f), + right_deadzone_roundness(0.0f) +{ + for (int i = 0; i < 6; ++i) + { + axis_values[i] = 0.0f; + axis_activation_min[i] = 0.0f; + axis_activation_max[i] = 1.0f; + axis_response_curves[i] = game_controller_response_curve::linear; + } +} + +void game_controller::set_activation_threshold(game_controller_axis axis, float min, float max) +{ + axis_activation_min[static_cast(axis)] = min; + axis_activation_max[static_cast(axis)] = max; +} + +void game_controller::set_response_curve(game_controller_axis axis, game_controller_response_curve curve) +{ + axis_response_curves[static_cast(axis)] = curve; +} + +void game_controller::set_left_deadzone_cross(bool cross) +{ + left_deadzone_cross = cross; +} + +void game_controller::set_right_deadzone_cross(bool cross) +{ + right_deadzone_cross = cross; +} + +void game_controller::set_left_deadzone_roundness(float roundness) +{ + left_deadzone_roundness = roundness; +} + +void game_controller::set_right_deadzone_roundness(float roundness) +{ + right_deadzone_roundness = roundness; +} void game_controller::press(game_controller_button button) { @@ -58,19 +103,154 @@ void game_controller::release(game_controller_button button) void game_controller::move(game_controller_axis axis, float value) { + // Update axis value + axis_values[static_cast(axis)] = value; + if (!device::event_dispatcher) { return; } + + switch (axis) + { + case game_controller_axis::left_x: + case game_controller_axis::left_y: + if (left_deadzone_cross) + handle_axial_motion(axis); + else + handle_biaxial_motion(game_controller_axis::left_x, game_controller_axis::left_y); + break; + + case game_controller_axis::right_x: + case game_controller_axis::right_y: + if (right_deadzone_cross) + handle_axial_motion(axis); + else + handle_biaxial_motion(game_controller_axis::right_x, game_controller_axis::right_y); + break; + + default: + handle_axial_motion(axis); + break; + } +} +void game_controller::handle_axial_motion(game_controller_axis axis) +{ + // Get axis parameters + const int axis_index = static_cast(axis); + const float activation_min = axis_activation_min[axis_index]; + const float activation_max = axis_activation_max[axis_index]; + const float axis_value = axis_values[axis_index]; + const game_controller_response_curve response_curve = axis_response_curves[axis_index]; + + // Build event game_controller_axis_moved_event event; event.controller = this; event.axis = axis; - event.value = value; - + + if (std::abs(axis_value) > activation_min) + { + // Remap response value according to activation thresholds and clamp to `[0, 1]`. + float response = math::map(std::abs(axis_value), activation_min, activation_max, 0.0f, 1.0f); + response = std::clamp(response, 0.0f, 1.0f); + + // Remap response value according to axis response curve + response = curve_response(axis, response); + + // Restore sign of axis motion + response = (axis_value < 0.0f) ? -response : response; + + event.value = response; + } + else + { + event.value = 0.0f; + } + + // Dispatch event device::event_dispatcher->queue(event); } +void game_controller::handle_biaxial_motion(game_controller_axis axis_x, game_controller_axis axis_y) +{ + // Get axis parameters + const int x_axis_index = static_cast(axis_x); + const int y_axis_index = static_cast(axis_y); + const float x_activation_min = axis_activation_min[x_axis_index]; + const float x_activation_max = axis_activation_max[x_axis_index]; + const float y_activation_min = axis_activation_min[y_axis_index]; + const float y_activation_max = axis_activation_max[y_axis_index]; + const float x_axis_value = axis_values[x_axis_index]; + const float y_axis_value = axis_values[y_axis_index]; + const game_controller_response_curve x_response_curve = axis_response_curves[x_axis_index]; + const game_controller_response_curve y_response_curve = axis_response_curves[y_axis_index]; + const float deadzone_roundness = (axis_x == game_controller_axis::left_x) ? left_deadzone_roundness : right_deadzone_roundness; + + const float radius = std::min(x_activation_min, y_activation_min) * deadzone_roundness; + const float dx = std::max(0.0f, std::abs(x_axis_value) - x_activation_min + radius); + const float dy = std::max(0.0f, std::abs(y_axis_value) - y_activation_min + radius); + const float distance = std::sqrt(dx * dx + dy * dy) - radius; + + // Build event + game_controller_axis_moved_event event; + event.controller = this; + + if (distance > 0.0f) + { + const float nx = std::abs(x_axis_value) / distance; + const float ny = std::abs(y_axis_value) / distance; + const float ndx = (distance - x_activation_min) / (x_activation_max - x_activation_min); + const float ndy = (distance - y_activation_min) / (y_activation_max - y_activation_min); + + float response_x = std::clamp(nx * ndx, 0.0f, 1.0f); + float response_y = std::clamp(ny * ndy, 0.0f, 1.0f); + + response_x = curve_response(axis_x, response_x); + response_y = curve_response(axis_y, response_y); + + // Restore signs of axis motions + response_x = (x_axis_value < 0.0f) ? -response_x : response_x; + response_y = (y_axis_value < 0.0f) ? -response_y : response_y; + + event.value = response_x; + event.axis = axis_x; + device::event_dispatcher->queue(event); + event.value = response_y; + event.axis = axis_y; + device::event_dispatcher->queue(event); + } + else + { + event.value = 0.0f; + event.axis = axis_x; + device::event_dispatcher->queue(event); + event.axis = axis_y; + device::event_dispatcher->queue(event); + } +} + +float game_controller::curve_response(game_controller_axis axis, float response) const +{ + const game_controller_response_curve response_curve = axis_response_curves[static_cast(axis)]; + + switch (response_curve) + { + case game_controller_response_curve::linear: + break; + + case game_controller_response_curve::square: + response = response * response; + break; + + case game_controller_response_curve::cube: + response = response * response * response; + break; + } + + return response; +} + void game_controller::connect(bool reconnected) { connected = true; diff --git a/src/input/game-controller.hpp b/src/input/game-controller.hpp index 0b73ad9..98a09be 100644 --- a/src/input/game-controller.hpp +++ b/src/input/game-controller.hpp @@ -24,6 +24,7 @@ namespace input { +/// Game controller buttons. enum class game_controller_button { a, @@ -43,16 +44,41 @@ enum class game_controller_button dpad_right }; +/// Game controller axes. enum class game_controller_axis { + /// Left stick x-axis. left_x, + + /// Left stick y-axis. left_y, + + /// Right stick x-axis. right_x, + + /// Right stick y-axis. right_y, + + /// Left trigger. left_trigger, + + /// Right trigger. right_trigger, }; +/// Game controller axis activation response curves. +enum class game_controller_response_curve +{ + /// Linear response curve. + linear, + + /// Squared response curve. + square, + + /// Cubed response curve. + cube +}; + /** * A virtual game controller which can generate game controller-related input events and pass them to an event dispatcher. * @@ -65,24 +91,69 @@ public: * Creates a game controller input device. */ game_controller(); - + /// Destroys a game controller input device. virtual ~game_controller() = default; + /** + * Sets the activation threshold for a game controller axis. + * + * @param axis Game controller axis. + * @param min Axis minimum activation threshold. + * @param max Axis maximum activation threshold. + */ + void set_activation_threshold(game_controller_axis axis, float min, float max); + + /** + * Sets the activation response curve of an axis. + * + * @param axis Game controller axis. + * @param curve Activation response curve. + */ + void set_response_curve(game_controller_axis axis, game_controller_response_curve curve); + + /** + * Sets the type of deadzone shape for the axes on the left stick. + * + * @param cross If `true`, the x and y axes are independently activated, if `false`, activation of the x and y axes are dependent on their combined magnitude. + */ + void set_left_deadzone_cross(bool cross); + + /** + * Sets the type of deadzone shape for the axes on the right stick. + * + * @param cross If `true`, the x and y axes are independently activated, if `false`, activation of the x and y axes are dependent on their combined magnitude. + */ + void set_right_deadzone_cross(bool cross); + + /** + * Sets the roundness of the deadzone for the axes on the left stick. + * + * @param roundness Roundness of the deadzone shape for non-cross deadzones. A value of `0.0` results in a square deadzone, while a value of `1.0` results in a circular deadzone. Values between `0.0` and `1.0` result in a rounded rectangle deadzone. + */ + void set_left_deadzone_roundness(float roundness); + + /** + * Sets the roundness of the deadzone for the axes on the right stick. + * + * @param roundness Roundness of the deadzone shape for non-cross deadzones. A value of `0.0` results in a square deadzone, while a value of `1.0` results in a circular deadzone. Values between `0.0` and `1.0` result in a rounded rectangle deadzone. + */ + void set_right_deadzone_roundness(float roundness); + /** * Simulates a game controller button press. * * @param button Index of the pressed button. */ void press(game_controller_button button); - + /** * Simulates a game controller button release. * * @param button Index of the released button. */ void release(game_controller_button button); - + /** * Simulates a game controller axis movement. * @@ -106,7 +177,20 @@ public: bool is_connected() const; private: + void handle_axial_motion(game_controller_axis axis); + void handle_biaxial_motion(game_controller_axis axis_x, game_controller_axis axis_y); + float curve_response(game_controller_axis axis, float response) const; + bool connected; + float axis_values[6]; + float axis_activation_min[6]; + float axis_activation_max[6]; + game_controller_response_curve axis_response_curves[6]; + + bool left_deadzone_cross; + bool right_deadzone_cross; + float left_deadzone_roundness; + float right_deadzone_roundness; }; inline bool game_controller::is_connected() const diff --git a/src/math/map.hpp b/src/math/map.hpp new file mode 100644 index 0000000..b040519 --- /dev/null +++ b/src/math/map.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_MATH_MAP_HPP +#define ANTKEEPER_MATH_MAP_HPP + +namespace math { + +/** + * Remaps a number from one range to another. + * + * @param value Value to remap. + * @param from_min Start of the first range. + * @param from_max End of the first range. + * @param to_min Start of the second range. + * @param to_max End of the second range. + * @return Unclamped remapped value. + */ +template +T map(T value, T from_min, T from_max, T to_min, T to_max) +{ + return to_min + (to_max - to_min) * ((value - from_min) / (from_max - from_min)); +} + +} // namespace math + +#endif // ANTKEEPER_MATH_MAP_HPP diff --git a/src/math/math.hpp b/src/math/math.hpp index 91ecc12..158f9b6 100644 --- a/src/math/math.hpp +++ b/src/math/math.hpp @@ -46,5 +46,6 @@ namespace math {} #include "math/quadrature.hpp" #include "math/interpolation.hpp" #include "math/random.hpp" +#include "math/map.hpp" #endif // ANTKEEPER_MATH_HPP