summaryrefslogtreecommitdiff
path: root/src/core/hid/input_converter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hid/input_converter.cpp')
-rw-r--r--src/core/hid/input_converter.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
new file mode 100644
index 000000000..5834622e9
--- /dev/null
+++ b/src/core/hid/input_converter.cpp
@@ -0,0 +1,345 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include <random>
6
7#include "common/input.h"
8#include "core/hid/input_converter.h"
9
10namespace Core::HID {
11
12Input::BatteryStatus TransformToBattery(const Input::CallbackStatus& callback) {
13 Input::BatteryStatus battery{};
14 switch (callback.type) {
15 case Input::InputType::Analog:
16 case Input::InputType::Trigger: {
17 const auto value = TransformToTrigger(callback).analog.value;
18 battery = Input::BatteryLevel::Empty;
19 if (value > 0.2f) {
20 battery = Input::BatteryLevel::Critical;
21 }
22 if (value > 0.4f) {
23 battery = Input::BatteryLevel::Low;
24 }
25 if (value > 0.6f) {
26 battery = Input::BatteryLevel::Medium;
27 }
28 if (value > 0.8f) {
29 battery = Input::BatteryLevel::Full;
30 }
31 if (value >= 1.0f) {
32 battery = Input::BatteryLevel::Charging;
33 }
34 break;
35 }
36 case Input::InputType::Battery:
37 battery = callback.battery_status;
38 break;
39 default:
40 LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
41 break;
42 }
43
44 return battery;
45}
46
47Input::ButtonStatus TransformToButton(const Input::CallbackStatus& callback) {
48 Input::ButtonStatus status{};
49 switch (callback.type) {
50 case Input::InputType::Analog:
51 case Input::InputType::Trigger:
52 status.value = TransformToTrigger(callback).pressed;
53 break;
54 case Input::InputType::Button:
55 status = callback.button_status;
56 break;
57 default:
58 LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
59 break;
60 }
61
62 if (status.inverted) {
63 status.value = !status.value;
64 }
65
66 return status;
67}
68
69Input::MotionStatus TransformToMotion(const Input::CallbackStatus& callback) {
70 Input::MotionStatus status{};
71 switch (callback.type) {
72 case Input::InputType::Button: {
73 if (TransformToButton(callback).value) {
74 std::random_device device;
75 std::mt19937 gen(device());
76 std::uniform_int_distribution<s16> distribution(-1000, 1000);
77 Input::AnalogProperties properties{
78 .deadzone = 0.0,
79 .range = 1.0f,
80 .offset = 0.0,
81 };
82 status.accel.x = {
83 .value = 0,
84 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
85 .properties = properties,
86 };
87 status.accel.y = {
88 .value = 0,
89 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
90 .properties = properties,
91 };
92 status.accel.z = {
93 .value = 0,
94 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
95 .properties = properties,
96 };
97 status.gyro.x = {
98 .value = 0,
99 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
100 .properties = properties,
101 };
102 status.gyro.y = {
103 .value = 0,
104 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
105 .properties = properties,
106 };
107 status.gyro.z = {
108 .value = 0,
109 .raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
110 .properties = properties,
111 };
112 }
113 break;
114 }
115 case Input::InputType::Motion:
116 status = callback.motion_status;
117 break;
118 default:
119 LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
120 break;
121 }
122 SanitizeAnalog(status.accel.x, false);
123 SanitizeAnalog(status.accel.y, false);
124 SanitizeAnalog(status.accel.z, false);
125 SanitizeAnalog(status.gyro.x, false);
126 SanitizeAnalog(status.gyro.y, false);
127 SanitizeAnalog(status.gyro.z, false);
128
129 return status;
130}
131
132Input::StickStatus TransformToStick(const Input::CallbackStatus& callback) {
133 Input::StickStatus status{};
134
135 switch (callback.type) {
136 case Input::InputType::Stick:
137 status = callback.stick_status;
138 break;
139 default:
140 LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
141 break;
142 }
143
144 SanitizeStick(status.x, status.y, true);
145 const Input::AnalogProperties& properties_x = status.x.properties;
146 const Input::AnalogProperties& properties_y = status.y.properties;
147 const float x = status.x.value;
148 const float y = status.y.value;
149
150 // Set directional buttons
151 status.right = x > properties_x.threshold;
152 status.left = x < -properties_x.threshold;
153 status.up = y > properties_y.threshold;
154 status.down = y < -properties_y.threshold;
155
156 return status;
157}
158
159Input::TouchStatus TransformToTouch(const Input::CallbackStatus& callback) {
160 Input::TouchStatus status{};
161
162 switch (callback.type) {
163 case Input::InputType::Touch:
164 status = callback.touch_status;
165 break;
166 default:
167 LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
168 break;
169 }
170
171 SanitizeAnalog(status.x, true);
172 SanitizeAnalog(status.y, true);
173 float& x = status.x.value;
174 float& y = status.y.value;
175
176 // Adjust if value is inverted
177 x = status.x.properties.inverted ? 1.0f + x : x;
178 y = status.y.properties.inverted ? 1.0f + y : y;
179
180 // clamp value
181 x = std::clamp(x, 0.0f, 1.0f);
182 y = std::clamp(y, 0.0f, 1.0f);
183
184 if (status.pressed.inverted) {
185 status.pressed.value = !status.pressed.value;
186 }
187
188 return status;
189}
190
191Input::TriggerStatus TransformToTrigger(const Input::CallbackStatus& callback) {
192 Input::TriggerStatus status{};
193 float& raw_value = status.analog.raw_value;
194 bool calculate_button_value = true;
195
196 switch (callback.type) {
197 case Input::InputType::Analog:
198 status.analog.properties = callback.analog_status.properties;
199 raw_value = callback.analog_status.raw_value;
200 break;
201 case Input::InputType::Button:
202 status.analog.properties.range = 1.0f;
203 status.analog.properties.inverted = callback.button_status.inverted;
204 raw_value = callback.button_status.value ? 1.0f : 0.0f;
205 break;
206 case Input::InputType::Trigger:
207 status = callback.trigger_status;
208 calculate_button_value = false;
209 break;
210 default:
211 LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
212 break;
213 }
214
215 SanitizeAnalog(status.analog, true);
216 const Input::AnalogProperties& properties = status.analog.properties;
217 float& value = status.analog.value;
218
219 // Set button status
220 if (calculate_button_value) {
221 status.pressed = value > properties.threshold;
222 }
223
224 // Adjust if value is inverted
225 value = properties.inverted ? 1.0f + value : value;
226
227 // clamp value
228 value = std::clamp(value, 0.0f, 1.0f);
229
230 return status;
231}
232
233void SanitizeAnalog(Input::AnalogStatus& analog, bool clamp_value) {
234 const Input::AnalogProperties& properties = analog.properties;
235 float& raw_value = analog.raw_value;
236 float& value = analog.value;
237
238 if (!std::isnormal(raw_value)) {
239 raw_value = 0;
240 }
241
242 // Apply center offset
243 raw_value -= properties.offset;
244
245 // Set initial values to be formated
246 value = raw_value;
247
248 // Calculate vector size
249 const float r = std::abs(value);
250
251 // Return zero if value is smaller than the deadzone
252 if (r <= properties.deadzone || properties.deadzone == 1.0f) {
253 analog.value = 0;
254 return;
255 }
256
257 // Adjust range of value
258 const float deadzone_factor =
259 1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
260 value = value * deadzone_factor / properties.range;
261
262 // Invert direction if needed
263 if (properties.inverted) {
264 value = -value;
265 }
266
267 // Clamp value
268 if (clamp_value) {
269 value = std::clamp(value, -1.0f, 1.0f);
270 }
271}
272
273void SanitizeStick(Input::AnalogStatus& analog_x, Input::AnalogStatus& analog_y, bool clamp_value) {
274 const Input::AnalogProperties& properties_x = analog_x.properties;
275 const Input::AnalogProperties& properties_y = analog_y.properties;
276 float& raw_x = analog_x.raw_value;
277 float& raw_y = analog_y.raw_value;
278 float& x = analog_x.value;
279 float& y = analog_y.value;
280
281 if (!std::isnormal(raw_x)) {
282 raw_x = 0;
283 }
284 if (!std::isnormal(raw_y)) {
285 raw_y = 0;
286 }
287
288 // Apply center offset
289 raw_x += properties_x.offset;
290 raw_y += properties_y.offset;
291
292 // Apply X scale correction from offset
293 if (std::abs(properties_x.offset) < 0.5f) {
294 if (raw_x > 0) {
295 raw_x /= 1 + properties_x.offset;
296 } else {
297 raw_x /= 1 - properties_x.offset;
298 }
299 }
300
301 // Apply Y scale correction from offset
302 if (std::abs(properties_y.offset) < 0.5f) {
303 if (raw_y > 0) {
304 raw_y /= 1 + properties_y.offset;
305 } else {
306 raw_y /= 1 - properties_y.offset;
307 }
308 }
309
310 // Invert direction if needed
311 raw_x = properties_x.inverted ? -raw_x : raw_x;
312 raw_y = properties_y.inverted ? -raw_y : raw_y;
313
314 // Set initial values to be formated
315 x = raw_x;
316 y = raw_y;
317
318 // Calculate vector size
319 float r = x * x + y * y;
320 r = std::sqrt(r);
321
322 // TODO(German77): Use deadzone and range of both axis
323
324 // Return zero if values are smaller than the deadzone
325 if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
326 x = 0;
327 y = 0;
328 return;
329 }
330
331 // Adjust range of joystick
332 const float deadzone_factor =
333 1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
334 x = x * deadzone_factor / properties_x.range;
335 y = y * deadzone_factor / properties_x.range;
336 r = r * deadzone_factor / properties_x.range;
337
338 // Normalize joystick
339 if (clamp_value && r > 1.0f) {
340 x /= r;
341 y /= r;
342 }
343}
344
345} // namespace Core::HID