| | |
| | |
| |
|
| | #include <chrono> |
| | #include <cmath> |
| | #include "common/math_util.h" |
| | #include "common/settings.h" |
| | #include "input_common/helpers/stick_from_buttons.h" |
| |
|
| | namespace InputCommon { |
| |
|
| | class Stick final : public Common::Input::InputDevice { |
| | public: |
| | |
| | |
| | |
| | static constexpr float MAX_RANGE = 32766.0f / 32767.0f; |
| | static constexpr float TAU = Common::PI * 2.0f; |
| | |
| | static constexpr float APERTURE = TAU * 0.15f; |
| |
|
| | using Button = std::unique_ptr<Common::Input::InputDevice>; |
| |
|
| | Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, |
| | float modifier_scale_, float modifier_angle_) |
| | : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), |
| | right(std::move(right_)), modifier(std::move(modifier_)), updater(std::move(updater_)), |
| | modifier_scale(modifier_scale_), modifier_angle(modifier_angle_) { |
| | up->SetCallback({ |
| | .on_change = |
| | [this](const Common::Input::CallbackStatus& callback_) { |
| | UpdateUpButtonStatus(callback_); |
| | }, |
| | }); |
| | down->SetCallback({ |
| | .on_change = |
| | [this](const Common::Input::CallbackStatus& callback_) { |
| | UpdateDownButtonStatus(callback_); |
| | }, |
| | }); |
| | left->SetCallback({ |
| | .on_change = |
| | [this](const Common::Input::CallbackStatus& callback_) { |
| | UpdateLeftButtonStatus(callback_); |
| | }, |
| | }); |
| | right->SetCallback({ |
| | .on_change = |
| | [this](const Common::Input::CallbackStatus& callback_) { |
| | UpdateRightButtonStatus(callback_); |
| | }, |
| | }); |
| | modifier->SetCallback({ |
| | .on_change = |
| | [this](const Common::Input::CallbackStatus& callback_) { |
| | UpdateModButtonStatus(callback_); |
| | }, |
| | }); |
| | updater->SetCallback({ |
| | .on_change = [this](const Common::Input::CallbackStatus& callback_) { SoftUpdate(); }, |
| | }); |
| | last_x_axis_value = 0.0f; |
| | last_y_axis_value = 0.0f; |
| | } |
| |
|
| | bool IsAngleGreater(float old_angle, float new_angle) const { |
| | const float top_limit = new_angle + APERTURE; |
| | return (old_angle > new_angle && old_angle <= top_limit) || |
| | (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); |
| | } |
| |
|
| | bool IsAngleSmaller(float old_angle, float new_angle) const { |
| | const float bottom_limit = new_angle - APERTURE; |
| | return (old_angle >= bottom_limit && old_angle < new_angle) || |
| | (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); |
| | } |
| |
|
| | float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { |
| | float new_angle = angle; |
| |
|
| | auto time_difference = static_cast<float>( |
| | std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); |
| | time_difference /= 1000.0f; |
| | if (time_difference > 0.5f) { |
| | time_difference = 0.5f; |
| | } |
| |
|
| | if (IsAngleGreater(new_angle, goal_angle)) { |
| | new_angle -= modifier_angle * time_difference; |
| | if (new_angle < 0) { |
| | new_angle += TAU; |
| | } |
| | if (!IsAngleGreater(new_angle, goal_angle)) { |
| | return goal_angle; |
| | } |
| | } else if (IsAngleSmaller(new_angle, goal_angle)) { |
| | new_angle += modifier_angle * time_difference; |
| | if (new_angle >= TAU) { |
| | new_angle -= TAU; |
| | } |
| | if (!IsAngleSmaller(new_angle, goal_angle)) { |
| | return goal_angle; |
| | } |
| | } else { |
| | return goal_angle; |
| | } |
| | return new_angle; |
| | } |
| |
|
| | void SetGoalAngle(bool r, bool l, bool u, bool d) { |
| | |
| | if (r && !u && !d) { |
| | goal_angle = 0.0f; |
| | } |
| |
|
| | |
| | if (r && u && !d) { |
| | goal_angle = Common::PI * 0.25f; |
| | } |
| |
|
| | |
| | if (u && !l && !r) { |
| | goal_angle = Common::PI * 0.5f; |
| | } |
| |
|
| | |
| | if (l && u && !d) { |
| | goal_angle = Common::PI * 0.75f; |
| | } |
| |
|
| | |
| | if (l && !u && !d) { |
| | goal_angle = Common::PI; |
| | } |
| |
|
| | |
| | if (l && !u && d) { |
| | goal_angle = Common::PI * 1.25f; |
| | } |
| |
|
| | |
| | if (d && !l && !r) { |
| | goal_angle = Common::PI * 1.5f; |
| | } |
| |
|
| | |
| | if (r && !u && d) { |
| | goal_angle = Common::PI * 1.75f; |
| | } |
| | } |
| |
|
| | void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) { |
| | up_status = button_callback.button_status.value; |
| | UpdateStatus(); |
| | } |
| |
|
| | void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) { |
| | down_status = button_callback.button_status.value; |
| | UpdateStatus(); |
| | } |
| |
|
| | void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) { |
| | left_status = button_callback.button_status.value; |
| | UpdateStatus(); |
| | } |
| |
|
| | void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) { |
| | right_status = button_callback.button_status.value; |
| | UpdateStatus(); |
| | } |
| |
|
| | void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) { |
| | const auto& new_status = button_callback.button_status; |
| | const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value; |
| | modifier_status.toggle = new_status.toggle; |
| |
|
| | |
| | if (!modifier_status.toggle) { |
| | modifier_status.locked = false; |
| | if (modifier_status.value != new_button_value) { |
| | modifier_status.value = new_button_value; |
| | } |
| | } else { |
| | |
| | if (new_button_value && !modifier_status.locked) { |
| | modifier_status.locked = true; |
| | modifier_status.value = !modifier_status.value; |
| | } |
| |
|
| | |
| | if (!new_button_value && modifier_status.locked) { |
| | modifier_status.locked = false; |
| | } |
| | } |
| |
|
| | UpdateStatus(); |
| | } |
| |
|
| | void UpdateStatus() { |
| | bool r = right_status; |
| | bool l = left_status; |
| | bool u = up_status; |
| | bool d = down_status; |
| |
|
| | |
| | if (r && l) { |
| | r = false; |
| | l = false; |
| | } |
| | if (u && d) { |
| | u = false; |
| | d = false; |
| | } |
| |
|
| | |
| | if (r || l || u || d) { |
| | amplitude = modifier_status.value ? modifier_scale : MAX_RANGE; |
| | } else { |
| | amplitude = 0; |
| | } |
| |
|
| | const auto now = std::chrono::steady_clock::now(); |
| | const auto time_difference = static_cast<u64>( |
| | std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); |
| |
|
| | if (time_difference < 10) { |
| | |
| | SetGoalAngle(r, l, u, d); |
| | angle = goal_angle; |
| | } else { |
| | angle = GetAngle(now); |
| | SetGoalAngle(r, l, u, d); |
| | } |
| |
|
| | last_update = now; |
| | Common::Input::CallbackStatus status{ |
| | .type = Common::Input::InputType::Stick, |
| | .stick_status = GetStatus(), |
| | }; |
| | last_x_axis_value = status.stick_status.x.raw_value; |
| | last_y_axis_value = status.stick_status.y.raw_value; |
| | TriggerOnChange(status); |
| | } |
| |
|
| | void ForceUpdate() override { |
| | up->ForceUpdate(); |
| | down->ForceUpdate(); |
| | left->ForceUpdate(); |
| | right->ForceUpdate(); |
| | modifier->ForceUpdate(); |
| | } |
| |
|
| | void SoftUpdate() { |
| | Common::Input::CallbackStatus status{ |
| | .type = Common::Input::InputType::Stick, |
| | .stick_status = GetStatus(), |
| | }; |
| | if (last_x_axis_value == status.stick_status.x.raw_value && |
| | last_y_axis_value == status.stick_status.y.raw_value) { |
| | return; |
| | } |
| | last_x_axis_value = status.stick_status.x.raw_value; |
| | last_y_axis_value = status.stick_status.y.raw_value; |
| | TriggerOnChange(status); |
| | } |
| |
|
| | Common::Input::StickStatus GetStatus() const { |
| | Common::Input::StickStatus status{}; |
| | status.x.properties = properties; |
| | status.y.properties = properties; |
| |
|
| | if (Settings::values.emulate_analog_keyboard) { |
| | const auto now = std::chrono::steady_clock::now(); |
| | const float angle_ = GetAngle(now); |
| | status.x.raw_value = std::cos(angle_) * amplitude; |
| | status.y.raw_value = std::sin(angle_) * amplitude; |
| | return status; |
| | } |
| |
|
| | status.x.raw_value = std::cos(goal_angle) * amplitude; |
| | status.y.raw_value = std::sin(goal_angle) * amplitude; |
| | return status; |
| | } |
| |
|
| | private: |
| | static constexpr Common::Input::AnalogProperties properties{ |
| | .deadzone = 0.0f, |
| | .range = 1.0f, |
| | .threshold = 0.5f, |
| | .offset = 0.0f, |
| | .inverted = false, |
| | .toggle = false, |
| | }; |
| |
|
| | Button up; |
| | Button down; |
| | Button left; |
| | Button right; |
| | Button modifier; |
| | Button updater; |
| | float modifier_scale{}; |
| | float modifier_angle{}; |
| | float angle{}; |
| | float goal_angle{}; |
| | float amplitude{}; |
| | bool up_status{}; |
| | bool down_status{}; |
| | bool left_status{}; |
| | bool right_status{}; |
| | float last_x_axis_value{}; |
| | float last_y_axis_value{}; |
| | Common::Input::ButtonStatus modifier_status{}; |
| | std::chrono::time_point<std::chrono::steady_clock> last_update; |
| | }; |
| |
|
| | std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create( |
| | const Common::ParamPackage& params) { |
| | const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); |
| | auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine)); |
| | auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine)); |
| | auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine)); |
| | auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine)); |
| | auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine)); |
| | auto updater = Common::Input::CreateInputDeviceFromString("engine:updater,button:0"); |
| | auto modifier_scale = params.Get("modifier_scale", 0.5f); |
| | auto modifier_angle = params.Get("modifier_angle", 5.5f); |
| | return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left), |
| | std::move(right), std::move(modifier), std::move(updater), |
| | modifier_scale, modifier_angle); |
| | } |
| |
|
| | } |
| |
|