Browse Source

Add advanced deadzone configuration functions to the game controller class

master
C. J. Howard 3 years ago
parent
commit
4ae99d7596
9 changed files with 456 additions and 33 deletions
  1. +3
    -0
      src/application.cpp
  2. +16
    -10
      src/game/states/forage.cpp
  3. +109
    -3
      src/game/states/loading.cpp
  4. +3
    -3
      src/input/control.cpp
  5. +10
    -10
      src/input/control.hpp
  6. +184
    -4
      src/input/game-controller.cpp
  7. +87
    -3
      src/input/game-controller.hpp
  8. +43
    -0
      src/math/map.hpp
  9. +1
    -0
      src/math/math.hpp

+ 3
- 0
src/application.cpp View File

@ -210,6 +210,9 @@ application::application():
keyboard->set_event_dispatcher(event_dispatcher); keyboard->set_event_dispatcher(event_dispatcher);
mouse = new input::mouse(); mouse = new input::mouse();
mouse->set_event_dispatcher(event_dispatcher); mouse->set_event_dispatcher(event_dispatcher);
// Connect game controllers
translate_sdl_events();
// Setup frame scheduler // Setup frame scheduler
frame_scheduler = new ::frame_scheduler(); frame_scheduler = new ::frame_scheduler();

+ 16
- 10
src/game/states/forage.cpp View File

