diff options
Diffstat (limited to 'src/core/hid/input_converter.cpp')
-rw-r--r-- | src/core/hid/input_converter.cpp | 383 |
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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include <random> + +#include "common/input.h" +#include "core/hid/input_converter.h" + +namespace Core::HID { + +Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) { + Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None}; + switch (callback.type) { + case Common::Input::InputType::Analog: + case Common::Input::InputType::Trigger: { + const auto value = TransformToTrigger(callback).analog.value; + battery = Common::Input::BatteryLevel::Empty; + if (value > 0.2f) { + battery = Common::Input::BatteryLevel::Critical; + } + if (value > 0.4f) { + battery = Common::Input::BatteryLevel::Low; + } + if (value > 0.6f) { + battery = Common::Input::BatteryLevel::Medium; + } + if (value > 0.8f) { + battery = Common::Input::BatteryLevel::Full; + } + if (value >= 1.0f) { + battery = Common::Input::BatteryLevel::Charging; + } + break; + } + case Common::Input::InputType::Button: + battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging + : Common::Input::BatteryLevel::Critical; + break; + case Common::Input::InputType::Battery: + battery = callback.battery_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type); + break; + } + + return battery; +} + +Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) { + Common::Input::ButtonStatus status{}; + switch (callback.type) { + case Common::Input::InputType::Analog: + case Common::Input::InputType::Trigger: + status.value = TransformToTrigger(callback).pressed.value; + break; + case Common::Input::InputType::Button: + status = callback.button_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type); + break; + } + + if (status.inverted) { + status.value = !status.value; + } + + return status; +} + +Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) { + Common::Input::MotionStatus status{}; + switch (callback.type) { + case Common::Input::InputType::Button: { + Common::Input::AnalogProperties properties{ + .deadzone = 0.0f, + .range = 1.0f, + .offset = 0.0f, + }; + status.delta_timestamp = 5000; + status.force_update = true; + status.accel.x = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.accel.y = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.accel.z = { + .value = 0.0f, + .raw_value = -1.0f, + .properties = properties, + }; + status.gyro.x = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.gyro.y = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + status.gyro.z = { + .value = 0.0f, + .raw_value = 0.0f, + .properties = properties, + }; + if (TransformToButton(callback).value) { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<s16> distribution(-1000, 1000); + status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f; + } + break; + } + case Common::Input::InputType::Motion: + status = callback.motion_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type); + break; + } + SanitizeAnalog(status.accel.x, false); + SanitizeAnalog(status.accel.y, false); + SanitizeAnalog(status.accel.z, false); + SanitizeAnalog(status.gyro.x, false); + SanitizeAnalog(status.gyro.y, false); + SanitizeAnalog(status.gyro.z, false); + + return status; +} + +Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) { + Common::Input::StickStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Stick: + status = callback.stick_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type); + break; + } + + SanitizeStick(status.x, status.y, true); + const auto& properties_x = status.x.properties; + const auto& properties_y = status.y.properties; + const float x = status.x.value; + const float y = status.y.value; + + // Set directional buttons + status.right = x > properties_x.threshold; + status.left = x < -properties_x.threshold; + status.up = y > properties_y.threshold; + status.down = y < -properties_y.threshold; + + return status; +} + +Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) { + Common::Input::TouchStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Touch: + status = callback.touch_status; + break; + case Common::Input::InputType::Stick: + status.x = callback.stick_status.x; + status.y = callback.stick_status.y; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type); + break; + } + + SanitizeAnalog(status.x, true); + SanitizeAnalog(status.y, true); + float& x = status.x.value; + float& y = status.y.value; + + // Adjust if value is inverted + x = status.x.properties.inverted ? 1.0f + x : x; + y = status.y.properties.inverted ? 1.0f + y : y; + + // clamp value + x = std::clamp(x, 0.0f, 1.0f); + y = std::clamp(y, 0.0f, 1.0f); + + if (status.pressed.inverted) { + status.pressed.value = !status.pressed.value; + } + + return status; +} + +Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) { + Common::Input::TriggerStatus status{}; + float& raw_value = status.analog.raw_value; + bool calculate_button_value = true; + + switch (callback.type) { + case Common::Input::InputType::Analog: + status.analog.properties = callback.analog_status.properties; + raw_value = callback.analog_status.raw_value; + break; + case Common::Input::InputType::Button: + status.analog.properties.range = 1.0f; + status.analog.properties.inverted = callback.button_status.inverted; + raw_value = callback.button_status.value ? 1.0f : 0.0f; + break; + case Common::Input::InputType::Trigger: + status = callback.trigger_status; + calculate_button_value = false; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type); + break; + } + + SanitizeAnalog(status.analog, true); + const auto& properties = status.analog.properties; + float& value = status.analog.value; + + // Set button status + if (calculate_button_value) { + status.pressed.value = value > properties.threshold; + } + + // Adjust if value is inverted + value = properties.inverted ? 1.0f + value : value; + + // clamp value + value = std::clamp(value, 0.0f, 1.0f); + + return status; +} + +Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) { + Common::Input::AnalogStatus status{}; + + switch (callback.type) { + case Common::Input::InputType::Analog: + status.properties = callback.analog_status.properties; + status.raw_value = callback.analog_status.raw_value; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type); + break; + } + + SanitizeAnalog(status, false); + + // Adjust if value is inverted + status.value = status.properties.inverted ? -status.value : status.value; + + return status; +} + +void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { + const auto& properties = analog.properties; + float& raw_value = analog.raw_value; + float& value = analog.value; + + if (!std::isnormal(raw_value)) { + raw_value = 0; + } + + // Apply center offset + raw_value -= properties.offset; + + // Set initial values to be formated + value = raw_value; + + // Calculate vector size + const float r = std::abs(value); + + // Return zero if value is smaller than the deadzone + if (r <= properties.deadzone || properties.deadzone == 1.0f) { + analog.value = 0; + return; + } + + // Adjust range of value + const float deadzone_factor = + 1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone); + value = value * deadzone_factor / properties.range; + + // Invert direction if needed + if (properties.inverted) { + value = -value; + } + + // Clamp value + if (clamp_value) { + value = std::clamp(value, -1.0f, 1.0f); + } +} + +void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y, + bool clamp_value) { + const auto& properties_x = analog_x.properties; + const auto& properties_y = analog_y.properties; + float& raw_x = analog_x.raw_value; + float& raw_y = analog_y.raw_value; + float& x = analog_x.value; + float& y = analog_y.value; + + if (!std::isnormal(raw_x)) { + raw_x = 0; + } + if (!std::isnormal(raw_y)) { + raw_y = 0; + } + + // Apply center offset + raw_x += properties_x.offset; + raw_y += properties_y.offset; + + // Apply X scale correction from offset + if (std::abs(properties_x.offset) < 0.5f) { + if (raw_x > 0) { + raw_x /= 1 + properties_x.offset; + } else { + raw_x /= 1 - properties_x.offset; + } + } + + // Apply Y scale correction from offset + if (std::abs(properties_y.offset) < 0.5f) { + if (raw_y > 0) { + raw_y /= 1 + properties_y.offset; + } else { + raw_y /= 1 - properties_y.offset; + } + } + + // Invert direction if needed + raw_x = properties_x.inverted ? -raw_x : raw_x; + raw_y = properties_y.inverted ? -raw_y : raw_y; + + // Set initial values to be formated + x = raw_x; + y = raw_y; + + // Calculate vector size + float r = x * x + y * y; + r = std::sqrt(r); + + // TODO(German77): Use deadzone and range of both axis + + // Return zero if values are smaller than the deadzone + if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) { + x = 0; + y = 0; + return; + } + + // Adjust range of joystick + const float deadzone_factor = + 1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone); + x = x * deadzone_factor / properties_x.range; + y = y * deadzone_factor / properties_x.range; + r = r * deadzone_factor / properties_x.range; + + // Normalize joystick + if (clamp_value && r > 1.0f) { + x /= r; + y /= r; + } +} + +} // namespace Core::HID |