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 FacialTracking { #if UNITY_EDITOR [OpenXRFeature(UiName = "Facial Tracking", BuildTargetGroups = new[] { BuildTargetGroup.Standalone }, Company = "HTC", Desc = "Facial Tracking OpenXR Feature", DocumentationLink = "https://developer.vive.com/resources/openxr/openxr-pcvr/tutorials/unity/integrate-facial-tracking-your-avatar/", OpenxrExtensionStrings = "XR_HTC_facial_tracking", Version = "0.0.1", FeatureId = featureId)] #endif public class VIVE_FacialTracking_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.htc.openxr.facialtracking.feature"; private ulong m_XrInstance; private ulong m_XrSession; private ulong m_systemid; [Obsolete] XrSystemProperties systemProperties; [Obsolete] public ulong m_expressionHandle; [Obsolete] public ulong m_expressionHandle_Lip; 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 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; } 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); } } private bool GetXrFunctionDelegates(ulong xrInstance) { if (xrGetInstanceProcAddr == null || xrGetInstanceProcAddr == IntPtr.Zero) UnityEngine.Debug.LogError("xrGetInstanceProcAddr is null"); // Get delegate of xrGetInstanceProcAddr. var xrGetProc = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr); // Get delegate of other OpenXR functions using xrGetInstanceProcAddr. if (!MarshelFunc(xrInstance, xrGetProc, "xrGetSystemProperties", ref m_xrGetSystemProperties)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrCreateFacialTrackerHTC", ref m_xrCreateFacialTrackerHTC)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrDestroyFacialTrackerHTC", ref m_xrDestroyFacialTrackerHTC)) { return false; } if (!MarshelFunc(xrInstance, xrGetProc, "xrGetFacialExpressionsHTC", ref m_xrGetFacialExpressionsHTC)) { return false; } return true; } 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; } delegate int XrGetInstanceProcAddrDelegate(ulong instance, string name, out IntPtr function); XrGetInstanceProcAddrDelegate m_XrGetInstanceProcAddr; delegate int xrGetSystemPropertiesDelegate(ulong instance, ulong systemId, ref XrSystemProperties properties); xrGetSystemPropertiesDelegate m_xrGetSystemProperties; public int xrGetSystemProperties(ref XrSystemProperties properties) => m_xrGetSystemProperties(m_XrInstance, m_systemid, ref properties); public int xrGetSystemProperties(ulong instance, ulong systemId, ref XrSystemProperties properties) => m_xrGetSystemProperties(instance, systemId, ref properties); delegate int xrCreateFacialTrackerHTCDelegate(ulong session, XrFacialTrackerCreateInfoHTC createInfo, out ulong expression); xrCreateFacialTrackerHTCDelegate m_xrCreateFacialTrackerHTC; public int xrCreateFacialTrackerHTC(XrFacialTrackerCreateInfoHTC createInfo ,out ulong handle) => m_xrCreateFacialTrackerHTC(m_XrSession, createInfo, out handle); public int xrCreateFacialTrackerHTC(ulong session, XrFacialTrackerCreateInfoHTC createInfo, out ulong handle) => m_xrCreateFacialTrackerHTC(session, createInfo, out handle); delegate int xrDestroyFacialTrackerHTCDelegate(ulong facialTracker); xrDestroyFacialTrackerHTCDelegate m_xrDestroyFacialTrackerHTC; public int xrDestroyFacialTrackerHTC(ulong handle) => m_xrDestroyFacialTrackerHTC(handle); delegate int xrGetFacialExpressionsHTCDelegate(ulong facialTracker, ref XrFacialExpressionsHTC eyeExpressionData); xrGetFacialExpressionsHTCDelegate m_xrGetFacialExpressionsHTC; public int xrGetFacialExpressionsHTC(ulong handle,ref XrFacialExpressionsHTC eyeExpressionData) => m_xrGetFacialExpressionsHTC(handle, ref eyeExpressionData); public unsafe void SystemSupportsFacialTracking(out XrResult result,out bool isSupportEye,out bool isSupportLip) { XrSystemFacialTrackingPropertiesHTC expressionProperties = new XrSystemFacialTrackingPropertiesHTC { type = XrStructureType.XR_TYPE_SYSTEM_FACIAL_TRACKING_PROPERTIES_HTC, next = IntPtr.Zero, }; var systemProp = new XrSystemProperties() { type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES, next = (IntPtr)(&expressionProperties), }; result = (XrResult)xrGetSystemProperties(XrInstance, SystemId ,ref systemProp); isSupportEye = expressionProperties.supportEyeFacialTracking != 0; isSupportLip = expressionProperties.supportLipFacialTracking != 0; } public bool TryCreateFacialTracker(XrFacialTrackingTypeHTC type,out ulong handle, out XrResult result) { XrFacialTrackerCreateInfoHTC createInfo = new XrFacialTrackerCreateInfoHTC( XrStructureType.XR_TYPE_FACIAL_TRACKER_CREATE_INFO_HTC, IntPtr.Zero, type); result = (XrResult)xrCreateFacialTrackerHTC(XrSession,createInfo, out handle); return result == XrResult.XR_SUCCESS; } public bool TryDestroFacialTracker(ulong handle, out XrResult result) { result= (XrResult)xrDestroyFacialTrackerHTC(handle); return result == XrResult.XR_SUCCESS; } public bool TryGetFacialData(ulong handle,out bool isActive,int maxExpressioncount,out XrResult result,float[] blendshapes) { XrFacialExpressionsHTC facialExpression = new XrFacialExpressionsHTC() { type = XrStructureType.XR_TYPE_FACIAL_EXPRESSIONS_HTC, expressionCount = maxExpressioncount, sampleTime = 10L, //An arbitrary number greater than 0 blendShapeWeightings = Marshal.AllocCoTaskMem(sizeof(float) * maxExpressioncount), }; result = (XrResult)xrGetFacialExpressionsHTC(handle, ref facialExpression); Marshal.Copy(facialExpression.blendShapeWeightings, blendshapes, 0, maxExpressioncount); isActive = facialExpression.isActive != 0u; return result == XrResult.XR_SUCCESS; } } } }