#if UNITY_OPENXR_PACKAGE || PACKAGE_DOCS_GENERATION using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.XR.Hands; using UnityEngine.XR.Hands.ProviderImplementation; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #if UNITY_OPENXR_PACKAGE_1_8 using UnityEngine.XR.OpenXR.Features.Interactions; #endif namespace UnityEngine.XR.Hands.OpenXR { /// /// Hand tracking provider for the OpenXR platform. /// public unsafe class OpenXRHandProvider : XRHandSubsystemProvider { /// /// See . /// public override void Start() {} /// /// See . /// public override void Stop() {} /// /// See . /// public override void Destroy() => NativeApi.Destroy(); /// public override void GetHandLayout(NativeArray handJointsInLayout) { if (!NativeApi.TryInitialize()) { Debug.LogError("OpenXR hand provider failed to initialize - no data will be tracked or surfaced!"); return; } handJointsInLayout[XRHandJointID.Palm.ToIndex()] = true; handJointsInLayout[XRHandJointID.Wrist.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleTip.ToIndex()] = true; m_IsValid = true; } /// public override XRHandSubsystem.UpdateSuccessFlags TryUpdateHands( XRHandSubsystem.UpdateType updateType, ref Pose leftHandRootPose, NativeArray leftHandJoints, ref Pose rightHandRootPose, NativeArray rightHandJoints) { if (!m_IsValid) return XRHandSubsystem.UpdateSuccessFlags.None; return NativeApi.TryUpdateHands( updateType, ref leftHandRootPose, leftHandJoints.GetUnsafePtr(), ref rightHandRootPose, rightHandJoints.GetUnsafePtr()); } /// public override bool canSurfaceCommonPoseData { get { if (m_IsHandInteractionProfileEnabled) return true; m_IsHandInteractionProfileEnabled = #if UNITY_OPENXR_PACKAGE_1_8 OpenXRRuntime.IsExtensionEnabled(HandInteractionProfile.extensionString); #else false; #endif return m_IsHandInteractionProfileEnabled; } } bool m_IsHandInteractionProfileEnabled; /// public override bool TryGetAimPose(Handedness handedness, out Pose aimPose) { aimPose = Pose.identity; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; if (handDevice.TryGetFeatureValue(Usages.aimPosition, out var position)) aimPose.position = position; if (handDevice.TryGetFeatureValue(Usages.aimRotation, out var rotation)) aimPose.rotation = rotation; return handDevice.TryGetFeatureValue(Usages.isAimPoseTracked, out var isTracked) && isTracked; #else return false; #endif } /// public override bool TryGetAimActivateValue(Handedness handedness, out float aimActivateValue) { aimActivateValue = 0f; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; handDevice.TryGetFeatureValue(Usages.aimActivateValue, out aimActivateValue); return handDevice.TryGetFeatureValue(Usages.isAimActivateValueReady, out var isReady) && isReady; #else return false; #endif } /// public override bool TryGetGraspValue(Handedness handedness, out float graspValue) { graspValue = 0f; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; handDevice.TryGetFeatureValue(Usages.graspValue, out graspValue); return handDevice.TryGetFeatureValue(Usages.isGraspValueReady, out var isReady) && isReady; #else return false; #endif } /// public override bool TryGetGripPose(Handedness handedness, out Pose gripPose) { gripPose = Pose.identity; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; if (handDevice.TryGetFeatureValue(Usages.gripPosition, out var position)) gripPose.position = position; if (handDevice.TryGetFeatureValue(Usages.gripRotation, out var rotation)) gripPose.rotation = rotation; return handDevice.TryGetFeatureValue(Usages.isGripPoseTracked, out var isTracked) && isTracked; #else return false; #endif } /// public override bool TryGetPinchPose(Handedness handedness, out Pose pinchPose) { pinchPose = Pose.identity; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; if (handDevice.TryGetFeatureValue(Usages.pinchPosition, out var position)) pinchPose.position = position; if (handDevice.TryGetFeatureValue(Usages.pinchRotation, out var rotation)) pinchPose.rotation = rotation; return handDevice.TryGetFeatureValue(Usages.isPinchPoseTracked, out var isTracked) && isTracked; #else return false; #endif } /// public override bool TryGetPinchValue(Handedness handedness, out float pinchValue) { pinchValue = 0f; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; handDevice.TryGetFeatureValue(Usages.pinchValue, out pinchValue); return handDevice.TryGetFeatureValue(Usages.isPinchValueReady, out var isReady) && isReady; #else return false; #endif } /// public override bool TryGetPokePose(Handedness handedness, out Pose pokePose) { pokePose = Pose.identity; #if UNITY_OPENXR_PACKAGE_1_8 if (!TryGetHandDevice(handedness, out var handDevice)) return false; if (handDevice.TryGetFeatureValue(Usages.pokePosition, out var position)) pokePose.position = position; if (handDevice.TryGetFeatureValue(Usages.pokeRotation, out var rotation)) pokePose.rotation = rotation; return handDevice.TryGetFeatureValue(Usages.isPokePoseTracked, out var isTracked) && isTracked; #else return false; #endif } #if UNITY_OPENXR_PACKAGE_1_8 static class Usages { internal static readonly InputFeatureUsage isAimPoseTracked = new InputFeatureUsage("PointerIsTracked"); internal static readonly InputFeatureUsage aimPosition = new InputFeatureUsage("PointerPosition"); internal static readonly InputFeatureUsage aimRotation = new InputFeatureUsage("PointerRotation"); internal static readonly InputFeatureUsage isAimActivateValueReady = new InputFeatureUsage("PointerActivateReady"); internal static readonly InputFeatureUsage aimActivateValue = new InputFeatureUsage("PointerActivateValue"); internal static readonly InputFeatureUsage isGraspValueReady = new InputFeatureUsage("GraspReady"); internal static readonly InputFeatureUsage graspValue = new InputFeatureUsage("GraspValue"); internal static readonly InputFeatureUsage isGripPoseTracked = new InputFeatureUsage("DeviceIsTracked"); internal static readonly InputFeatureUsage gripPosition = new InputFeatureUsage("DevicePosition"); internal static readonly InputFeatureUsage gripRotation = new InputFeatureUsage("DeviceRotation"); internal static readonly InputFeatureUsage isPinchPoseTracked = new InputFeatureUsage("PinchIsTracked"); internal static readonly InputFeatureUsage pinchPosition = new InputFeatureUsage("PinchPosition"); internal static readonly InputFeatureUsage pinchRotation = new InputFeatureUsage("PinchRotation"); internal static readonly InputFeatureUsage isPinchValueReady = new InputFeatureUsage("PinchReady"); internal static readonly InputFeatureUsage pinchValue = new InputFeatureUsage("PinchValue"); internal static readonly InputFeatureUsage isPokePoseTracked = new InputFeatureUsage("PokeIsTracked"); internal static readonly InputFeatureUsage pokePosition = new InputFeatureUsage("PokePosition"); internal static readonly InputFeatureUsage pokeRotation = new InputFeatureUsage("PokeRotation"); } InputDevice m_LeftHandInteractionDevice; InputDevice m_RightHandInteractionDevice; static readonly List s_DevicesReuse = new List(); bool TryGetHandDevice(Handedness handedness, out InputDevice device) { if (handedness == Handedness.Left && m_LeftHandInteractionDevice.isValid) { device = m_LeftHandInteractionDevice; return true; } else if (handedness == Handedness.Right && m_RightHandInteractionDevice.isValid) { device = m_RightHandInteractionDevice; return true; } InputDevices.GetDevicesWithCharacteristics( handedness == Handedness.Left ? InputDeviceCharacteristics.Left : InputDeviceCharacteristics.Right, s_DevicesReuse); for (int deviceIndex = 0; deviceIndex < s_DevicesReuse.Count; ++deviceIndex) { if (s_DevicesReuse[deviceIndex].name != k_HandInteractionDeviceName) continue; device = s_DevicesReuse[deviceIndex]; if (handedness == Handedness.Left) m_LeftHandInteractionDevice = device; else m_RightHandInteractionDevice = device; return true; } device = default; return false; } #endif bool m_IsValid; static internal string id { get; private set; } static OpenXRHandProvider() => id = "OpenXR Hands"; const string k_HandInteractionDeviceName = "Hand Interaction OpenXR"; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Register() { var settings = OpenXRSettings.Instance; if (settings == null) return; var feature = settings.GetFeature(); if (feature != null && feature.enabled) { #if UNITY_OPENXR_PACKAGE_1_8 var profile = OpenXRSettings.Instance.GetFeature(); bool commonPosesEnabled = profile != null && profile.enabled; #else bool commonPosesEnabled = false; #endif var handsSubsystemCinfo = new XRHandSubsystemDescriptor.Cinfo { id = id, providerType = typeof(OpenXRHandProvider), supportsAimPose = commonPosesEnabled, supportsAimActivateValue = commonPosesEnabled, supportsGraspValue = commonPosesEnabled, supportsGripPose = commonPosesEnabled, supportsPinchPose = commonPosesEnabled, supportsPinchValue = commonPosesEnabled, supportsPokePose = commonPosesEnabled, }; XRHandSubsystemDescriptor.Register(handsSubsystemCinfo); } } static class NativeApi { [DllImport(HandTracking.k_LibraryName, EntryPoint = "UnityOpenXRHands_TryInitialize")] internal static extern bool TryInitialize(); [DllImport(HandTracking.k_LibraryName, EntryPoint = "UnityOpenXRHands_Destroy")] internal static extern void Destroy(); [DllImport(HandTracking.k_LibraryName, EntryPoint = "UnityOpenXRHands_TryUpdateHands")] internal static unsafe extern XRHandSubsystem.UpdateSuccessFlags TryUpdateHands( XRHandSubsystem.UpdateType updateType, ref Pose leftRootPose, void* leftHandJoints, ref Pose rightRootPose, void* rightHandJoints); } } } #endif // UNITY_OPENXR_PACKAGE || PACKAGE_DOCS_GENERATION