// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.XR.OpenXR; namespace Microsoft.MixedReality.OpenXR { [Flags] internal enum NativeDirectionFlags { X = 1, Y = 2, Z = 4, } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct NativeGesturePoseData { public ulong gestureTime; public Pose headPose; public NativeSpaceLocationFlags headPoseFlags; public Pose eyeGazePose; public NativeSpaceLocationFlags eyeGazePoseFlags; public Pose handAimPose; public NativeSpaceLocationFlags handAimPoseFlags; public Pose handGripPose; public NativeSpaceLocationFlags handGripPoseFlags; } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct NativeGestureEventData { public GestureEventType eventType; public GestureHandedness handedness; public NativeGesturePoseData poseData; public TappedEventData tappedData; public ManipulationEventData manipulationData; public NavigationEventData navigationData; } internal static class GestureSubsystemExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsValid(this NativeSpaceLocationFlags flags) { return flags.HasFlag(NativeSpaceLocationFlags.OrientationValid) && flags.HasFlag(NativeSpaceLocationFlags.PositionValid); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsTracked(this NativeSpaceLocationFlags flags) { return flags.HasFlag(NativeSpaceLocationFlags.OrientationTracked) && flags.HasFlag(NativeSpaceLocationFlags.PositionTracked); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsTappedEvent(this NativeGestureEventData eventData) { return eventData.eventType.HasFlag(GestureEventType.Tapped); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsManipulationEvent(this NativeGestureEventData eventData) { var eventType = eventData.eventType; return eventType.HasFlag(GestureEventType.ManipulationStarted) || eventType.HasFlag(GestureEventType.ManipulationUpdated) || eventType.HasFlag(GestureEventType.ManipulationCompleted); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNavigationEvent(this NativeGestureEventData eventData) { var eventType = eventData.eventType; return eventType.HasFlag(GestureEventType.NavigationStarted) || eventType.HasFlag(GestureEventType.NavigationUpdated) || eventType.HasFlag(GestureEventType.NavigationCompleted); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? Get(this NativeGestureEventData eventData, T value, bool hasValue) where T : struct { if (hasValue) { return value; } return null; } } internal class GestureSubsystem : Disposable { private static MixedRealityFeaturePlugin Feature => OpenXRFeaturePlugin.Feature; private readonly ulong m_gestureRecognizerHandle = 0; private GestureSettings m_gestureSettings = GestureSettings.None; private bool m_running = false; private readonly object m_runningLock = new object(); internal static GestureSubsystem TryCreateGestureSubsystem(GestureSettings settings) { if (!Feature.IsValidAndEnabled()) { Debug.LogWarning($"{MixedRealityFeaturePlugin.featureName} is not enabled."); return null; } ulong handle = NativeLib.TryCreateGestureRecognizer(settings); if (handle == 0) { Debug.LogWarning($"GestureSubsystem is not supported with settings: {settings}."); return null; } return new GestureSubsystem(settings, handle); } private GestureSubsystem(GestureSettings settings, ulong handle) { m_gestureRecognizerHandle = handle; m_gestureSettings = settings; } internal GestureSettings GestureSettings { get { return m_gestureSettings; } set { if (m_gestureSettings != value) { if (NativeLib.TrySetGestureSettings(m_gestureRecognizerHandle, value)) { m_gestureSettings = value; } else { Debug.LogWarning($"Cannot set gesture setting to {value}"); } } } } internal bool TryGetNextEvent(ref GestureEventData eventData) { return NativeLib.TryGetNextEventData(m_gestureRecognizerHandle, ref eventData); } internal void CancelPendingGestures() { NativeLib.CancelPendingGesture(m_gestureRecognizerHandle); } protected override void DisposeNativeResources() { base.DisposeNativeResources(); NativeLib.DestroyGestureRecognizer(m_gestureRecognizerHandle); } internal void Start() { lock (m_runningLock) { if (m_running) { Debug.LogError($"GestureSubsystem is already started."); return; } NativeLib.StartGestureRecognizer(m_gestureRecognizerHandle); m_running = true; } } internal void Stop() { lock (m_runningLock) { if (!m_running) { Debug.LogError($"GestureSubsystem cannot be stopped before started."); return; } m_running = false; NativeLib.StopGestureRecognizer(m_gestureRecognizerHandle); } } } }