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