Pose the cockpit hands from the headset's hand tracking

- 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>
This commit is contained in:
iChris4andClaude Opus 5.5 committed 2026-09-25 00:05:48 +02:00
1 parent c990c595f0
commit a21e22b605
24 files changed
+1549 -49

No files matched your search

+10
View File
@@ -11,6 +11,11 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- The Khronos loader brokers the system OpenXR runtime through this permission. -->
<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM" />
<!-- Tracked hands ([vr] hand_tracking): a normal permission, granted at install without a
prompt. horizonos is the current name; Horizon OS logs the com.oculus one as deprecated,
which older builds still need. -->
<uses-permission android:name="horizonos.permission.HAND_TRACKING" />
<uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
<queries>
<provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
@@ -29,6 +34,11 @@
<!-- XR_FB_passthrough around the menu screen: without this flag Horizon OS keeps the app
passthrough-disabled and the passthrough layer composites nothing. No camera permission. -->
<uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="false" />
<!-- Hands or controllers: without this flag Horizon OS keeps the app controllers-only ("hands
are disabled by manifest flag") and gives it no tracked hands. With it, putting the
controllers down drives khr/simple_controller from the hands; with tracked hands off
they press nothing but the palm-up menu gesture (openxr_hand_tracking.h). -->
<uses-feature android:name="oculus.software.handtracking" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
@@ -189,6 +189,14 @@ class SettingsPage(
write = { c, value -> c.setBool("vr", "hand_steering", value) },
enabledIf = cockpit,
)
// The hands follow the headset's hand tracking; kVrHandTrackingDefault is off. The
// hands are only drawn while they can steer, so it goes with hand steering.
toggle(
R.string.vr_hand_tracking, R.string.vr_hand_tracking_helper,
read = { it.bool("vr", "hand_tracking") ?: false },
write = { c, value -> c.setBool("vr", "hand_tracking", value) },
enabledIf = { c -> cockpit(c) && (c.bool("vr", "hand_steering") ?: true) },
)
slider(
R.string.vr_lean_back, R.string.vr_lean_back_helper, -45.0, 45.0, 1.0,
read = { number(it, "vr", "lean_back_degrees", -45.0, 45.0, 0.0) },
@@ -533,6 +533,8 @@
<string name="vr_seat_custom">Custom</string>
<string name="vr_hand_steering">Hand steering</string>
<string name="vr_hand_steering_helper">In the cockpit, squeeze a grip near the steering wheel or handlebar to grab it, and turn it to steer. Releasing both grips gives steering back to the stick. Hand steering by heurazy.</string>
<string name="vr_hand_tracking">Tracked hands</string>
<string name="vr_hand_tracking_helper">The cockpit hands follow your own. Holding the controllers, the fingers follow their touch sensors; put the controllers down and the headset\'s cameras track your hands. Needs hand tracking on in the headset\'s settings.</string>
<string name="vr_lean_back">Lean back angle</string>
<string name="vr_lean_back_helper">Tilts the race view back for playing reclined. 0 applies no tilt.</string>