#if UNITY_OPENXR_PACKAGE || PACKAGE_DOCS_GENERATION using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine.XR.Hands; using UnityEngine.XR.Hands.ProviderImplementation; using UnityEngine.XR.Management; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #if UNITY_EDITOR using UnityEditor; using UnityEditor.XR.Management; #endif #if UNITY_OPENXR_PACKAGE_1_2 && !UNITY_OPENXR_PACKAGE_1_6 using UnityEngine.XR.OpenXR.Features.OculusQuestSupport; #endif namespace UnityEngine.XR.Hands.OpenXR { /// /// This enables the use of /// hand-tracking data in OpenXR through the . /// It enables /// XR_EXT_hand_tracking in the underlying runtime. To retrieve hand /// data, use the retrieved from /// . /// /// /// For this extension to be available, you must install the /// /// XR Hands package. /// #if UNITY_EDITOR [UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Hand Tracking Subsystem", BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA, BuildTargetGroup.Android }, Company = "Unity", Desc = "Creates and manages an XRHandSubsystem.", DocumentationLink = XRHelpURLConstants.k_CurrentManualDocsBaseUrl + "features/handtracking.html", Version = "0.0.1", OpenxrExtensionStrings = extensionString, Category = UnityEditor.XR.OpenXR.Features.FeatureCategory.Feature, FeatureId = featureId, Priority = -100)] #endif public class HandTracking : OpenXRFeature { /// /// The feature ID string. This is used to give the feature a well known /// ID for reference. /// public const string featureId = "com.unity.openxr.feature.input.handtracking"; /// /// The OpenXR Extension string. OpenXR uses this to check if this /// extension is available or enabled. See /// hand interaction extension /// documentation for more information on this OpenXR extension. /// public const string extensionString = "XR_EXT_hand_tracking"; /// /// The that retrieves hand data from its /// provider. Will only be valid when this feature is enabled and /// running. To subscribe to updates, use . /// public static XRHandSubsystem subsystem => s_Subsystem; /// /// Event-args struct passed to when /// the subsystem is created. /// public struct SubsystemCreatedEventArgs { /// /// The subsystem that was just created. /// public XRHandSubsystem subsystem { get; internal set; } } /// /// Event-args struct passed to when /// the subsystem is about to be destroyed. /// public struct DestroyingSubsystemEventArgs { /// /// The subsystem about to be destroyed. /// public XRHandSubsystem subsystem { get; internal set; } } /// /// Called when this feature creates an . /// public static Action subsystemCreated; /// /// Called just before this feature destroys an . /// public static Action destroyingSubsystem; /// /// Whether an should be created when the /// session is. Defaults to . /// /// /// If you wish to set this to in time to stop /// automatic creation, do so in a static method decorated with /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]. /// When you wish to initialize the subsystem, you can then call /// . /// public static bool automaticallyInitializeSubsystem { get; set; } = true; /// /// Ensures an is created. /// /// /// Will be automatically called at start-up if /// is , /// which it is by default. If called later, will automatically start /// the subsystem if the session is running. /// public static void EnsureSubsystemInitialized() { if (s_Subsystem != null) return; var descriptors = new List(); s_This.CreateSubsystem(descriptors, OpenXRHandProvider.id); s_Subsystem = XRGeneralSettings.Instance?.Manager?.activeLoader?.GetLoadedSubsystem(); if (s_Subsystem == null) { Debug.LogError($"Failed to find descriptor '{OpenXRHandProvider.id}' - HandTracking OpenXR feature will not do anything!"); return; } s_This.m_Updater = new XRHandProviderUtility.SubsystemUpdater(s_Subsystem); if (s_This.m_ShouldBeRunning) { s_Subsystem.Start(); s_This.m_Updater.Start(); } if (subsystemCreated != null) subsystemCreated.Invoke(new SubsystemCreatedEventArgs {subsystem = s_Subsystem}); } /// See . protected override void OnSystemChange(ulong xrSystem) { base.OnSystemChange(xrSystem); NativeApi.OnSystemChange(xrSystem); } /// See . protected override bool OnInstanceCreate(ulong xrInstance) { s_This = this; if (!base.OnInstanceCreate(xrInstance)) return false; return NativeApi.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr); } /// See . protected override void OnAppSpaceChange(ulong xrSpace) { base.OnAppSpaceChange(xrSpace); NativeApi.OnAppSpaceChange(xrSpace); } /// /// Called after xrCreateSession. /// /// /// Creates an with the OpenXR provider. /// protected override void OnSessionCreate(ulong xrSession) { base.OnSessionCreate(xrSession); NativeApi.OnSessionCreate(xrSession); if (automaticallyInitializeSubsystem) EnsureSubsystemInitialized(); } /// See . protected override void OnSessionDestroy(ulong xrSpace) { base.OnSessionDestroy(xrSpace); NativeApi.OnSessionDestroy(xrSpace); } /// See . protected override void OnInstanceDestroy(ulong xrInstance) { base.OnInstanceDestroy(xrInstance); NativeApi.OnInstanceDestroy(xrInstance); } /// See . protected override void OnInstanceLossPending(ulong xrInstance) { base.OnInstanceLossPending(xrInstance); NativeApi.OnInstanceLossPending(xrInstance); } /// /// Called after the OpenXR loader has started its subsystems. /// /// /// Starts the and automatic updating for /// it. To subscribe to updates, use . /// protected override void OnSubsystemStart() { m_ShouldBeRunning = true; if (s_Subsystem == null) return; s_Subsystem.Start(); m_Updater.Start(); } /// /// Called before the OpenXR loader stops its subsystems. /// /// /// Stops the and automatic updating for it. /// protected override void OnSubsystemStop() { m_ShouldBeRunning = false; m_Updater?.Stop(); s_Subsystem?.Stop(); } /// /// Called before the OpenXR loader destroys its subsystems. /// /// /// Destroys the . /// protected override void OnSubsystemDestroy() { m_Updater?.Destroy(); m_Updater = null; if (destroyingSubsystem != null) destroyingSubsystem.Invoke(new DestroyingSubsystemEventArgs {subsystem = s_Subsystem}); s_Subsystem?.Destroy(); s_Subsystem = null; } /// protected override IntPtr HookGetInstanceProcAddr(IntPtr func) => NativeApi.Intercept_xrGetInstanceProcAddr(func); #if UNITY_EDITOR protected override void GetValidationChecks(List results, BuildTargetGroup targetGroup) { #if UNITY_OPENXR_PACKAGE_1_2 && !UNITY_OPENXR_PACKAGE_1_6 results.Add(new ValidationRule(this) { message = "Hand-tracking does not work on a Quest device at runtime without a version of the OpenXR package at version 1.6.0 or newer.", checkPredicate = () => { var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(targetGroup); if (null == settings) return false; var questFeature = settings.GetFeature(); return questFeature == null || !questFeature.enabled; }, error = true }); #endif // UNITY_OPENXR_PACKAGE_1_6 } #endif // UNITY_EDITOR internal const string k_LibraryName = "UnityOpenXRHands"; static class NativeApi { [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnSystemChange")] static internal extern void OnSystemChange(ulong xrSystem); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnInstanceCreate")] static internal extern bool OnInstanceCreate(ulong xrInstance, IntPtr xrGetInstanceProcAddr); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnAppSpaceChange")] static internal extern void OnAppSpaceChange(ulong xrSpace); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnSessionCreate")] static internal extern void OnSessionCreate(ulong xrSession); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnSessionDestroy")] static internal extern void OnSessionDestroy(ulong xrSession); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnInstanceDestroy")] static internal extern void OnInstanceDestroy(ulong xrInstance); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_OnInstanceLossPending")] static internal extern void OnInstanceLossPending(ulong xrInstance); [DllImport(k_LibraryName, EntryPoint = "UnityOpenXRHands_intercept_xrGetInstanceProcAddr")] static internal extern IntPtr Intercept_xrGetInstanceProcAddr(IntPtr func); } #if UNITY_EDITOR internal static bool OpenXRLoaderEnabledForEditorPlayMode() { var settings = XRGeneralSettings.Instance?.AssignedSettings ?? (XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Standalone)?.AssignedSettings); if (!settings) return false; foreach (var activeLoader in settings.activeLoaders) { if (activeLoader is OpenXRLoader) return true; } return false; } #endif XRHandProviderUtility.SubsystemUpdater m_Updater; bool m_ShouldBeRunning; static XRHandSubsystem s_Subsystem; static HandTracking s_This; } } #endif // UNITY_OPENXR_PACKAGE || PACKAGE_DOCS_GENERATION