// Copyright HTC Corporation All Rights Reserved. using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; using UnityEngine; using System.Runtime.InteropServices; using System; using System.Linq; using UnityEngine.XR; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; using UnityEditor.XR.OpenXR.Features; #endif namespace VIVE.OpenXR.Hand { #if UNITY_EDITOR [OpenXRFeature(UiName = "VIVE XR Hand Tracking", BuildTargetGroups = new[] { BuildTargetGroup.Android , BuildTargetGroup.Standalone }, Company = "HTC", Desc = "Support the Hand Tracking extension.", DocumentationLink = "..\\Documentation", OpenxrExtensionStrings = kOpenxrExtensionString, Version = "4.0.0", FeatureId = featureId)] #endif public class ViveHandTracking : OpenXRFeature { const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandTracking"; void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); } void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); } void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); } /// /// OpenXR specification 12.29 XR_EXT_hand_tracking. /// public const string kOpenxrExtensionString = "XR_EXT_hand_tracking"; /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string featureId = "vive.openxr.feature.hand.tracking"; #region OpenXR Life Cycle private bool m_XrInstanceCreated = false; private XrInstance m_XrInstance = 0; /// /// Called when xrCreateInstance is done. /// /// The created instance. /// True for valid XrInstance protected override bool OnInstanceCreate(ulong xrInstance) { if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString)) { WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled."); return false; } m_XrInstanceCreated = true; m_XrInstance = xrInstance; DEBUG("OnInstanceCreate() " + m_XrInstance); return GetXrFunctionDelegates(m_XrInstance); } /// /// Called when xrDestroyInstance is done. /// /// The instance to destroy. protected override void OnInstanceDestroy(ulong xrInstance) { m_XrInstanceCreated = false; m_XrInstance = 0; DEBUG("OnInstanceDestroy() " + xrInstance); } private XrSystemId m_XrSystemId = 0; /// /// Called when the XrSystemId retrieved by xrGetSystem is changed. /// /// The system id. protected override void OnSystemChange(ulong xrSystem) { m_XrSystemId = xrSystem; DEBUG("OnSystemChange() " + m_XrSystemId); } private bool m_XrSessionCreated = false; private XrSession m_XrSession = 0; private bool hasReferenceSpaceLocal = false, hasReferenceSpaceStage = false; private XrSpace m_ReferenceSpaceLocal = 0, m_ReferenceSpaceStage = 0; private bool hasLeftHandTracker = false, hasRightHandTracker = false; private XrHandTrackerEXT leftHandTracker = 0, rightHandTracker = 0; /// /// Called when xrCreateSession is done. /// /// The created session ID. protected override void OnSessionCreate(ulong xrSession) { m_XrSession = xrSession; m_XrSessionCreated = true; DEBUG("OnSessionCreate() " + m_XrSession); // Enumerate supported reference space types and create the XrSpace. XrReferenceSpaceType[] spaces = new XrReferenceSpaceType[Enum.GetNames(typeof(XrReferenceSpaceType)).Count()]; UInt32 spaceCountOutput; #pragma warning disable 0618 if (EnumerateReferenceSpaces( spaceCapacityInput: 0, spaceCountOutput: out spaceCountOutput, spaces: out spaces[0]) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { DEBUG("OnSessionCreate() spaceCountOutput: " + spaceCountOutput); Array.Resize(ref spaces, (int)spaceCountOutput); #pragma warning disable 0618 if (EnumerateReferenceSpaces( spaceCapacityInput: spaceCountOutput, spaceCountOutput: out spaceCountOutput, spaces: out spaces[0]) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { XrReferenceSpaceCreateInfo createInfo; /// Create m_ReferenceSpaceLocal if (IsReferenceSpaceTypeSupported(spaceCountOutput, spaces, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL)) { createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO; createInfo.next = IntPtr.Zero; createInfo.referenceSpaceType = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL;//referenceSpaceType; createInfo.poseInReferenceSpace.orientation = new XrQuaternionf(0, 0, 0, 1); createInfo.poseInReferenceSpace.position = new XrVector3f(0, 0, 0); #pragma warning disable 0618 if (CreateReferenceSpace( createInfo: ref createInfo, space: out m_ReferenceSpaceLocal) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { hasReferenceSpaceLocal = true; DEBUG("OnSessionCreate() CreateReferenceSpace LOCAL: " + m_ReferenceSpaceLocal); } else { ERROR("OnSessionCreate() CreateReferenceSpace LOCAL failed."); } } /// Create m_ReferenceSpaceStage if (IsReferenceSpaceTypeSupported(spaceCountOutput, spaces, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE)) { createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO; createInfo.next = IntPtr.Zero; createInfo.referenceSpaceType = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE; createInfo.poseInReferenceSpace.orientation = new XrQuaternionf(0, 0, 0, 1); createInfo.poseInReferenceSpace.position = new XrVector3f(0, 0, 0); #pragma warning disable 0618 if (CreateReferenceSpace( createInfo: ref createInfo, space: out m_ReferenceSpaceStage) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { hasReferenceSpaceStage = true; DEBUG("OnSessionCreate() CreateReferenceSpace STAGE: " + m_ReferenceSpaceStage); } else { ERROR("OnSessionCreate() CreateReferenceSpace STAGE failed."); } } } else { ERROR("OnSessionCreate() EnumerateReferenceSpaces(" + spaceCountOutput + ") failed."); } } else { ERROR("OnSessionCreate() EnumerateReferenceSpaces(0) failed."); } { // left hand tracker if (CreateHandTrackers(true, out XrHandTrackerEXT value)) { hasLeftHandTracker = true; leftHandTracker = value; DEBUG("OnSessionCreate() leftHandTracker " + leftHandTracker); } } { // right hand tracker if (CreateHandTrackers(false, out XrHandTrackerEXT value)) { hasRightHandTracker = true; rightHandTracker = value; DEBUG("OnSessionCreate() rightHandTracker " + rightHandTracker); } } } /// /// Called when xrDestroySession is done. /// /// The session ID to destroy. protected override void OnSessionDestroy(ulong xrSession) { DEBUG("OnSessionDestroy() " + xrSession); // Reference Space is binding with xrSession so we destroy the xrSpace when xrSession is destroyed. if (hasReferenceSpaceLocal) { #pragma warning disable 0618 if (DestroySpace(m_ReferenceSpaceLocal) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { DEBUG("OnSessionDestroy() DestroySpace LOCAL " + m_ReferenceSpaceLocal); m_ReferenceSpaceLocal = 0; } else { ERROR("OnSessionDestroy() DestroySpace LOCAL " + m_ReferenceSpaceLocal + " failed."); } hasReferenceSpaceLocal = false; } if (hasReferenceSpaceStage) { #pragma warning disable 0618 if (DestroySpace(m_ReferenceSpaceStage) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { DEBUG("OnSessionDestroy() DestroySpace STAGE " + m_ReferenceSpaceStage); m_ReferenceSpaceStage = 0; } else { ERROR("OnSessionDestroy() DestroySpace STAGE " + m_ReferenceSpaceStage + " failed."); } hasReferenceSpaceStage = false; } // Hand Tracking is binding with xrSession so we destroy the hand trackers when xrSession is destroyed. if (hasLeftHandTracker) { if (DestroyHandTrackerEXT(leftHandTracker) == XrResult.XR_SUCCESS) { DEBUG("OnSessionDestroy() Left DestroyHandTrackerEXT " + leftHandTracker); } else { ERROR("OnSessionDestroy() Left DestroyHandTrackerEXT " + leftHandTracker + " failed."); } hasLeftHandTracker = false; } if (hasRightHandTracker) { if (DestroyHandTrackerEXT(rightHandTracker) == XrResult.XR_SUCCESS) { DEBUG("OnSessionDestroy() Right DestroyHandTrackerEXT " + rightHandTracker); } else { ERROR("OnSessionDestroy() Right DestroyHandTrackerEXT " + rightHandTracker + " failed."); } hasRightHandTracker = false; } if (m_XrSession == xrSession) { m_XrSession = 0; m_XrSessionCreated = false; } } #endregion #region OpenXR function delegates /// xrGetInstanceProcAddr OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr; /// xrGetSystemProperties OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties; /// /// An application can call GetSystemProperties to retrieve information about the system such as vendor ID, system name, and graphics and tracking properties. /// /// Points to an instance of the XrSystemProperties structure, that will be filled with returned information. /// XR_SUCCESS for success. [Obsolete("This function will become private in next release")] public XrResult GetSystemProperties(ref XrSystemProperties properties) { if (!m_XrSessionCreated) { ERROR("GetSystemProperties() XR_ERROR_SESSION_LOST."); return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("GetSystemProperties() XR_ERROR_INSTANCE_LOST."); return XrResult.XR_ERROR_INSTANCE_LOST; } return xrGetSystemProperties(m_XrInstance, m_XrSystemId, ref properties); } /// xrEnumerateReferenceSpaces OpenXRHelper.xrEnumerateReferenceSpacesDelegate xrEnumerateReferenceSpaces; /// /// Enumerates the set of reference space types that this runtime supports for a given session. Runtimes must always return identical buffer contents from this enumeration for the lifetime of the session. /// /// The capacity of the spaces array, or 0 to indicate a request to retrieve the required capacity. /// A pointer to the count of spaces written, or a pointer to the required capacity in the case that spaceCapacityInput is insufficient. /// A pointer to an application-allocated array that will be filled with the enumerant of each supported reference space. It can be NULL if spaceCapacityInput is 0. /// XR_SUCCESS for success. [Obsolete("This function will become private in next release")] public XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces) { if (!m_XrSessionCreated) { ERROR("EnumerateReferenceSpaces() XR_ERROR_SESSION_LOST."); spaceCountOutput = 0; spaces = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT; return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("EnumerateReferenceSpaces() XR_ERROR_SESSION_LOST."); spaceCountOutput = 0; spaces = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT; return XrResult.XR_ERROR_INSTANCE_LOST; } return xrEnumerateReferenceSpaces(m_XrSession, spaceCapacityInput, out spaceCountOutput, out spaces); } /// xrCreateReferenceSpace OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace; /// /// Creates an XrSpace handle based on a chosen reference space. Application can provide an XrPosef to define the position and orientation of the new space’s origin within the natural reference frame of the reference space. /// /// The XrReferenceSpaceCreateInfo used to specify the space. /// The returned XrSpace handle. /// XR_SUCCESS for success. [Obsolete("This function will become private in next release")] public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space) { if (!m_XrSessionCreated) { ERROR("CreateReferenceSpace() XR_ERROR_SESSION_LOST."); space = 0; return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("CreateReferenceSpace() XR_ERROR_INSTANCE_LOST."); space = 0; return XrResult.XR_ERROR_INSTANCE_LOST; } return xrCreateReferenceSpace(m_XrSession, ref createInfo, out space); } /// xrDestroySpace OpenXRHelper.xrDestroySpaceDelegate xrDestroySpace; /// /// XrSpace handles are destroyed using DestroySpace. The runtime may still use this space if there are active dependencies (e.g, compositions in progress). /// /// Must be a valid XrSpace handle. /// XR_SUCCESS for success. [Obsolete("This function will become private in next release")] public XrResult DestroySpace(XrSpace space) { if (!m_XrSessionCreated) { ERROR("DestroySpace() XR_ERROR_SESSION_LOST."); return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("DestroySpace() XR_ERROR_INSTANCE_LOST."); return XrResult.XR_ERROR_INSTANCE_LOST; } return xrDestroySpace(space); } /// xrCreateHandTrackerEXT ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate xrCreateHandTrackerEXT; /// /// An application can create an XrHandTrackerEXT handle using CreateHandTrackerEXT function. /// /// The XrHandTrackerCreateInfoEXT used to specify the hand tracker. /// The returned XrHandTrackerEXT handle. /// XR_SUCCESS for success. public XrResult CreateHandTrackerEXT(ref XrHandTrackerCreateInfoEXT createInfo, out XrHandTrackerEXT handTracker) { if (!m_XrSessionCreated) { ERROR("CreateHandTrackerEXT() XR_ERROR_SESSION_LOST."); handTracker = 0; return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("CreateHandTrackerEXT() XR_ERROR_INSTANCE_LOST."); handTracker = 0; return XrResult.XR_ERROR_INSTANCE_LOST; } if (createInfo.hand == XrHandEXT.XR_HAND_LEFT_EXT && hasLeftHandTracker) { DEBUG("CreateHandTrackerEXT() Left tracker " + leftHandTracker + " already created."); handTracker = leftHandTracker; return XrResult.XR_SUCCESS; } if (createInfo.hand == XrHandEXT.XR_HAND_RIGHT_EXT && hasRightHandTracker) { DEBUG("CreateHandTrackerEXT() Right tracker " + rightHandTracker + " already created."); handTracker = rightHandTracker; return XrResult.XR_SUCCESS; } return xrCreateHandTrackerEXT(m_XrSession, ref createInfo, out handTracker); } /// xrDestroyHandTrackerEXT ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate xrDestroyHandTrackerEXT; /// /// Releases the handTracker and the underlying resources when finished with hand tracking experiences. /// /// An XrHandTrackerEXT previously created by CreateHandTrackerEXT. /// XR_SUCCESS for success. public XrResult DestroyHandTrackerEXT(XrHandTrackerEXT handTracker) { if (!m_XrSessionCreated) { ERROR("DestroyHandTrackerEXT() XR_ERROR_SESSION_LOST."); return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("DestroyHandTrackerEXT() XR_ERROR_INSTANCE_LOST."); return XrResult.XR_ERROR_INSTANCE_LOST; } return xrDestroyHandTrackerEXT(handTracker); } /// xrLocateHandJointsEXT ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate xrLocateHandJointsEXT; /// /// The LocateHandJointsEXT function locates an array of hand joints to a base space at given time. /// /// An XrHandTrackerEXT previously created by CreateHandTrackerEXT. /// A pointer to XrHandJointsLocateInfoEXT describing information to locate hand joints. /// A pointer to XrHandJointLocationsEXT receiving the returned hand joint locations. /// public XrResult LocateHandJointsEXT(XrHandTrackerEXT handTracker, XrHandJointsLocateInfoEXT locateInfo, ref XrHandJointLocationsEXT locations) { if (!m_XrSessionCreated) { ERROR("LocateHandJointsEXT() XR_ERROR_SESSION_LOST."); return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("LocateHandJointsEXT() XR_ERROR_INSTANCE_LOST."); return XrResult.XR_ERROR_INSTANCE_LOST; } return xrLocateHandJointsEXT(handTracker, locateInfo, ref locations); } private bool GetXrFunctionDelegates(XrInstance xrInstance) { /// xrGetInstanceProcAddr if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero) { DEBUG("Get function pointer of xrGetInstanceProcAddr."); XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer( xrGetInstanceProcAddr, typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate; } else { ERROR("xrGetInstanceProcAddr"); return false; } IntPtr funcPtr = IntPtr.Zero; /// xrGetSystemProperties if (XrGetInstanceProcAddr(xrInstance, "xrGetSystemProperties", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrGetSystemProperties."); xrGetSystemProperties = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(OpenXRHelper.xrGetSystemPropertiesDelegate)) as OpenXRHelper.xrGetSystemPropertiesDelegate; } } else { ERROR("xrGetSystemProperties"); return false; } /// xrEnumerateReferenceSpaces if (XrGetInstanceProcAddr(xrInstance, "xrEnumerateReferenceSpaces", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrEnumerateReferenceSpaces."); xrEnumerateReferenceSpaces = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(OpenXRHelper.xrEnumerateReferenceSpacesDelegate)) as OpenXRHelper.xrEnumerateReferenceSpacesDelegate; } } else { ERROR("xrEnumerateReferenceSpaces"); return false; } /// xrCreateReferenceSpace if (XrGetInstanceProcAddr(xrInstance, "xrCreateReferenceSpace", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrCreateReferenceSpace."); xrCreateReferenceSpace = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(OpenXRHelper.xrCreateReferenceSpaceDelegate)) as OpenXRHelper.xrCreateReferenceSpaceDelegate; } } else { ERROR("xrCreateReferenceSpace"); return false; } /// xrDestroySpace if (XrGetInstanceProcAddr(xrInstance, "xrDestroySpace", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrDestroySpace."); xrDestroySpace = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(OpenXRHelper.xrDestroySpaceDelegate)) as OpenXRHelper.xrDestroySpaceDelegate; } } else { ERROR("xrDestroySpace"); return false; } /// xrCreateHandTrackerEXT if (XrGetInstanceProcAddr(xrInstance, "xrCreateHandTrackerEXT", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrCreateHandTrackerEXT."); xrCreateHandTrackerEXT = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate)) as ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate; } } else { ERROR("xrCreateHandTrackerEXT"); return false; } /// xrDestroyHandTrackerEXT if (XrGetInstanceProcAddr(xrInstance, "xrDestroyHandTrackerEXT", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrDestroyHandTrackerEXT."); xrDestroyHandTrackerEXT = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate)) as ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate; } } else { ERROR("xrDestroyHandTrackerEXT"); return false; } /// xrLocateHandJointsEXT if (XrGetInstanceProcAddr(xrInstance, "xrLocateHandJointsEXT", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrLocateHandJointsEXT."); xrLocateHandJointsEXT = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate)) as ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate; } } else { ERROR("xrLocateHandJointsEXT"); return false; } return true; } #endregion static List s_InputSubsystems = new List(); /// /// Retrieves the current tracking origin in Unity XR. /// /// The tracking origin in TrackingOriginModeFlags public TrackingOriginModeFlags GetTrackingOriginMode() { XRInputSubsystem subsystem = null; SubsystemManager.GetInstances(s_InputSubsystems); if (s_InputSubsystems.Count > 0) { subsystem = s_InputSubsystems[0]; } if (subsystem != null) { return subsystem.GetTrackingOriginMode(); } return TrackingOriginModeFlags.Unknown; } private bool IsReferenceSpaceTypeSupported(UInt32 spaceCountOutput, XrReferenceSpaceType[] spaces, XrReferenceSpaceType space) { bool support = false; for (int i = 0; i < spaceCountOutput; i++) { DEBUG("IsReferenceSpaceTypeSupported() supported space[" + i + "]: " + spaces[i]); if (spaces[i] == space) { support = true; } } return support; } XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties; XrSystemProperties systemProperties; private bool IsHandTrackingSupported() { bool ret = false; if (!m_XrSessionCreated) { ERROR("IsHandTrackingSupported() session is not created."); return ret; } handTrackingSystemProperties.type = XrStructureType.XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES; systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(handTrackingSystemProperties)); long offset = 0; if (IntPtr.Size == 4) offset = systemProperties.next.ToInt32(); else offset = systemProperties.next.ToInt64(); IntPtr sys_hand_tracking_prop_ptr = new IntPtr(offset); Marshal.StructureToPtr(handTrackingSystemProperties, sys_hand_tracking_prop_ptr, false); #pragma warning disable 0618 if (GetSystemProperties(ref systemProperties) == XrResult.XR_SUCCESS) #pragma warning restore 0618 { if (IntPtr.Size == 4) offset = systemProperties.next.ToInt32(); else offset = systemProperties.next.ToInt64(); sys_hand_tracking_prop_ptr = new IntPtr(offset); handTrackingSystemProperties = (XrSystemHandTrackingPropertiesEXT)Marshal.PtrToStructure(sys_hand_tracking_prop_ptr, typeof(XrSystemHandTrackingPropertiesEXT)); DEBUG("IsHandTrackingSupported() XrSystemHandTrackingPropertiesEXT.supportsHandTracking: " + handTrackingSystemProperties.supportsHandTracking); ret = handTrackingSystemProperties.supportsHandTracking > 0; } else { ERROR("IsHandTrackingSupported() GetSystemProperties failed."); } Marshal.FreeHGlobal(systemProperties.next); return ret; } private bool CreateHandTrackers(bool isLeft, out XrHandTrackerEXT handTracker) { if (!IsHandTrackingSupported()) { ERROR("CreateHandTrackers() " + (isLeft ? "Left" : "Right") + " hand tracking is NOT supported."); handTracker = 0; return false; } XrHandTrackerCreateInfoEXT createInfo; createInfo.type = XrStructureType.XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT; createInfo.next = IntPtr.Zero; createInfo.hand = isLeft ? XrHandEXT.XR_HAND_LEFT_EXT : XrHandEXT.XR_HAND_RIGHT_EXT; createInfo.handJointSet = XrHandJointSetEXT.XR_HAND_JOINT_SET_DEFAULT_EXT; var ret = CreateHandTrackerEXT(ref createInfo, out handTracker); DEBUG("CreateHandTrackers() " + (isLeft ? "Left" : "Right") + " CreateHandTrackerEXT = " + ret); return ret == XrResult.XR_SUCCESS; } private XrHandJointLocationEXT[] jointLocationsL = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT]; private XrHandJointLocationEXT[] jointLocationsR = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT]; private XrHandJointLocationsEXT locations = new XrHandJointLocationsEXT(XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT, IntPtr.Zero, false, 0, IntPtr.Zero); public bool GetHandTrackingSpace(out XrSpace space) { space = 0; TrackingOriginModeFlags origin = GetTrackingOriginMode(); if (origin == TrackingOriginModeFlags.Unknown || origin == TrackingOriginModeFlags.Unbounded) { return false; } space = (origin == TrackingOriginModeFlags.Device ? m_ReferenceSpaceLocal : m_ReferenceSpaceStage); return true; } /// /// Retrieves the XrHandJointLocationEXT data. /// /// Left or right hand. /// Output parameter to retrieve XrHandJointLocationEXT data. /// True for valid data. public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation) { bool ret = false; handJointLocation = isLeft ? jointLocationsL : jointLocationsR; if (isLeft && !hasLeftHandTracker) { return ret; } if (!isLeft && !hasRightHandTracker) { return ret; } TrackingOriginModeFlags origin = GetTrackingOriginMode(); if (origin == TrackingOriginModeFlags.Unknown || origin == TrackingOriginModeFlags.Unbounded) { return ret; } XrSpace baseSpace = (origin == TrackingOriginModeFlags.Device ? m_ReferenceSpaceLocal : m_ReferenceSpaceStage); /// Configures XrHandJointsLocateInfoEXT XrHandJointsLocateInfoEXT locateInfo = new XrHandJointsLocateInfoEXT( in_type: XrStructureType.XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, in_next: IntPtr.Zero, in_baseSpace: baseSpace, in_time: 1);// /// Configures XrHandJointLocationsEXT locations.type = XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT; locations.next = IntPtr.Zero; locations.isActive = false; locations.jointCount = (uint)(isLeft ? jointLocationsL.Length : jointLocationsR.Length); XrHandJointLocationEXT joint_location_ext_type = default(XrHandJointLocationEXT); int jointLocationsLength = isLeft ? jointLocationsL.Length : jointLocationsR.Length; locations.jointLocations = Marshal.AllocHGlobal(Marshal.SizeOf(joint_location_ext_type) * jointLocationsLength); long offset = 0; /*if (IntPtr.Size == 4) offset = locations.jointLocations.ToInt32(); else offset = locations.jointLocations.ToInt64(); for (int i = 0; i < jointLocationsLength; i++) { IntPtr joint_location_ext_ptr = new IntPtr(offset); if (isLeft) Marshal.StructureToPtr(jointLocationsL[i], joint_location_ext_ptr, false); else Marshal.StructureToPtr(jointLocationsR[i], joint_location_ext_ptr, false); offset += Marshal.SizeOf(joint_location_ext_type); }*/ if (LocateHandJointsEXT( handTracker: (isLeft ? leftHandTracker : rightHandTracker), locateInfo: locateInfo, locations: ref locations) == XrResult.XR_SUCCESS) { if (locations.isActive) { if (IntPtr.Size == 4) offset = locations.jointLocations.ToInt32(); else offset = locations.jointLocations.ToInt64(); for (int i = 0; i < locations.jointCount; i++) { IntPtr joint_location_ext_ptr = new IntPtr(offset); if (isLeft) jointLocationsL[i] = (XrHandJointLocationEXT)Marshal.PtrToStructure(joint_location_ext_ptr, typeof(XrHandJointLocationEXT)); else jointLocationsR[i] = (XrHandJointLocationEXT)Marshal.PtrToStructure(joint_location_ext_ptr, typeof(XrHandJointLocationEXT)); offset += Marshal.SizeOf(joint_location_ext_type); } // ToDo: locationFlags? handJointLocation = isLeft ? jointLocationsL : jointLocationsR; ret = true; } } Marshal.FreeHGlobal(locations.jointLocations); return ret; } } }