@ -271,22 +271,28 @@ void setup_controls(game::context* ctx)
const float dolly_speed = 20.0f; const float dolly_speed = 20.0f;
const float truck_speed = dolly_speed; const float truck_speed = dolly_speed;
const float pedestal_speed = 30.0f; 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_tilt = false;
bool mouse_invert_pan = 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_tilt = false;
bool gamepad_invert_pan = false; bool gamepad_invert_pan = false;
if (ctx->config->contains("mouse_look_sensitivity"))
mouse_look_sensitivity = math::radians((*ctx->config)["mouse_look_sensitivity"].get<float>());
if (ctx->config->contains("mouse_tilt_sensitivity"))
mouse_tilt_sensitivity = math::radians((*ctx->config)["mouse_tilt_sensitivity"].get<float>());
if (ctx->config->contains("mouse_pan_sensitivity"))
mouse_pan_sensitivity = math::radians((*ctx->config)["mouse_pan_sensitivity"].get<float>());
if (ctx->config->contains("mouse_invert_tilt")) if (ctx->config->contains("mouse_invert_tilt"))
mouse_invert_tilt = math::radians((*ctx->config)["mouse_invert_tilt"].get<bool>()); mouse_invert_tilt = math::radians((*ctx->config)["mouse_invert_tilt"].get<bool>());
if (ctx->config->contains("mouse_invert_pan")) if (ctx->config->contains("mouse_invert_pan"))
mouse_invert_pan = math::radians((*ctx->config)["mouse_invert_pan"].get<bool>()); mouse_invert_pan = math::radians((*ctx->config)["mouse_invert_pan"].get<bool>());
if (ctx->config->contains("gamepad_look_sensitivity"))
gamepad_look_sensitivity = math::radians((*ctx->config)["gamepad_look_sensitivity"].get<float>());
if (ctx->config->contains("gamepad_tilt_sensitivity"))
gamepad_tilt_sensitivity = math::radians((*ctx->config)["gamepad_tilt_sensitivity"].get<float>());
if (ctx->config->contains("gamepad_pan_sensitivity"))
gamepad_pan_sensitivity = math::radians((*ctx->config)["gamepad_pan_sensitivity"].get<float>());
if (ctx->config->contains("gamepad_invert_tilt")) if (ctx->config->contains("gamepad_invert_tilt"))
gamepad_invert_tilt = math::radians((*ctx->config)["gamepad_invert_tilt"].get<bool>()); gamepad_invert_tilt = math::radians((*ctx->config)["gamepad_invert_tilt"].get<bool>());
if (ctx->config->contains("gamepad_invert_pan")) 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* move_fast = ctx->controls["move_fast"];
const input::control* mouse_rotate = ctx->controls["mouse_rotate"]; 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 ctx->controls["dolly_forward"]->set_active_callback
( (

+ 109
- 3
src/game/states/loading.cpp View File

@ -56,6 +56,8 @@ namespace loading {
/// Creates or loads control configuration /// Creates or loads control configuration
static void load_controls(game::context* ctx); 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. /// Creates the universe and solar system.
static void cosmogenesis(game::context* ctx); static void cosmogenesis(game::context* ctx);
@ -128,6 +130,11 @@ void load_controls(game::context* ctx)
// Allocate known controls // Allocate known controls
ctx->controls["toggle_fullscreen"] = new input::control(); ctx->controls["toggle_fullscreen"] = new input::control();
ctx->controls["screenshot"] = 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["menu_back"] = new input::control();
ctx->controls["dolly_forward"] = new input::control(); ctx->controls["dolly_forward"] = new input::control();
ctx->controls["dolly_backward"] = 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["tilt_down_mouse"] = new input::control();
ctx->controls["use_tool"] = 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 // Get keyboard and mouse devices
input::keyboard* keyboard = ctx->app->get_keyboard(); input::keyboard* keyboard = ctx->app->get_keyboard();
input::mouse* mouse = ctx->app->get_mouse(); 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<float>();
if (ctx->config->contains("gamepad_leftx_activation_max"))
gamepad_leftx_activation_max = (*ctx->config)["gamepad_leftx_activation_max"].get<float>();
if (ctx->config->contains("gamepad_lefty_activation_min"))
gamepad_lefty_activation_min = (*ctx->config)["gamepad_lefty_activation_min"].get<float>();
if (ctx->config->contains("gamepad_lefty_activation_max"))
gamepad_lefty_activation_max = (*ctx->config)["gamepad_lefty_activation_max"].get<float>();
if (ctx->config->contains("gamepad_rightx_activation_min"))
gamepad_rightx_activation_min = (*ctx->config)["gamepad_rightx_activation_min"].get<float>();
if (ctx->config->contains("gamepad_rightx_activation_max"))
gamepad_rightx_activation_max = (*ctx->config)["gamepad_rightx_activation_max"].get<float>();
if (ctx->config->contains("gamepad_righty_activation_min"))
gamepad_righty_activation_min = (*ctx->config)["gamepad_righty_activation_min"].get<float>();
if (ctx->config->contains("gamepad_righty_activation_max"))
gamepad_righty_activation_max = (*ctx->config)["gamepad_righty_activation_max"].get<float>();
if (ctx->config->contains("gamepad_lefttrigger_activation_min"))
gamepad_lefttrigger_activation_min = (*ctx->config)["gamepad_lefttrigger_activation_min"].get<float>();
if (ctx->config->contains("gamepad_lefttrigger_activation_max"))
gamepad_lefttrigger_activation_max = (*ctx->config)["gamepad_lefttrigger_activation_max"].get<float>();
if (ctx->config->contains("gamepad_righttrigger_activation_min"))
gamepad_righttrigger_activation_min = (*ctx->config)["gamepad_righttrigger_activation_min"].get<float>();
if (ctx->config->contains("gamepad_righttrigger_activation_max"))
gamepad_righttrigger_activation_max = (*ctx->config)["gamepad_righttrigger_activation_max"].get<float>();
if (ctx->config->contains("gamepad_left_deadzone_cross"))
gamepad_left_deadzone_cross = (*ctx->config)["gamepad_left_deadzone_cross"].get<bool>();
if (ctx->config->contains("gamepad_right_deadzone_cross"))
gamepad_right_deadzone_cross = (*ctx->config)["gamepad_right_deadzone_cross"].get<bool>();
if (ctx->config->contains("gamepad_left_deadzone_roundness"))
gamepad_left_deadzone_roundness = (*ctx->config)["gamepad_left_deadzone_roundness"].get<float>();
if (ctx->config->contains("gamepad_right_deadzone_roundness"))
gamepad_right_deadzone_roundness = (*ctx->config)["gamepad_right_deadzone_roundness"].get<float>();
if (ctx->config->contains("gamepad_leftx_response_curve"))
gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get<std::string>());
if (ctx->config->contains("gamepad_leftx_response_curve"))
gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get<std::string>());
if (ctx->config->contains("gamepad_rightx_response_curve"))
gamepad_rightx_response_curve = parse_response_curve((*ctx->config)["gamepad_rightx_response_curve"].get<std::string>());
if (ctx->config->contains("gamepad_leftx_response_curve"))
gamepad_leftx_response_curve = parse_response_curve((*ctx->config)["gamepad_leftx_response_curve"].get<std::string>());
if (ctx->config->contains("gamepad_lefttrigger_response_curve"))
gamepad_lefttrigger_response_curve = parse_response_curve((*ctx->config)["gamepad_lefttrigger_response_curve"].get<std::string>());
if (ctx->config->contains("gamepad_righttrigger_response_curve"))
gamepad_righttrigger_response_curve = parse_response_curve((*ctx->config)["gamepad_righttrigger_response_curve"].get<std::string>());
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 // 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) void cosmogenesis(game::context* ctx)
{ {
// Init time // Init time

+ 3
- 3
src/input/control.cpp View File

@ -22,7 +22,7 @@
namespace input { namespace input {
control::control(): control::control():
deadzone(0.0f),
activation_threshold(0.0f),
current_value(0.0f), current_value(0.0f),
previous_value(0.0f), previous_value(0.0f),
reset(false), reset(false),
@ -97,9 +97,9 @@ void control::set_temporary_value(float value)
reset = true; 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<void()> callback) void control::set_activated_callback(std::function<void()> callback)

+ 10
- 10
src/input/control.hpp View File

@ -54,11 +54,11 @@ public:
void set_temporary_value(float value); 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. /// Sets the callback for when the control is activated.
void set_activated_callback(std::function<void()> callback); void set_activated_callback(std::function<void()> callback);
@ -79,8 +79,8 @@ public:
*/ */
void set_callbacks_enabled(bool enabled); 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. /// Returns the current value of the control.
float get_current_value() const; float get_current_value() const;
@ -95,7 +95,7 @@ public:
bool was_active() const; bool was_active() const;
private: private:
float deadzone;
float activation_threshold;
float current_value; float current_value;
float previous_value; float previous_value;
bool reset; bool reset;
@ -111,9 +111,9 @@ inline void control::set_callbacks_enabled(bool enabled)
this->callbacks_enabled = 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 inline float control::get_current_value() const
@ -128,12 +128,12 @@ inline float control::get_previous_value() const
inline bool control::is_active() const inline bool control::is_active() const
{ {
return current_value > deadzone;
return current_value > activation_threshold;
} }
inline bool control::was_active() const inline bool control::was_active() const
{ {
return previous_value > deadzone;
return previous_value > activation_threshold;
} }
} // namespace input } // namespace input

+ 184
- 4
src/input/game-controller.cpp View File

@ -20,13 +20,58 @@
#include "game-controller.hpp" #include "game-controller.hpp"
#include "event/input-events.hpp" #include "event/input-events.hpp"
#include "event/event-dispatcher.hpp" #include "event/event-dispatcher.hpp"
#include "math/map.hpp"
#include <algorithm>
#include <cmath> #include <cmath>
namespace input { namespace input {
game_controller::game_controller(): 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<int>(axis)] = min;
axis_activation_max[static_cast<int>(axis)] = max;
}
void game_controller::set_response_curve(game_controller_axis axis, game_controller_response_curve curve)
{
axis_response_curves[static_cast<int>(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) 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) void game_controller::move(game_controller_axis axis, float value)
{ {
// Update axis value
axis_values[static_cast<int>(axis)] = value;
if (!device::event_dispatcher) if (!device::event_dispatcher)
{ {
return; 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<int>(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; game_controller_axis_moved_event event;
event.controller = this; event.controller = this;
event.axis = axis; 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); 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<int>(axis_x);
const int y_axis_index = static_cast<int>(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<float>(x_activation_min, y_activation_min) * deadzone_roundness;
const float dx = std::max<float>(0.0f, std::abs(x_axis_value) - x_activation_min + radius);
const float dy = std::max<float>(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<int>(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) void game_controller::connect(bool reconnected)
{ {
connected = true; connected = true;

+ 87
- 3
src/input/game-controller.hpp View File

@ -24,6 +24,7 @@
namespace input { namespace input {
/// Game controller buttons.
enum class game_controller_button enum class game_controller_button
{ {
a, a,
@ -43,16 +44,41 @@ enum class game_controller_button
dpad_right dpad_right
}; };
/// Game controller axes.
enum class game_controller_axis enum class game_controller_axis
{ {
/// Left stick x-axis.
left_x, left_x,
/// Left stick y-axis.
left_y, left_y,
/// Right stick x-axis.
right_x, right_x,
/// Right stick y-axis.
right_y, right_y,
/// Left trigger.
left_trigger, left_trigger,
/// Right 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. * 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. * Creates a game controller input device.
*/ */
game_controller(); game_controller();
/// Destroys a game controller input device. /// Destroys a game controller input device.
virtual ~game_controller() = default; 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. * Simulates a game controller button press.
* *
* @param button Index of the pressed button. * @param button Index of the pressed button.
*/ */
void press(game_controller_button button); void press(game_controller_button button);
/** /**
* Simulates a game controller button release. * Simulates a game controller button release.
* *
* @param button Index of the released button. * @param button Index of the released button.
*/ */
void release(game_controller_button button); void release(game_controller_button button);
/** /**
* Simulates a game controller axis movement. * Simulates a game controller axis movement.
* *
@ -106,7 +177,20 @@ public:
bool is_connected() const; bool is_connected() const;
private: 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; 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 inline bool game_controller::is_connected() const

+ 43
- 0
src/math/map.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <class T>
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

+ 1
- 0
src/math/math.hpp View File

@ -46,5 +46,6 @@ namespace math {}
#include "math/quadrature.hpp" #include "math/quadrature.hpp"
#include "math/interpolation.hpp" #include "math/interpolation.hpp"
#include "math/random.hpp" #include "math/random.hpp"
#include "math/map.hpp"
#endif // ANTKEEPER_MATH_HPP #endif // ANTKEEPER_MATH_HPP

Loading…
Cancel
Save