mirror of
https://github.com/thejerf/baijiSteamVR.git
synced 2026-10-18 16:01:35 +02:00
826 lines
28 KiB
C++
826 lines
28 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#ifdef ENABLE_VR
|
|
|
|
#include "InputCommon/ControllerInterface/OpenXR/OpenXR.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MathUtil.h"
|
|
#include "Common/Matrix.h"
|
|
#include "Common/VR/OpenXRInputState.h"
|
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
|
|
|
namespace ciface::OpenXR
|
|
{
|
|
namespace
|
|
{
|
|
constexpr std::string_view SOURCE_NAME = "OpenXR";
|
|
|
|
enum class Hand
|
|
{
|
|
Left = 0,
|
|
Right = 1,
|
|
};
|
|
|
|
Common::Quaternion ToQuaternion(const std::array<float, 4>& quat)
|
|
{
|
|
return {quat[3], quat[0], quat[1], quat[2]};
|
|
}
|
|
|
|
Common::Vec3 ToVec3(const std::array<float, 3>& vec)
|
|
{
|
|
return {vec[0], vec[1], vec[2]};
|
|
}
|
|
|
|
class OpenXRDevice final : public Core::Device
|
|
{
|
|
public:
|
|
explicit OpenXRDevice(ControllerInterface* controller_interface)
|
|
: m_controller_interface(*controller_interface)
|
|
{
|
|
AddHandInputs(Hand::Left);
|
|
AddHandInputs(Hand::Right);
|
|
AddOutput(new RumbleOutput(this, RumbleTarget::Both));
|
|
AddOutput(new RumbleOutput(this, RumbleTarget::Left));
|
|
AddOutput(new RumbleOutput(this, RumbleTarget::Right));
|
|
}
|
|
|
|
std::string GetName() const override { return "OpenXR Controller"; }
|
|
|
|
std::string GetSource() const override { return std::string(SOURCE_NAME); }
|
|
|
|
bool IsVirtualDevice() const override { return true; }
|
|
|
|
int GetSortPriority() const override { return -10; }
|
|
|
|
Core::DeviceRemoval UpdateInput() override
|
|
{
|
|
m_snapshot = Common::VR::OpenXRInputState::GetSnapshot();
|
|
const bool has_openxr_controller = m_snapshot.runtime_active &&
|
|
(m_snapshot.controllers[0].connected ||
|
|
m_snapshot.controllers[1].connected);
|
|
if (!has_openxr_controller)
|
|
{
|
|
if (const auto gamepad = FindSteamInputGamepad())
|
|
{
|
|
m_snapshot = MakeGamepadSnapshot(*gamepad);
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (m_last_sdl_sensor_log.time_since_epoch().count() == 0 ||
|
|
now - m_last_sdl_sensor_log >= std::chrono::seconds(1))
|
|
{
|
|
m_last_sdl_sensor_log = now;
|
|
const auto& left = m_snapshot.controllers[0];
|
|
const auto& right = m_snapshot.controllers[1];
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE,
|
|
"SDL gamepad {} mapped input: left[A={} B={} View={} System={} "
|
|
"dpad={}/{}/{}/{} trigger={:.2f} stick=({:.2f},{:.2f})] "
|
|
"right[A={} B={} X={} Y={} Menu={} System={} trigger={:.2f} "
|
|
"squeeze={:.2f} stick=({:.2f},{:.2f})]",
|
|
gamepad->GetQualifiedName(), left.primary_button, left.secondary_button,
|
|
left.view_button, left.system_button, left.dpad_up, left.dpad_down,
|
|
left.dpad_left, left.dpad_right, left.trigger_value, left.thumbstick_x,
|
|
left.thumbstick_y, right.primary_button, right.secondary_button,
|
|
right.frame_x_button, right.frame_y_button, right.menu_button,
|
|
right.system_button, right.trigger_value, right.squeeze_value,
|
|
right.thumbstick_x, right.thumbstick_y);
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE,
|
|
"SDL motion input {}: accel[U/D/L/R/F/B]={}/{}/{}/{}/{}/{} "
|
|
"values=({:.2f},{:.2f},{:.2f}) gyro[P+/P-/R+/R-/Y+/Y-]={}/{}/{}/{}/{}/{} "
|
|
"values=({:.2f},{:.2f},{:.2f})",
|
|
gamepad->GetQualifiedName(), gamepad->FindInput("Accel Up") != nullptr,
|
|
gamepad->FindInput("Accel Down") != nullptr,
|
|
gamepad->FindInput("Accel Left") != nullptr,
|
|
gamepad->FindInput("Accel Right") != nullptr,
|
|
gamepad->FindInput("Accel Forward") != nullptr,
|
|
gamepad->FindInput("Accel Backward") != nullptr,
|
|
GetInput(*gamepad, "Accel Right") - GetInput(*gamepad, "Accel Left"),
|
|
GetInput(*gamepad, "Accel Backward") - GetInput(*gamepad, "Accel Forward"),
|
|
GetInput(*gamepad, "Accel Up") - GetInput(*gamepad, "Accel Down"),
|
|
gamepad->FindInput("Gyro Pitch Up") != nullptr,
|
|
gamepad->FindInput("Gyro Pitch Down") != nullptr,
|
|
gamepad->FindInput("Gyro Roll Left") != nullptr,
|
|
gamepad->FindInput("Gyro Roll Right") != nullptr,
|
|
gamepad->FindInput("Gyro Yaw Left") != nullptr,
|
|
gamepad->FindInput("Gyro Yaw Right") != nullptr,
|
|
GetInput(*gamepad, "Gyro Pitch Up") - GetInput(*gamepad, "Gyro Pitch Down"),
|
|
GetInput(*gamepad, "Gyro Roll Left") - GetInput(*gamepad, "Gyro Roll Right"),
|
|
GetInput(*gamepad, "Gyro Yaw Left") - GetInput(*gamepad, "Gyro Yaw Right"));
|
|
}
|
|
std::lock_guard lock(m_gamepad_mutex);
|
|
m_steam_input_gamepad = gamepad;
|
|
if (!m_gamepad_logged)
|
|
{
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "OpenXR: Using SDL gamepad '{}' for Steam Frame input.",
|
|
gamepad->GetName());
|
|
m_gamepad_logged = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::lock_guard lock(m_gamepad_mutex);
|
|
m_steam_input_gamepad.reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::lock_guard lock(m_gamepad_mutex);
|
|
m_steam_input_gamepad.reset();
|
|
if (!m_openxr_logged)
|
|
{
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "OpenXR: Using OpenXR controller actions for input.");
|
|
m_openxr_logged = true;
|
|
}
|
|
}
|
|
|
|
UpdateMotionState(Hand::Left);
|
|
UpdateMotionState(Hand::Right);
|
|
return Core::DeviceRemoval::Keep;
|
|
}
|
|
|
|
const Common::VR::OpenXRControllerState& GetControllerState(Hand hand) const
|
|
{
|
|
return m_snapshot.controllers[static_cast<size_t>(hand)];
|
|
}
|
|
|
|
struct MotionState
|
|
{
|
|
Common::Vec3 acceleration{};
|
|
Common::Vec3 gyroscope{};
|
|
bool acceleration_valid = false;
|
|
bool gyroscope_valid = false;
|
|
};
|
|
|
|
const MotionState& GetMotionState(Hand hand) const
|
|
{
|
|
return m_motion_state[static_cast<size_t>(hand)];
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<ciface::Core::Device> FindSteamInputGamepad() const
|
|
{
|
|
const auto devices = m_controller_interface.GetAllDevices();
|
|
|
|
// In Flat Vulkan mode Steam Input presents the Frame controllers as an SDL gamepad. This
|
|
// Frame-only build deliberately binds the first SDL gamepad to its single logical controller.
|
|
for (const auto& device : devices)
|
|
{
|
|
if (device->GetSource() == "SDL" && device->FindInput("Button S") &&
|
|
device->FindInput("Left X+") && device->FindInput("Right X+"))
|
|
{
|
|
return device;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static ControlState GetInput(const ciface::Core::Device& device, std::string_view name)
|
|
{
|
|
for (const auto* input : device.Inputs())
|
|
{
|
|
if (input->IsMatchingName(name))
|
|
return input->GetState();
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
static Common::VR::OpenXRInputSnapshot MakeGamepadSnapshot(const ciface::Core::Device& device)
|
|
{
|
|
Common::VR::OpenXRInputSnapshot snapshot{};
|
|
auto& left = snapshot.controllers[0];
|
|
auto& right = snapshot.controllers[1];
|
|
left.connected = true;
|
|
right.connected = true;
|
|
|
|
// SDL's standardized gamepad layout is the Steam Input compatibility layout for Flat mode.
|
|
left.primary_button = GetInput(device, "Button X") > 0.5;
|
|
left.secondary_button = GetInput(device, "Button Y") > 0.5;
|
|
left.view_button = GetInput(device, "Back") > 0.5;
|
|
left.system_button = GetInput(device, "Guide") > 0.5;
|
|
left.dpad_up = GetInput(device, "Pad N") > 0.5;
|
|
left.dpad_down = GetInput(device, "Pad S") > 0.5;
|
|
left.dpad_left = GetInput(device, "Pad W") > 0.5;
|
|
left.dpad_right = GetInput(device, "Pad E") > 0.5;
|
|
left.bumper_button = GetInput(device, "Shoulder L") > 0.5;
|
|
left.squeeze_button = GetInput(device, "Paddle 2") > 0.5;
|
|
left.squeeze_value = left.squeeze_button ? 1.0f : 0.0f;
|
|
left.trigger_value = static_cast<float>(std::clamp(GetInput(device, "Trigger L"), 0.0, 1.0));
|
|
left.trigger_button = left.trigger_value > 0.5f;
|
|
left.thumbstick_x = static_cast<float>(GetInput(device, "Left X+") -
|
|
GetInput(device, "Left X-"));
|
|
left.thumbstick_y = static_cast<float>(GetInput(device, "Left Y+") -
|
|
GetInput(device, "Left Y-"));
|
|
left.thumbstick_button = GetInput(device, "Thumb L") > 0.5;
|
|
|
|
right.primary_button = GetInput(device, "Button A") > 0.5;
|
|
right.secondary_button = GetInput(device, "Button B") > 0.5;
|
|
right.frame_x_button = GetInput(device, "Button X") > 0.5;
|
|
right.frame_y_button = GetInput(device, "Button Y") > 0.5;
|
|
right.menu_button = GetInput(device, "Start") > 0.5;
|
|
right.system_button = GetInput(device, "Guide") > 0.5;
|
|
right.bumper_button = GetInput(device, "Shoulder R") > 0.5;
|
|
right.squeeze_button = GetInput(device, "Paddle 1") > 0.5;
|
|
right.squeeze_value = right.squeeze_button ? 1.0f : 0.0f;
|
|
right.trigger_value = static_cast<float>(std::clamp(GetInput(device, "Trigger R"), 0.0, 1.0));
|
|
right.trigger_button = right.trigger_value > 0.5f;
|
|
right.thumbstick_x = static_cast<float>(GetInput(device, "Right X+") -
|
|
GetInput(device, "Right X-"));
|
|
right.thumbstick_y = static_cast<float>(GetInput(device, "Right Y+") -
|
|
GetInput(device, "Right Y-"));
|
|
right.thumbstick_button = GetInput(device, "Thumb R") > 0.5;
|
|
return snapshot;
|
|
}
|
|
|
|
enum class RumbleTarget
|
|
{
|
|
Both,
|
|
Left,
|
|
Right,
|
|
};
|
|
|
|
class RumbleOutput final : public Core::Device::Output
|
|
{
|
|
public:
|
|
RumbleOutput(OpenXRDevice* device, RumbleTarget target) : m_device(*device), m_target(target) {}
|
|
|
|
std::string GetName() const override
|
|
{
|
|
switch (m_target)
|
|
{
|
|
case RumbleTarget::Both:
|
|
return "Motor";
|
|
case RumbleTarget::Left:
|
|
return "Motor Left";
|
|
case RumbleTarget::Right:
|
|
return "Motor Right";
|
|
}
|
|
return "Motor";
|
|
}
|
|
|
|
void SetState(ControlState state) override { m_device.SetRumbleState(state, m_target); }
|
|
|
|
private:
|
|
OpenXRDevice& m_device;
|
|
RumbleTarget m_target;
|
|
};
|
|
|
|
std::string GetHandPrefix(Hand hand) const { return hand == Hand::Left ? "Left" : "Right"; }
|
|
|
|
void SetRumbleState(ControlState state, RumbleTarget target)
|
|
{
|
|
const float amplitude = std::clamp(static_cast<float>(state), 0.0f, 1.0f);
|
|
std::shared_ptr<ciface::Core::Device> gamepad;
|
|
{
|
|
std::lock_guard lock(m_gamepad_mutex);
|
|
gamepad = m_steam_input_gamepad;
|
|
}
|
|
|
|
if (gamepad)
|
|
{
|
|
const auto set_output = [gamepad, amplitude](std::string_view name) {
|
|
if (auto* const output = gamepad->FindOutput(name))
|
|
{
|
|
output->SetState(amplitude);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
switch (target)
|
|
{
|
|
case RumbleTarget::Both:
|
|
if (!set_output("Motor"))
|
|
{
|
|
set_output("Motor L");
|
|
set_output("Motor R");
|
|
}
|
|
break;
|
|
case RumbleTarget::Left:
|
|
if (!set_output("Motor L"))
|
|
set_output("Motor");
|
|
break;
|
|
case RumbleTarget::Right:
|
|
if (!set_output("Motor R"))
|
|
set_output("Motor");
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (target)
|
|
{
|
|
case RumbleTarget::Both:
|
|
Common::VR::OpenXRInputState::SetRumble(amplitude);
|
|
break;
|
|
case RumbleTarget::Left:
|
|
Common::VR::OpenXRInputState::SetRumbleForHand(0, amplitude);
|
|
break;
|
|
case RumbleTarget::Right:
|
|
Common::VR::OpenXRInputState::SetRumbleForHand(1, amplitude);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AddHandInputs(Hand hand)
|
|
{
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Primary));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Secondary));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Menu));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::System));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Bumper));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Trigger));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Squeeze));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::Thumbstick));
|
|
if (hand == Hand::Left)
|
|
{
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::View));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::DpadUp));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::DpadDown));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::DpadLeft));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::DpadRight));
|
|
}
|
|
else
|
|
{
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::FrameX));
|
|
AddInput(new DigitalInput(this, hand, DigitalControl::FrameY));
|
|
}
|
|
AddInput(new AnalogInput(this, hand, AnalogControl::Trigger));
|
|
AddInput(new AnalogInput(this, hand, AnalogControl::Squeeze));
|
|
AddInput(new AxisInput(this, hand, AxisControl::ThumbstickX, false));
|
|
AddInput(new AxisInput(this, hand, AxisControl::ThumbstickX, true));
|
|
AddInput(new AxisInput(this, hand, AxisControl::ThumbstickY, false));
|
|
AddInput(new AxisInput(this, hand, AxisControl::ThumbstickY, true));
|
|
AddMotionInputs(hand);
|
|
}
|
|
|
|
enum class DigitalControl
|
|
{
|
|
Primary,
|
|
Secondary,
|
|
Menu,
|
|
System,
|
|
View,
|
|
Bumper,
|
|
DpadUp,
|
|
DpadDown,
|
|
DpadLeft,
|
|
DpadRight,
|
|
FrameX,
|
|
FrameY,
|
|
Trigger,
|
|
Squeeze,
|
|
Thumbstick,
|
|
};
|
|
|
|
enum class AnalogControl
|
|
{
|
|
Trigger,
|
|
Squeeze,
|
|
SqueezeForce,
|
|
};
|
|
|
|
enum class AxisControl
|
|
{
|
|
ThumbstickX,
|
|
ThumbstickY,
|
|
};
|
|
|
|
enum class MotionControl
|
|
{
|
|
AccelUp,
|
|
AccelDown,
|
|
AccelLeft,
|
|
AccelRight,
|
|
AccelForward,
|
|
AccelBackward,
|
|
GyroPitchUp,
|
|
GyroPitchDown,
|
|
GyroRollLeft,
|
|
GyroRollRight,
|
|
GyroYawLeft,
|
|
GyroYawRight,
|
|
};
|
|
|
|
class DigitalInput final : public Core::Device::Input
|
|
{
|
|
public:
|
|
DigitalInput(const OpenXRDevice* device, Hand hand, DigitalControl control)
|
|
: m_device(*device), m_hand(hand), m_control(control)
|
|
{
|
|
}
|
|
|
|
std::string GetName() const override
|
|
{
|
|
const std::string prefix = m_device.GetHandPrefix(m_hand);
|
|
switch (m_control)
|
|
{
|
|
case DigitalControl::Primary:
|
|
return prefix + (m_hand == Hand::Left ? " Button X" : " Button A");
|
|
case DigitalControl::Secondary:
|
|
return prefix + (m_hand == Hand::Left ? " Button Y" : " Button B");
|
|
case DigitalControl::Menu:
|
|
return prefix + " Button Menu";
|
|
case DigitalControl::System:
|
|
return prefix + " Button System";
|
|
case DigitalControl::View:
|
|
return prefix + " Button View";
|
|
case DigitalControl::Bumper:
|
|
return prefix + " Button Bumper";
|
|
case DigitalControl::DpadUp:
|
|
return prefix + " D-Pad Up";
|
|
case DigitalControl::DpadDown:
|
|
return prefix + " D-Pad Down";
|
|
case DigitalControl::DpadLeft:
|
|
return prefix + " D-Pad Left";
|
|
case DigitalControl::DpadRight:
|
|
return prefix + " D-Pad Right";
|
|
case DigitalControl::FrameX:
|
|
return prefix + " Button X";
|
|
case DigitalControl::FrameY:
|
|
return prefix + " Button Y";
|
|
case DigitalControl::Trigger:
|
|
return prefix + " Button Trigger";
|
|
case DigitalControl::Squeeze:
|
|
return prefix + " Button Squeeze";
|
|
case DigitalControl::Thumbstick:
|
|
return prefix + " Button Thumbstick";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
ControlState GetState() const override
|
|
{
|
|
const auto& state = m_device.GetControllerState(m_hand);
|
|
if (!state.connected)
|
|
return 0.0;
|
|
|
|
switch (m_control)
|
|
{
|
|
case DigitalControl::Primary:
|
|
return state.primary_button ? 1.0 : 0.0;
|
|
case DigitalControl::Secondary:
|
|
return state.secondary_button ? 1.0 : 0.0;
|
|
case DigitalControl::Menu:
|
|
// Keep the old left-menu control name as a hidden alias for Frame's left View button.
|
|
// Existing hotkey configs used Left Button Menu for Stop.
|
|
return (m_hand == Hand::Left ? state.view_button : state.menu_button) ? 1.0 : 0.0;
|
|
case DigitalControl::System:
|
|
return state.system_button ? 1.0 : 0.0;
|
|
case DigitalControl::View:
|
|
return state.view_button ? 1.0 : 0.0;
|
|
case DigitalControl::Bumper:
|
|
return state.bumper_button ? 1.0 : 0.0;
|
|
case DigitalControl::DpadUp:
|
|
return state.dpad_up ? 1.0 : 0.0;
|
|
case DigitalControl::DpadDown:
|
|
return state.dpad_down ? 1.0 : 0.0;
|
|
case DigitalControl::DpadLeft:
|
|
return state.dpad_left ? 1.0 : 0.0;
|
|
case DigitalControl::DpadRight:
|
|
return state.dpad_right ? 1.0 : 0.0;
|
|
case DigitalControl::FrameX:
|
|
return state.frame_x_button ? 1.0 : 0.0;
|
|
case DigitalControl::FrameY:
|
|
return state.frame_y_button ? 1.0 : 0.0;
|
|
case DigitalControl::Trigger:
|
|
return state.trigger_button ? 1.0 : 0.0;
|
|
case DigitalControl::Squeeze:
|
|
return state.squeeze_button ? 1.0 : 0.0;
|
|
case DigitalControl::Thumbstick:
|
|
return state.thumbstick_button ? 1.0 : 0.0;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
bool IsHidden() const override
|
|
{
|
|
// Frame has a left D-pad and View button, not the left-hand X/Y/Menu controls in Touch.
|
|
return m_hand == Hand::Left &&
|
|
(m_control == DigitalControl::Primary || m_control == DigitalControl::Secondary ||
|
|
m_control == DigitalControl::Menu);
|
|
}
|
|
|
|
private:
|
|
const OpenXRDevice& m_device;
|
|
const Hand m_hand;
|
|
const DigitalControl m_control;
|
|
};
|
|
|
|
class AnalogInput final : public Core::Device::Input
|
|
{
|
|
public:
|
|
AnalogInput(const OpenXRDevice* device, Hand hand, AnalogControl control)
|
|
: m_device(*device), m_hand(hand), m_control(control)
|
|
{
|
|
}
|
|
|
|
std::string GetName() const override
|
|
{
|
|
const std::string prefix = m_device.GetHandPrefix(m_hand);
|
|
switch (m_control)
|
|
{
|
|
case AnalogControl::Trigger:
|
|
return prefix + " Trigger";
|
|
case AnalogControl::Squeeze:
|
|
return prefix + " Squeeze";
|
|
case AnalogControl::SqueezeForce:
|
|
return prefix + " Squeeze Force";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool IsHidden() const override { return m_control == AnalogControl::Squeeze; }
|
|
|
|
ControlState GetState() const override
|
|
{
|
|
const auto& state = m_device.GetControllerState(m_hand);
|
|
if (!state.connected)
|
|
return 0.0;
|
|
|
|
switch (m_control)
|
|
{
|
|
case AnalogControl::Trigger:
|
|
return state.trigger_value;
|
|
case AnalogControl::Squeeze:
|
|
return state.squeeze_value;
|
|
case AnalogControl::SqueezeForce:
|
|
return state.squeeze_force;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
private:
|
|
const OpenXRDevice& m_device;
|
|
const Hand m_hand;
|
|
const AnalogControl m_control;
|
|
};
|
|
|
|
class AxisInput final : public Core::Device::Input
|
|
{
|
|
public:
|
|
AxisInput(const OpenXRDevice* device, Hand hand, AxisControl axis, bool positive)
|
|
: m_device(*device), m_hand(hand), m_axis(axis), m_positive(positive)
|
|
{
|
|
}
|
|
|
|
std::string GetName() const override
|
|
{
|
|
const std::string prefix = m_device.GetHandPrefix(m_hand);
|
|
const char axis_char = m_axis == AxisControl::ThumbstickX ? 'X' : 'Y';
|
|
return prefix + " Thumbstick " + axis_char + (m_positive ? '+' : '-');
|
|
}
|
|
|
|
ControlState GetState() const override
|
|
{
|
|
const auto& state = m_device.GetControllerState(m_hand);
|
|
if (!state.connected)
|
|
return 0.0;
|
|
|
|
const float value =
|
|
m_axis == AxisControl::ThumbstickX ? state.thumbstick_x : state.thumbstick_y;
|
|
return m_positive ? std::max<float>(0.0f, value) : std::max<float>(0.0f, -value);
|
|
}
|
|
|
|
private:
|
|
const OpenXRDevice& m_device;
|
|
const Hand m_hand;
|
|
const AxisControl m_axis;
|
|
const bool m_positive;
|
|
};
|
|
|
|
class MotionInput final : public Core::Device::Input
|
|
{
|
|
public:
|
|
MotionInput(const OpenXRDevice* device, Hand hand, MotionControl control)
|
|
: m_device(*device), m_hand(hand), m_control(control)
|
|
{
|
|
}
|
|
|
|
std::string GetName() const override
|
|
{
|
|
const std::string prefix = m_device.GetHandPrefix(m_hand);
|
|
switch (m_control)
|
|
{
|
|
case MotionControl::AccelUp:
|
|
return prefix + " Raw Accel Up";
|
|
case MotionControl::AccelDown:
|
|
return prefix + " Raw Accel Down";
|
|
case MotionControl::AccelLeft:
|
|
return prefix + " Raw Accel Left";
|
|
case MotionControl::AccelRight:
|
|
return prefix + " Raw Accel Right";
|
|
case MotionControl::AccelForward:
|
|
return prefix + " Raw Accel Forward";
|
|
case MotionControl::AccelBackward:
|
|
return prefix + " Raw Accel Backward";
|
|
case MotionControl::GyroPitchUp:
|
|
return prefix + " Raw Gyro Pitch Up";
|
|
case MotionControl::GyroPitchDown:
|
|
return prefix + " Raw Gyro Pitch Down";
|
|
case MotionControl::GyroRollLeft:
|
|
return prefix + " Raw Gyro Roll Left";
|
|
case MotionControl::GyroRollRight:
|
|
return prefix + " Raw Gyro Roll Right";
|
|
case MotionControl::GyroYawLeft:
|
|
return prefix + " Raw Gyro Yaw Left";
|
|
case MotionControl::GyroYawRight:
|
|
return prefix + " Raw Gyro Yaw Right";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool IsDetectable() const override { return false; }
|
|
bool IsHidden() const override { return true; }
|
|
|
|
ControlState GetState() const override
|
|
{
|
|
const auto& motion = m_device.GetMotionState(m_hand);
|
|
constexpr float accel_scale = float(MathUtil::GRAVITY_ACCELERATION);
|
|
constexpr float gyro_scale = float(MathUtil::PI);
|
|
|
|
const auto normalize = [](float value, float scale) -> ControlState {
|
|
return std::clamp<ControlState>(value / scale, 0.0, 1.0);
|
|
};
|
|
|
|
switch (m_control)
|
|
{
|
|
case MotionControl::AccelUp:
|
|
return motion.acceleration_valid ? normalize(motion.acceleration.z, accel_scale) : 0.0;
|
|
case MotionControl::AccelDown:
|
|
return motion.acceleration_valid ? normalize(-motion.acceleration.z, accel_scale) : 0.0;
|
|
case MotionControl::AccelLeft:
|
|
return motion.acceleration_valid ? normalize(motion.acceleration.x, accel_scale) : 0.0;
|
|
case MotionControl::AccelRight:
|
|
return motion.acceleration_valid ? normalize(-motion.acceleration.x, accel_scale) : 0.0;
|
|
case MotionControl::AccelForward:
|
|
return motion.acceleration_valid ? normalize(-motion.acceleration.y, accel_scale) : 0.0;
|
|
case MotionControl::AccelBackward:
|
|
return motion.acceleration_valid ? normalize(motion.acceleration.y, accel_scale) : 0.0;
|
|
case MotionControl::GyroPitchUp:
|
|
return motion.gyroscope_valid ? normalize(motion.gyroscope.x, gyro_scale) : 0.0;
|
|
case MotionControl::GyroPitchDown:
|
|
return motion.gyroscope_valid ? normalize(-motion.gyroscope.x, gyro_scale) : 0.0;
|
|
case MotionControl::GyroRollLeft:
|
|
return motion.gyroscope_valid ? normalize(motion.gyroscope.y, gyro_scale) : 0.0;
|
|
case MotionControl::GyroRollRight:
|
|
return motion.gyroscope_valid ? normalize(-motion.gyroscope.y, gyro_scale) : 0.0;
|
|
case MotionControl::GyroYawLeft:
|
|
return motion.gyroscope_valid ? normalize(motion.gyroscope.z, gyro_scale) : 0.0;
|
|
case MotionControl::GyroYawRight:
|
|
return motion.gyroscope_valid ? normalize(-motion.gyroscope.z, gyro_scale) : 0.0;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
private:
|
|
const OpenXRDevice& m_device;
|
|
const Hand m_hand;
|
|
const MotionControl m_control;
|
|
};
|
|
|
|
void AddMotionInputs(Hand hand)
|
|
{
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelUp));
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelDown));
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelLeft));
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelRight));
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelForward));
|
|
AddInput(new MotionInput(this, hand, MotionControl::AccelBackward));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroPitchUp));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroPitchDown));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroRollLeft));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroRollRight));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroYawLeft));
|
|
AddInput(new MotionInput(this, hand, MotionControl::GyroYawRight));
|
|
}
|
|
|
|
struct VelocityHistory
|
|
{
|
|
bool has_sample = false;
|
|
Common::Vec3 previous_velocity{};
|
|
std::chrono::steady_clock::time_point previous_time{};
|
|
};
|
|
|
|
void UpdateMotionState(Hand hand)
|
|
{
|
|
const size_t hand_index = static_cast<size_t>(hand);
|
|
auto& motion = m_motion_state[hand_index];
|
|
motion = {};
|
|
|
|
const auto& controller = GetControllerState(hand);
|
|
const bool has_grip_pose = controller.grip_pose.valid;
|
|
const bool has_aim_pose = controller.aim_pose.valid;
|
|
if (!controller.connected || (!has_grip_pose && !has_aim_pose))
|
|
{
|
|
m_velocity_history[hand_index].has_sample = false;
|
|
return;
|
|
}
|
|
|
|
const Common::Quaternion reference_orientation =
|
|
(has_grip_pose ? ToQuaternion(controller.grip_pose.orientation) :
|
|
ToQuaternion(controller.aim_pose.orientation))
|
|
.Normalized();
|
|
const Common::Matrix33 world_to_local =
|
|
Common::Matrix33::FromQuaternion(reference_orientation).Transposed();
|
|
|
|
Common::Vec3 relative_acceleration{};
|
|
auto& velocity_history = m_velocity_history[hand_index];
|
|
if (controller.grip_velocity.linear_valid)
|
|
{
|
|
const Common::Vec3 current_velocity = ToVec3(controller.grip_velocity.linear);
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (velocity_history.has_sample)
|
|
{
|
|
const float dt = std::chrono::duration_cast<std::chrono::duration<float>>(
|
|
now - velocity_history.previous_time)
|
|
.count();
|
|
if (dt > 0.001f)
|
|
{
|
|
const Common::Vec3 world_acceleration =
|
|
(current_velocity - velocity_history.previous_velocity) / dt;
|
|
relative_acceleration = world_to_local * world_acceleration;
|
|
}
|
|
}
|
|
|
|
velocity_history.has_sample = true;
|
|
velocity_history.previous_velocity = current_velocity;
|
|
velocity_history.previous_time = now;
|
|
}
|
|
else
|
|
{
|
|
velocity_history.has_sample = false;
|
|
}
|
|
|
|
const auto get_matrix = [&world_to_local](int row, int col) {
|
|
return world_to_local.data[row * 3 + col];
|
|
};
|
|
float gx = -get_matrix(0, 1);
|
|
float gz = get_matrix(1, 1);
|
|
float gy = get_matrix(2, 1);
|
|
|
|
gx -= relative_acceleration.x / float(MathUtil::GRAVITY_ACCELERATION);
|
|
gz += relative_acceleration.y / float(MathUtil::GRAVITY_ACCELERATION);
|
|
gy += relative_acceleration.z / float(MathUtil::GRAVITY_ACCELERATION);
|
|
|
|
motion.acceleration = Common::Vec3(gx, gy, gz) * float(MathUtil::GRAVITY_ACCELERATION);
|
|
motion.acceleration_valid = true;
|
|
|
|
if (controller.grip_velocity.angular_valid)
|
|
{
|
|
const Common::Vec3 world_angular_velocity = ToVec3(controller.grip_velocity.angular);
|
|
const Common::Vec3 local_angular_velocity = world_to_local * world_angular_velocity;
|
|
motion.gyroscope = Common::Vec3(local_angular_velocity.x, local_angular_velocity.z,
|
|
local_angular_velocity.y);
|
|
motion.gyroscope_valid = true;
|
|
}
|
|
}
|
|
|
|
Common::VR::OpenXRInputSnapshot m_snapshot{};
|
|
ControllerInterface& m_controller_interface;
|
|
std::mutex m_gamepad_mutex;
|
|
std::shared_ptr<ciface::Core::Device> m_steam_input_gamepad;
|
|
bool m_gamepad_logged = false;
|
|
bool m_openxr_logged = false;
|
|
std::chrono::steady_clock::time_point m_last_sdl_sensor_log{};
|
|
std::array<MotionState, 2> m_motion_state{};
|
|
std::array<VelocityHistory, 2> m_velocity_history{};
|
|
};
|
|
|
|
class InputBackend final : public ciface::InputBackend
|
|
{
|
|
public:
|
|
using ciface::InputBackend::InputBackend;
|
|
|
|
void PopulateDevices() override
|
|
{
|
|
GetControllerInterface().RemoveDevice(
|
|
[](const auto* dev) { return dev->GetSource() == std::string(SOURCE_NAME); });
|
|
|
|
GetControllerInterface().AddDevice(std::make_shared<OpenXRDevice>(&GetControllerInterface()));
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
|
|
{
|
|
return std::make_unique<InputBackend>(controller_interface);
|
|
}
|
|
} // namespace ciface::OpenXR
|
|
|
|
#endif // ENABLE_VR
|