Antkeeper  0.0.1
gamepad.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <engine/input/gamepad.hpp>
21 #include <engine/math/map.hpp>
22 #include <algorithm>
23 #include <type_traits>
24 #include <cmath>
25 
26 namespace input {
27 
29 {
30  for (int i = 0; i < 6; ++i)
31  {
32  axis_positions[i] = 0.0f;
33  axis_activation_min[i] = 0.15f;
34  axis_activation_max[i] = 0.98f;
35  axis_response_curves[i] = gamepad_response_curve::linear;
36  }
37 }
38 
39 void gamepad::set_activation_threshold(gamepad_axis axis, float min, float max)
40 {
41  axis_activation_min[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = min;
42  axis_activation_max[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = max;
43 }
44 
46 {
47  axis_response_curves[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = curve;
48 }
49 
51 {
52  left_deadzone_cross = cross;
53 }
54 
56 {
57  right_deadzone_cross = cross;
58 }
59 
61 {
62  left_deadzone_roundness = roundness;
63 }
64 
66 {
67  right_deadzone_roundness = roundness;
68 }
69 
71 {
72  button_pressed_publisher.publish({this, button});
73 }
74 
76 {
77  button_released_publisher.publish({this, button});
78 }
79 
81 {
82  const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
83 
85  if (axis_index >= 6)
86  return;
87 
88  // Update axis position
89  axis_positions[axis_index] = position;
90 
91  switch (axis)
92  {
95  if (left_deadzone_cross)
96  handle_axial_motion(axis);
97  else
99  break;
100 
103  if (right_deadzone_cross)
104  handle_axial_motion(axis);
105  else
107  break;
108 
111  default:
112  handle_axial_motion(axis);
113  break;
114  }
115 }
116 
117 void gamepad::handle_axial_motion(gamepad_axis axis)
118 {
119  const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
120 
121  // Get axis parameters
122  const float activation_min = axis_activation_min[axis_index];
123  const float activation_max = axis_activation_max[axis_index];
124  const float axis_position = axis_positions[axis_index];
125  const gamepad_response_curve response_curve = axis_response_curves[axis_index];
126 
127  // Remap axis position
128  float remapped_position = 0.0f;
129  if (std::abs(axis_position) > activation_min)
130  {
131  // Remap position according to activation thresholds and clamp to `[0, 1]`.
132  float response = math::map(std::abs(axis_position), activation_min, activation_max, 0.0f, 1.0f);
133  response = std::clamp(response, 0.0f, 1.0f);
134 
135  // Remap position according to axis response curve
136  response = curve_response(axis, response);
137 
138  // Restore sign of axis motion
139  response = (axis_position < 0.0f) ? -response : response;
140 
141  remapped_position = response;
142  }
143 
144  axis_moved_publisher.publish({this, axis, remapped_position});
145 }
146 
147 void gamepad::handle_biaxial_motion(gamepad_axis axis_x, gamepad_axis axis_y)
148 {
149  // Get axis parameters
150  const int x_axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis_x);
151  const int y_axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis_y);
152  const float x_activation_min = axis_activation_min[x_axis_index];
153  const float x_activation_max = axis_activation_max[x_axis_index];
154  const float y_activation_min = axis_activation_min[y_axis_index];
155  const float y_activation_max = axis_activation_max[y_axis_index];
156  const float x_axis_position = axis_positions[x_axis_index];
157  const float y_axis_position = axis_positions[y_axis_index];
158  const gamepad_response_curve x_response_curve = axis_response_curves[x_axis_index];
159  const gamepad_response_curve y_response_curve = axis_response_curves[y_axis_index];
160  const float deadzone_roundness = (axis_x == gamepad_axis::left_stick_x) ? left_deadzone_roundness : right_deadzone_roundness;
161 
162  const float radius = std::min<float>(x_activation_min, y_activation_min) * deadzone_roundness;
163  const float dx = std::max<float>(0.0f, std::abs(x_axis_position) - x_activation_min + radius);
164  const float dy = std::max<float>(0.0f, std::abs(y_axis_position) - y_activation_min + radius);
165  const float distance = std::sqrt(dx * dx + dy * dy) - radius;
166 
167  if (distance > 0.0f)
168  {
169  const float nx = std::abs(x_axis_position) / distance;
170  const float ny = std::abs(y_axis_position) / distance;
171  const float ndx = (distance - x_activation_min) / (x_activation_max - x_activation_min);
172  const float ndy = (distance - y_activation_min) / (y_activation_max - y_activation_min);
173 
174  float response_x = std::clamp(nx * ndx, 0.0f, 1.0f);
175  float response_y = std::clamp(ny * ndy, 0.0f, 1.0f);
176 
177  response_x = curve_response(axis_x, response_x);
178  response_y = curve_response(axis_y, response_y);
179 
180  // Restore signs of axis motions
181  response_x = (x_axis_position < 0.0f) ? -response_x : response_x;
182  response_y = (y_axis_position < 0.0f) ? -response_y : response_y;
183 
184  axis_moved_publisher.publish({this, axis_x, response_x});
185  axis_moved_publisher.publish({this, axis_y, response_y});
186  }
187  else
188  {
189  axis_moved_publisher.publish({this, axis_x, 0.0f});
190  axis_moved_publisher.publish({this, axis_y, 0.0f});
191  }
192 }
193 
194 float gamepad::curve_response(gamepad_axis axis, float response) const
195 {
196  const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
197  const gamepad_response_curve response_curve = axis_response_curves[axis_index];
198 
199  switch (response_curve)
200  {
202  break;
203 
205  response = response * response;
206  break;
207 
209  response = response * response * response;
210  break;
211  }
212 
213  return response;
214 }
215 
216 } // namespace input
void set_left_deadzone_cross(bool cross)
Sets the type of deadzone shape for the axes on the left stick.
Definition: gamepad.cpp:50
void move(gamepad_axis axis, float position)
Simulates a gamepad axis movement.
Definition: gamepad.cpp:80
gamepad()
Constructs a gamepad input device.
Definition: gamepad.cpp:28
void set_activation_threshold(gamepad_axis axis, float min, float max)
Sets the activation threshold for a gamepad axis.
Definition: gamepad.cpp:39
void press(gamepad_button button)
Simulates a gamepad button press.
Definition: gamepad.cpp:70
void set_right_deadzone_roundness(float roundness)
Sets the roundness of the deadzone for the axes on the right stick.
Definition: gamepad.cpp:65
void set_right_deadzone_cross(bool cross)
Sets the type of deadzone shape for the axes on the right stick.
Definition: gamepad.cpp:55
void set_left_deadzone_roundness(float roundness)
Sets the roundness of the deadzone for the axes on the left stick.
Definition: gamepad.cpp:60
void set_response_curve(gamepad_axis axis, gamepad_response_curve curve)
Sets the activation response curve of an axis.
Definition: gamepad.cpp:45
void release(gamepad_button button)
Simulates a gamepad button release.
Definition: gamepad.cpp:75
Input devices, events, and mapping.
gamepad_response_curve
Gamepad axis activation response curves.
Definition: gamepad.hpp:34
@ cube
Cubed response curve.
@ square
Squared response curve.
@ linear
Linear response curve.
gamepad_button
Gamepad buttons.
gamepad_axis
Gamepad axes.
@ left_trigger
Left trigger.
@ right_trigger
Right trigger.
@ right_stick_y
Right stick Y-axis.
@ left_stick_y
Left stick Y-axis.
@ left_stick_x
Left stick X-axis.
@ right_stick_x
Right stick X-axis.
constexpr T map(T x, T from_min, T from_max, T to_min, T to_max) noexcept
Remaps a number from one range to another.
Definition: map.hpp:37
constexpr vector< T, N > abs(const vector< T, N > &x)
Returns the absolute values of each element.
Definition: vector.hpp:985
T distance(const vector< T, N > &p0, const vector< T, N > &p1)
Calculates the distance between two points.
Definition: vector.hpp:1106
constexpr vector< T, N > clamp(const vector< T, N > &x, const vector< T, N > &min, const vector< T, N > &max)
Clamps the values of a vector's elements.
Definition: vector.hpp:1069
constexpr vector< T, 3 > cross(const vector< T, 3 > &x, const vector< T, 3 > &y) noexcept
Calculates the cross product of two vectors.
Definition: vector.hpp:1095
vector< T, N > sqrt(const vector< T, N > &x)
Takes the square root of each element.