#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