mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 10:00:27 +02:00
- New [vr] hand_tracking (default off, Quest only for now): the Quest launcher's Settings > VR and the headset panel's VR tab, under hand steering. Two XR_EXT_hand_tracking trackers are located every XR frame: with the controllers held the Quest builds the joints from their touch sensors (XR_EXT_hand_tracking_data_source's controller source), once they are put down from its cameras. The trackers exist only while the option and hand steering are on and also serve the runtime hand mesh; the extensions (plus XR_FB_hand_tracking_aim) are asked for when either is on at launch. - Aurora skins the runtime mesh with the joints themselves (tracked pose times inverse bind pose, no curl, no grip); runtimes with joints but no mesh get a skeleton; non-finite joints put only that hand back on its grip curl. AuroraCockpitHand carries the 26 seated-frame joints and radii. - The manifest declares horizonos.permission.HAND_TRACKING (and the deprecated com.oculus.permission.HAND_TRACKING), both normal permissions with no prompt on a Quest 3, and oculus.software.handtracking as optional; without it Horizon OS keeps the app controllers-only. Bare hands then drive khr/simple_controller, so on Android a hand whose squeeze action is inactive and select active is treated as bare: with the option off it presses nothing but the menu gesture, is not drawn and feeds no Wii Remote motion. - Interaction-profile changes and tracker sources are logged. The pure rules (grasp from finger flexion, bare latch, pinch gate, bare-hand buttons, flick) live in the OpenXR-free vr/openxr_hand_tracking.h with mkw_vr_hand_tracking_tests; the driving and flick parts are wired in the next commits. Docs: OPENXR.md, docs/quest-port.md, README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
255 lines
12 KiB
C++
255 lines
12 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#if defined(MKW_ENABLE_OPENXR)
|
|
|
|
#include "vr/camera_toggle.h"
|
|
#include "vr/openxr_driving.h"
|
|
#include "vr/openxr_hand_tracking.h"
|
|
#include "vr/openxr_runtime.h"
|
|
#include "vr/openxr_settings_panel.h"
|
|
#include "vr/openxr_wii_remote.h"
|
|
#include "vr/steering_wheel.h"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
namespace mkw::vr {
|
|
|
|
// The rectangle the game's picture occupies on whichever virtual screen is
|
|
// showing it, in the application reference space: the target the Wii Remote
|
|
// pointer is aimed at. The pose faces +Z with +X right and +Y up across the
|
|
// picture. Invalid when no screen can be pointed at (for instance a race with
|
|
// its 2D layer left stretched across the eyes).
|
|
struct OpenXRPointerScreen {
|
|
bool valid = false;
|
|
XrPosef pose{{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}};
|
|
float half_width_meters = 0.0f;
|
|
float half_height_meters = 0.0f;
|
|
};
|
|
|
|
// OpenXR action-based controller input.
|
|
//
|
|
// Quest Touch controllers are not visible to SDL's joystick layer (the OS does
|
|
// not expose them as HID gamepads), so a standalone headset build would have no
|
|
// input at all. This module syncs an OpenXR action set on the pacing thread and
|
|
// feeds a virtual SDL joystick (SDL_AttachVirtualJoystick) that Aurora's
|
|
// existing controller code opens and assigns to a port exactly like a physical
|
|
// pad. What the game then sees on that port depends on OpenXRControllerMode:
|
|
//
|
|
// Wii Remote (default): the port is served through KPAD as a Wii Remote with a
|
|
// Nunchuk, like DolphinXR's OpenXR Wii Remote. Every XR frame the aim and grip
|
|
// poses are located at the measured current time, turned into both
|
|
// accelerometers and the IR pointer (the right aim ray against the virtual
|
|
// screen the renderer is showing), and published through openxr_wii_remote.h.
|
|
// Buttons are adapted from DolphinXR's "OpenXR Wii Remote" profile (see
|
|
// RemoteButtons).
|
|
// The game's rumble drives both controllers' haptics.
|
|
//
|
|
// Gamepad: the virtual joystick is read through PAD as a GameCube controller,
|
|
// and every binding the settings overlay offers applies:
|
|
// right A / B -> gamepad South / East (GameCube A / B)
|
|
// left X / Y -> gamepad West / North (GameCube X / Y)
|
|
// index triggers -> left / right trigger axes
|
|
// grip squeezes -> left / right shoulder buttons
|
|
// left / right thumbstick-> left / right stick axes, clicks -> stick buttons
|
|
// left menu -> Start
|
|
//
|
|
// Both are bound for the Oculus Touch profile; khr/simple_controller gets
|
|
// select/menu and the poses so an unknown runtime still offers something.
|
|
//
|
|
// In the first-person cockpit (openxr_driving.h) the grip poses are located in
|
|
// the seated frame every frame. With hand steering on, a squeezed grip near the
|
|
// steering wheel or handlebar takes hold of it; while held, the wheel replaces
|
|
// the left stick's X axis in both presentations and that grip no longer reaches
|
|
// the game (a shoulder on the gamepad; as a Wii Remote the grips are unbound,
|
|
// so a hand on the wheel cannot hold down a button).
|
|
//
|
|
// Tracked hands ([vr] hand_tracking, with hand steering): two hand trackers
|
|
// (XR_EXT_hand_tracking) located every frame give the cockpit the hands' own
|
|
// joints, from the controllers' touch sensors while they are held and from the
|
|
// cameras once they are put down. On the Quest a hand that drives
|
|
// khr/simple_controller instead of the Touch profile has bare-hand buttons
|
|
// (openxr_hand_tracking.h): with the option off it presses nothing but the menu
|
|
// gesture, so the manifest's hand-tracking permission changes nothing for
|
|
// players who leave it off.
|
|
//
|
|
// A right-thumbstick click on its own toggles the first-person camera, as its
|
|
// F10 checkbox does (first_person_toggle_click).
|
|
//
|
|
// Left Y (both thumbsticks clicked together as a gamepad) opens the in-headset
|
|
// settings panel (openxr_settings_panel.h). While it is open, and until every
|
|
// button has been released after it closes, the game sees idle controllers: the
|
|
// panel button, the pointer and the triggers belong to the panel.
|
|
//
|
|
// Lifetime: Create after the session exists (attaches the action set, which
|
|
// OpenXR permits once per session), Sync once per xrWaitFrame, Idle while the
|
|
// session is not running, Destroy before the session is destroyed. All of them
|
|
// run on the XR pacing thread. The Wii Remote bridge is internally locked, so
|
|
// the game thread may read it concurrently, and the virtual gamepad is only
|
|
// published here: OpenXRApplyVirtualGamepad() performs the SDL writes on the
|
|
// game thread, keeping SDL's joystick lock off this thread entirely.
|
|
class OpenXRInput final {
|
|
public:
|
|
explicit OpenXRInput(OpenXRLogCallback logger = {});
|
|
~OpenXRInput();
|
|
|
|
OpenXRInput(const OpenXRInput&) = delete;
|
|
OpenXRInput& operator=(const OpenXRInput&) = delete;
|
|
|
|
// Creates the action set/actions/spaces and attaches them to the session.
|
|
// Returns false (with LastError set) if the runtime rejects the action set;
|
|
// the caller continues without controller input rather than failing VR.
|
|
bool Create(OpenXRRuntime& runtime);
|
|
void Destroy();
|
|
|
|
// xrSyncActions + state reads, then publishes to the virtual gamepad and
|
|
// the Wii Remote bridge. predicted_display_time is the frame's XrTime;
|
|
// screen is where the Wii Remote pointer can land this frame, and
|
|
// settings_panel where the settings panel is (its whole rectangle). seat is
|
|
// the immersive seated frame, invalid outside an immersive race.
|
|
void Sync(XrTime predicted_display_time, const OpenXRPointerScreen& screen,
|
|
const OpenXRPointerScreen& settings_panel, const driving::SeatFrame& seat);
|
|
|
|
// The cockpit state the last Sync published (also OpenXRReadDriving()).
|
|
const DrivingSnapshot& Driving() const noexcept { return m_driving; }
|
|
// The tracked hands' joints in the seated frame, as the last Sync located
|
|
// them; read on the pacing thread only.
|
|
const hand_tracking::HandJointFrame& HandJoints() const noexcept { return m_joint_frame; }
|
|
// A hand's tracker while tracked hands keep one, else XR_NULL_HANDLE.
|
|
XrHandTrackerEXT HandTracker(uint32_t hand) const noexcept {
|
|
return hand < kHands ? m_hand_trackers[hand] : XR_NULL_HANDLE;
|
|
}
|
|
|
|
// Publishes a remote with nothing held, at rest and not pointing, and stops
|
|
// the haptics, for frames without focused input.
|
|
void Idle();
|
|
|
|
// Rumble for the given hand (0 = left, 1 = right); amplitude 0..1.
|
|
void ApplyHaptic(uint32_t hand, float amplitude, XrDuration duration);
|
|
|
|
bool IsCreated() const noexcept { return m_created; }
|
|
bool HasVirtualGamepad() const noexcept { return m_joystick_id != 0; }
|
|
const std::string& LastError() const noexcept { return m_last_error; }
|
|
|
|
private:
|
|
static constexpr uint32_t kHands = 2;
|
|
|
|
bool CreateActions();
|
|
bool SuggestBindings();
|
|
void CreatePoseSpaces();
|
|
void DestroyPoseSpaces();
|
|
void LoadInputClock();
|
|
XrTime InputSampleTime(XrTime predicted_display_time) const;
|
|
bool AttachVirtualGamepad();
|
|
void DetachVirtualGamepad();
|
|
// `withheld` publishes a remote at rest with nothing held and no pointer,
|
|
// while still tracking motion so releasing it does not read as a jolt.
|
|
void PublishWiiRemote(XrTime input_time, const OpenXRPointerScreen& screen,
|
|
const std::array<wii_remote::HandInputs, kHands>& hands, uint32_t injected_buttons,
|
|
bool withheld);
|
|
void PublishSettingsPanel(XrTime input_time, const OpenXRPointerScreen& panel,
|
|
const settings_panel::Frame& frame);
|
|
// Hand steering: locates the grips in the seated frame, runs the wheel and
|
|
// hands its steering to `hands` before the game sees them.
|
|
void UpdateDriving(XrTime display_time, const driving::SeatFrame& seat,
|
|
std::array<wii_remote::HandInputs, kHands>& hands, bool withheld);
|
|
void ResetDriving();
|
|
// Tracked hands: the extension's functions at Create, the trackers as the
|
|
// settings ask for them, and both hands located for `time`, their joints
|
|
// in `seat` when it is valid.
|
|
void LoadHandTracking();
|
|
void UpdateHandTrackers();
|
|
void DestroyHandTrackers();
|
|
void LocateHands(XrTime time, const driving::SeatFrame& seat);
|
|
void LogInteractionProfiles();
|
|
void UpdateRumble();
|
|
void StopRumble();
|
|
bool Check(XrResult result, const char* operation);
|
|
void Log(OpenXRLogLevel level, const std::string& message) const noexcept;
|
|
|
|
OpenXRLogCallback m_logger;
|
|
OpenXRRuntime* m_runtime = nullptr;
|
|
XrActionSet m_action_set = XR_NULL_HANDLE;
|
|
XrAction m_thumbstick = XR_NULL_HANDLE;
|
|
XrAction m_thumbstick_click = XR_NULL_HANDLE;
|
|
XrAction m_trigger = XR_NULL_HANDLE;
|
|
XrAction m_squeeze = XR_NULL_HANDLE;
|
|
XrAction m_button_primary = XR_NULL_HANDLE; // A / X
|
|
XrAction m_button_secondary = XR_NULL_HANDLE; // B / Y
|
|
XrAction m_menu = XR_NULL_HANDLE;
|
|
XrAction m_aim_pose = XR_NULL_HANDLE;
|
|
XrAction m_grip_pose = XR_NULL_HANDLE;
|
|
XrAction m_haptic = XR_NULL_HANDLE;
|
|
XrPath m_hand_paths[kHands]{};
|
|
XrSpace m_aim_spaces[kHands]{};
|
|
XrSpace m_grip_spaces[kHands]{};
|
|
// xrConvertWin32PerformanceCounterToTimeKHR / xrConvertTimespecTimeToTimeKHR,
|
|
// when the runtime offers them; the input time falls back to display time.
|
|
PFN_xrVoidFunction m_convert_now_to_xr_time = nullptr;
|
|
wii_remote::MotionTracker m_motion[kHands];
|
|
wii_remote::PointerFilter m_pointer;
|
|
settings_panel::Controls m_panel_controls;
|
|
XrTime m_last_input_time = 0;
|
|
bool m_panel_select_held = false;
|
|
std::array<float, 2> m_horizon{1.0f, 0.0f};
|
|
bool m_haptics_active[kHands]{};
|
|
uint32_t m_joystick_id = 0; // SDL_JoystickID; 0 when detached
|
|
void* m_joystick = nullptr; // SDL_Joystick*
|
|
ClickToggle m_first_person_click;
|
|
SteeringWheel m_wheel;
|
|
WheelReferenceLatch m_wheel_reference;
|
|
driving::WheelVisual m_wheel_visual;
|
|
std::array<bool, kHands> m_wheel_held{};
|
|
XrTime m_wheel_time = 0;
|
|
bool m_wheel_uses_geometry = false;
|
|
bool m_wheel_bike = false;
|
|
DrivingSnapshot m_driving{};
|
|
|
|
// Tracked hands.
|
|
struct TrackedHand {
|
|
bool active = false; // joints located this frame
|
|
bool seated = false; // and written into m_joint_frame, in the seated frame
|
|
hand_tracking::Source source = hand_tracking::Source::None;
|
|
hand_tracking::JointPositions positions{}; // application space
|
|
// XR_FB_hand_tracking_aim: the runtime's own pinch and menu gesture.
|
|
bool aim_valid = false;
|
|
bool aim_pinching = false;
|
|
bool aim_menu = false;
|
|
bool aim_system_gesture = false;
|
|
};
|
|
PFN_xrCreateHandTrackerEXT m_create_hand_tracker = nullptr;
|
|
PFN_xrDestroyHandTrackerEXT m_destroy_hand_tracker = nullptr;
|
|
PFN_xrLocateHandJointsEXT m_locate_hand_joints = nullptr;
|
|
bool m_hand_data_source = false; // XR_EXT_hand_tracking_data_source
|
|
bool m_hand_aim = false; // XR_FB_hand_tracking_aim
|
|
XrHandTrackerEXT m_hand_trackers[kHands]{};
|
|
bool m_hand_trackers_failed = false;
|
|
bool m_logged_hand_restart = false;
|
|
std::array<TrackedHand, kHands> m_tracked_hands{};
|
|
hand_tracking::HandJointFrame m_joint_frame{};
|
|
std::array<hand_tracking::Source, kHands> m_logged_sources{hand_tracking::Source::None,
|
|
hand_tracking::Source::None};
|
|
// Per frame, from the actions: a controller is in the hand (its squeeze is
|
|
// bound), and the hand drives simple_controller (Android only).
|
|
std::array<bool, kHands> m_squeeze_active{};
|
|
std::array<bool, kHands> m_hand_driven{};
|
|
std::array<bool, kHands> m_pinch{};
|
|
uint64_t m_profile_serial = 0;
|
|
|
|
bool m_created = false;
|
|
bool m_logged_sync_failure = false;
|
|
bool m_logged_pointer = false;
|
|
std::string m_last_error;
|
|
};
|
|
|
|
// Game thread: writes the gamepad the pacing thread last published, if any.
|
|
// Does nothing when no OpenXR controllers are attached.
|
|
void OpenXRApplyVirtualGamepad() noexcept;
|
|
|
|
} // namespace mkw::vr
|
|
|
|
#endif // defined(MKW_ENABLE_OPENXR)
|