using System; using System.Runtime.InteropServices; using UnityEditor; using UnityEngine.XR.OpenXR.Features; #if UNITY_EDITOR using UnityEditor.XR.OpenXR.Features; #endif namespace VIVE { namespace HandTracking { #if UNITY_EDITOR [OpenXRFeature(UiName = "Hand Tracking", BuildTargetGroups = new[] { BuildTargetGroup.Standalone }, Company = "HTC", Desc = "Hand Tracking OpenXR Feature", DocumentationLink = "https://developer.vive.com/resources/openxr/openxr-pcvr/tutorials/unity/how-integrate-hand-tracking-data-your-hand-model/", OpenxrExtensionStrings = "XR_EXT_hand_tracking", Version = "0.0.1", FeatureId = featureId)] #endif public class HandTracking_OpenXR_API : OpenXRFeature { /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string featureId = "com.company.openxr.handtracking.feature"; private IntPtr oldxrGetInstanceProcAddr; private IntPtr oldWaitFrame; private ulong m_xrInstance; private ulong m_xrSession; private ulong m_systemId; private XrFrameWaitInfo m_frameWaitInfo; private XrFrameState m_frameState; [Obsolete] public ulong m_space; [Obsolete] public XrSystemProperties systemProperties; [Obsolete] public ulong m_leftHandle; [Obsolete] public ulong m_rightHandle; public bool IsInitialized { get { return m_xrGetSystemProperties != null; } } public bool IsEnabledAndInitialized { get { return enabled && IsInitialized; } } public bool IsSessionCreated { get { return XrSession != default; } } public ulong XrInstance { get { return m_xrInstance; } } public ulong XrSession { get { return m_xrSession; } } public ulong SystemId { get { return m_systemId; } } public event Action onSessionCreate; public event Action onSessionDestroy; public event Action onSystemChange; protected override IntPtr HookGetInstanceProcAddr(IntPtr func) { UnityEngine.Debug.Log("EXT: registering our own xrGetInstanceProcAddr"); oldxrGetInstanceProcAddr = func; m_intercept_xrWaitFrame_xrGetInstanceProcAddr = intercept_xrWaitFrame_xrGetInstanceProcAddr; return Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame_xrGetInstanceProcAddr); } private int intercept_xrWaitFrame_xrGetInstanceProcAddr(ulong instance, string name, out IntPtr function) { if (oldxrGetInstanceProcAddr == null || oldxrGetInstanceProcAddr == IntPtr.Zero) { UnityEngine.Debug.LogError("oldxrGetInstanceProcAddr is null"); function = IntPtr.Zero; return -1; } // Get delegate of old xrGetInstanceProcAddr. var xrGetProc = Marshal.GetDelegateForFunctionPointer(oldxrGetInstanceProcAddr); int result = xrGetProc(instance, name,out function); if(name == "xrWaitFrame") { oldWaitFrame = function; m_intercept_xrWaitFrame = intercepted_xrWaitFrame; function = Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame); ; UnityEngine.Debug.Log("Getting xrWaitFrame func"); } return result; } private int intercepted_xrWaitFrame(ulong session,ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState) { // Get delegate of old xrWaitFrame. var xrWaitFrame = Marshal.GetDelegateForFunctionPointer(oldWaitFrame); int res = xrWaitFrame(session, ref frameWaitInfo, ref frameState); m_frameWaitInfo = frameWaitInfo; m_frameState = frameState; return res; } /// protected override bool OnInstanceCreate(ulong xrInstance) { UnityEngine.Debug.Log($"OnInstanceCreate({xrInstance:X})"); m_xrInstance = xrInstance; return GetXrFunctionDelegates(xrInstance); } /// protected override void OnInstanceDestroy(ulong xrInstance) { UnityEngine.Debug.Log($"OnInstanceDestroy({xrInstance:X})"); m_xrInstance = default; //ClearXrFunctionDelegates(); } /// protected override void OnSessionCreate(ulong xrSession) { UnityEngine.Debug.Log($"OnSessionCreate({xrSession:X})"); m_xrSession = xrSession; try { onSessionCreate?.Invoke(xrSession); } catch (Exception e) { UnityEngine.Debug.LogError(e); } } /// protected override void OnSystemChange(ulong xrSystem) { UnityEngine.Debug.Log($"OnSystemChange({xrSystem:X})"); m_systemId = xrSystem; try { onSystemChange?.Invoke(xrSystem); } catch (Exception e) { UnityEngine.Debug.LogError(e); } } /// protected override void OnSessionDestroy(ulong xrSession) { UnityEngine.Debug.Log($"OnSessionDestroy({xrSession:X})"); m_xrSession = default; try { onSessionDestroy?.Invoke(xrSession); } catch (Exception e) { UnityEngine.Debug.LogError(e); } } /// Return true if the result equals zero. private bool GetXrFunctionDelegates(ulong xrInstance) { if (xrGetInstanceProcAddr == null || xrGetInstanceProcAddr == IntPtr.Zero) { UnityEngine.Debug.LogError("xrGetInstanceProcAddr is null"); return false; } // Get delegate of xrGetInstanceProcAddr. var xrGetProc = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr); if (!MarshelFunc(xrInstance, xrGetProc, "xrGetSystemProperties", ref m_xrGetSystemProperties)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrEnumerateReferenceSpaces", ref m_xrEnumerateReferenceSpaces)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrEnumerateReferenceSpaces", ref m_xrEnumerateReferenceSpaces2)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrCreateReferenceSpace", ref m_xrCreateReferenceSpace)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrDestroySpace", ref m_xrDestroySpace)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrCreateHandTrackerEXT", ref m_xrCreateHandTrackerEXT)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrDestroyHandTrackerEXT", ref m_xrDestroyHandTrackerEXT)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrLocateHandJointsEXT", ref m_xrLocateHandJointsEXT)) { return false; } return true; } private void ClearXrFunctionDelegates() { m_xrGetSystemProperties = null; m_xrEnumerateReferenceSpaces = null; m_xrEnumerateReferenceSpaces2 = null; m_xrCreateReferenceSpace = null; m_xrDestroySpace = null; m_xrCreateHandTrackerEXT = null; m_xrDestroyHandTrackerEXT = null; m_xrLocateHandJointsEXT = null; } private static bool MarshelFunc(ulong instance, xrGetInstanceProcDelegate instanceProc, string funcName, ref T func, bool verbose = true) where T : Delegate { if (instanceProc(instance, funcName, out var fp) != 0) { if (verbose) { UnityEngine.Debug.LogError("Fail getting function " + funcName); } return false; } func = Marshal.GetDelegateForFunctionPointer(fp); return true; } private xrGetInstanceProcDelegate m_intercept_xrWaitFrame_xrGetInstanceProcAddr; private delegate int xrGetSystemPropertiesDelegate(ulong instance, ulong systemId, ref XrSystemProperties properties); private xrGetSystemPropertiesDelegate m_xrGetSystemProperties; public int xrGetSystemProperties(ref XrSystemProperties properties) => m_xrGetSystemProperties(XrInstance, SystemId, ref properties); public int xrGetSystemProperties(ulong instance, ulong systemId, ref XrSystemProperties properties) => m_xrGetSystemProperties(instance, systemId, ref properties); private delegate int xrWaitFrameDelegate(ulong session,ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState); private xrWaitFrameDelegate m_intercept_xrWaitFrame; #region space private delegate int xrEnumerateReferenceSpacesDelegate(ulong session, int spaceCapacityInput, out int spaceCountOutput, out XrReferenceSpaceType spaces); private xrEnumerateReferenceSpacesDelegate m_xrEnumerateReferenceSpaces; public int xrEnumerateReferenceSpaces(int spaceCapacityInput, out int spaceCountOutput, out XrReferenceSpaceType spaces) => m_xrEnumerateReferenceSpaces(XrSession, spaceCapacityInput, out spaceCountOutput, out spaces); private delegate int xrEnumerateReferenceSpacesDelegate2(ulong session, int spaceCapacityInput, out int spaceCountOutput, IntPtr spaces); private xrEnumerateReferenceSpacesDelegate2 m_xrEnumerateReferenceSpaces2; public int xrEnumerateReferenceSpaces(ulong session, int spaceCapacityInput, out int spaceCountOutput, IntPtr spaces) => m_xrEnumerateReferenceSpaces2(session, spaceCapacityInput, out spaceCountOutput, spaces); private delegate int xrCreateReferenceSpaceDelegate(ulong session, ref XrReferenceSpaceCreateInfo createInfo, out ulong space); private xrCreateReferenceSpaceDelegate m_xrCreateReferenceSpace; public int xrCreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out ulong space) => m_xrCreateReferenceSpace(XrSession, ref createInfo, out space); public int xrCreateReferenceSpace(ulong session, ref XrReferenceSpaceCreateInfo createInfo, out ulong space) => m_xrCreateReferenceSpace(session, ref createInfo, out space); private delegate int xrDestroySpaceDelegate(ulong space); private xrDestroySpaceDelegate m_xrDestroySpace; public int xrDestroySpace(ulong space) => m_xrDestroySpace(space); #endregion private delegate int xrCreateHandTrackerEXTDelegate(ulong session, XrHandTrackerCreateInfoEXT createInfo, out ulong handTracker); private xrCreateHandTrackerEXTDelegate m_xrCreateHandTrackerEXT; public int xrCreateHandTrackerEXT(XrHandTrackerCreateInfoEXT createInfo, out ulong handle) => m_xrCreateHandTrackerEXT(XrSession, createInfo, out handle); public int xrCreateHandTrackerEXT(ulong session, XrHandTrackerCreateInfoEXT createInfo, out ulong handle) => m_xrCreateHandTrackerEXT(session, createInfo, out handle); private delegate int xrDestroyHandTrackerEXTDelegate(ulong handTracker); private xrDestroyHandTrackerEXTDelegate m_xrDestroyHandTrackerEXT; public int xrDestroyHandTrackerEXT(ulong handle) => m_xrDestroyHandTrackerEXT(handle); private delegate int xrLocateHandJointsEXTDelegate(ulong handTracker,ref XrHandJointsLocateInfoEXT locateInfo, ref XrHandJointLocationsEXT locations); private xrLocateHandJointsEXTDelegate m_xrLocateHandJointsEXT; public int xrLocateHandJointsEXT(ulong handTracker,ref XrHandJointsLocateInfoEXT locateInfo, ref XrHandJointLocationsEXT locations) => m_xrLocateHandJointsEXT(handTracker,ref locateInfo, ref locations); public unsafe bool SystemSupportsHandTracking(out XrResult result) { var handTrackingSystemProp = new XrSystemHandTrackingPropertiesEXT() { type = XrStructureType.XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT, next = IntPtr.Zero, }; var systemProp = new XrSystemProperties() { type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES, next = (IntPtr)(&handTrackingSystemProp), }; result = (XrResult)xrGetSystemProperties(XrInstance, SystemId, ref systemProp); return result == XrResult.XR_SUCCESS && handTrackingSystemProp.supportsHandTracking != 0u; } public bool TryGetSupportedReferenceSpaceTypeCount(out int count, out XrResult result) { result = (XrResult)m_xrEnumerateReferenceSpaces2(XrSession, 0, out count, IntPtr.Zero); return result == XrResult.XR_SUCCESS; } public bool TryGetSupportedReferenceSpaceTypes(XrReferenceSpaceType[] spaces, out XrResult result) { if (spaces == null || spaces.Length == 0) { result = default; return true; } result = (XrResult)m_xrEnumerateReferenceSpaces2(XrSession, spaces.Length, out _, ArrayPtr(spaces)); return result == XrResult.XR_SUCCESS; } public bool TryGetSupportedReferenceSpaceType(XrReferenceSpaceType preferType, out XrReferenceSpaceType supportedType, out XrResult result) { supportedType = default; if (!TryGetSupportedReferenceSpaceTypeCount(out var count, out result)) { return false; } if (count == 0) { return false; } // FIXME: error code? var spaces = new XrReferenceSpaceType[count]; if (!TryGetSupportedReferenceSpaceTypes(spaces, out result)) { return false; } for (int i = 0; i < count; ++i) { supportedType = spaces[i]; if (supportedType == preferType) { break; } } return true; } public bool TryCreateReferenceSpace(XrReferenceSpaceType refSpaceType, XrVector3f position, XrQuaternionf orientation, out ulong handle, out XrResult result) { var createInfo = new XrReferenceSpaceCreateInfo() { type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO, next = IntPtr.Zero, referencespacetype = refSpaceType, poseInReferenceSpace = new XrPosef() { position = position, orientation = orientation, }, }; result = (XrResult)xrCreateReferenceSpace(XrSession, ref createInfo, out handle); return result == XrResult.XR_SUCCESS; } public bool TryDestroyReferenceSpace(ulong handle, out XrResult result) { result = (XrResult)xrDestroySpace(handle); return result == XrResult.XR_SUCCESS; } public bool TryCreateHandTracker(XrHandEXT hand, out ulong handle, out XrResult result, XrHandJointSetEXT jointSet = XrHandJointSetEXT.XR_HAND_JOINT_SET_DEFAULT_EXT) { var createInfo = new XrHandTrackerCreateInfoEXT() { type = XrStructureType.XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, next = IntPtr.Zero, hand = hand, handJointSet = XrHandJointSetEXT.XR_HAND_JOINT_SET_DEFAULT_EXT, }; result = (XrResult)xrCreateHandTrackerEXT(XrSession, createInfo, out handle); return result == XrResult.XR_SUCCESS; } public bool TryDestroyHandTracker(ulong handle, out XrResult result) { result = (XrResult)xrDestroyHandTrackerEXT(handle); return result == XrResult.XR_SUCCESS; } public bool TryLocateHandJoints(ulong handle, ulong space, out bool isActive, XrHandJointLocationEXT[] locationArray, out XrResult result) { return TryLocateHandJoints(handle, space, out isActive, locationArray, null, out result); } public bool TryLocateHandJoints(ulong handle, ulong space, out bool isActive, XrHandJointLocationEXT[] locationArray, XrHandJointVelocityEXT[] velocityArray, out XrResult result) { return TryLocateHandJoints(handle, space, IntPtr.Zero, out isActive, locationArray, velocityArray, out result); } public bool TryLocateHandJoints(ulong handle, ulong space, ref XrHandJointsMotionRangeEXT motionRange, out bool isActive, XrHandJointLocationEXT[] locationArray, out XrResult result) { return TryLocateHandJoints(handle, space, ref motionRange, out isActive, locationArray, null, out result); } public unsafe bool TryLocateHandJoints(ulong handle, ulong space, ref XrHandJointsMotionRangeEXT motionRange, out bool isActive, XrHandJointLocationEXT[] locationArray, XrHandJointVelocityEXT[] velocityArray, out XrResult result) { var motionRangeInfo = new XrHandJointsMotionRangeInfoEXT() { type = XrStructureType.XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, next = IntPtr.Zero, handJointsMotionRange = motionRange, }; bool res = TryLocateHandJoints(handle, space, (IntPtr)(&motionRangeInfo), out isActive, locationArray, velocityArray, out result); motionRange = motionRangeInfo.handJointsMotionRange; return res; } private unsafe bool TryLocateHandJoints(ulong handle, ulong space, IntPtr motionRangeInfo, out bool isActive, XrHandJointLocationEXT[] locationArray, XrHandJointVelocityEXT[] velocityArray, out XrResult result) { var locateInfo = new XrHandJointsLocateInfoEXT() { type = XrStructureType.XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, next = motionRangeInfo, baseSpace = space, time = m_frameState.predictedDisplayTime, //An arbitrary number greater than 0 }; var locVelocitiesPtr = IntPtr.Zero; if (velocityArray != null) { var locVelocities = new XrHandJointVelocitiesEXT() { type = XrStructureType.XR_TYPE_HAND_JOINT_VELOCITIES_EXT, next = IntPtr.Zero, jointCount = (uint)velocityArray.Length, jointVelocities = ArrayPtr(velocityArray) }; locVelocitiesPtr = (IntPtr)(&locVelocities); } var locations = new XrHandJointLocationsEXT() { type = XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT, next = locVelocitiesPtr, jointCount = locationArray == null ? 0 : locationArray.Length, jointLocations = ArrayPtr(locationArray), }; result = (XrResult)xrLocateHandJointsEXT(handle,ref locateInfo, ref locations); isActive = locations.isActive != 0u; return result == XrResult.XR_SUCCESS; } private unsafe static IntPtr ArrayPtr(XrReferenceSpaceType[] array) { if (array == null) { return IntPtr.Zero; } fixed (XrReferenceSpaceType* p = array) { return (IntPtr)p; } } private unsafe static IntPtr ArrayPtr(XrHandJointLocationEXT[] array) { if (array == null) { return IntPtr.Zero; } fixed (XrHandJointLocationEXT* p = array) { return (IntPtr)p; } } private unsafe static IntPtr ArrayPtr(XrHandJointVelocityEXT[] array) { if (array == null) { return IntPtr.Zero; } fixed (XrHandJointVelocityEXT* p = array) { return (IntPtr)p; } } } } }