// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.Utilities; using Microsoft.MixedReality.Toolkit.Windows.Utilities; using Microsoft.MixedReality.Toolkit.WindowsMixedReality; using Microsoft.MixedReality.Toolkit.XRSDK.Input; using System; using UnityEngine; using UnityEngine.XR; #if HP_CONTROLLER_ENABLED using Microsoft.MixedReality.Input; using MotionControllerHandedness = Microsoft.MixedReality.Input.Handedness; using System.Collections.Generic; using Unity.Profiling; #endif #if WINDOWS_UWP using Windows.Perception; using Windows.Perception.People; using Windows.UI.Input.Spatial; #elif UNITY_WSA && DOTNETWINRT_PRESENT using Microsoft.Windows.Perception; using Microsoft.Windows.Perception.People; using Microsoft.Windows.UI.Input.Spatial; #endif namespace Microsoft.MixedReality.Toolkit.XRSDK.WindowsMixedReality { /// /// Manages XR SDK devices on the Windows Mixed Reality platform. /// [MixedRealityDataProvider( typeof(IMixedRealityInputSystem), SupportedPlatforms.WindowsStandalone | SupportedPlatforms.WindowsUniversal, "XR SDK Windows Mixed Reality Device Manager", supportedUnityXRPipelines: SupportedUnityXRPipelines.XRSDK)] public class WindowsMixedRealityDeviceManager : XRSDKDeviceManager { /// /// Constructor. /// /// The instance that receives data from this provider. /// Friendly name of the service. /// Service priority. Used to determine order of instantiation. /// The service's configuration profile. public WindowsMixedRealityDeviceManager( IMixedRealityInputSystem inputSystem, string name = null, uint priority = DefaultPriority, BaseMixedRealityProfile profile = null) : base(inputSystem, name, priority, profile) { } private bool? IsActiveLoader => #if WMR_ENABLED LoaderHelpers.IsLoaderActive("Windows MR Loader"); #else false; #endif // WMR_ENABLED #region IMixedRealityDeviceManager Interface /// public override void Enable() { if (!IsActiveLoader.HasValue) { IsEnabled = false; EnableIfLoaderBecomesActive(); return; } else if (!IsActiveLoader.Value) { IsEnabled = false; return; } if (WindowsMixedRealityUtilities.UtilitiesProvider == null) { WindowsMixedRealityUtilities.UtilitiesProvider = new XRSDKWindowsMixedRealityUtilitiesProvider(); } #if (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP mixedRealityGazeProviderHeadOverride = Service?.GazeProvider as IMixedRealityGazeProviderHeadOverride; #endif // (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP #if WINDOWS_UWP WindowsMixedRealityUtilities.SpatialInteractionManager.SourcePressed += SpatialInteractionManager_SourcePressed; #endif // WINDOWS_UWP #if HP_CONTROLLER_ENABLED // Listens to events to track the HP Motion Controller motionControllerWatcher = new MotionControllerWatcher(); motionControllerWatcher.MotionControllerAdded += AddTrackedMotionController; motionControllerWatcher.MotionControllerRemoved += RemoveTrackedMotionController; var nowait = motionControllerWatcher.StartAsync(); #endif // HP_CONTROLLER_ENABLED base.Enable(); } private async void EnableIfLoaderBecomesActive() { await new WaitUntil(() => IsActiveLoader.HasValue); if (IsActiveLoader.Value) { Enable(); } } #if (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP private IMixedRealityGazeProviderHeadOverride mixedRealityGazeProviderHeadOverride = null; /// public override void Update() { if (!IsEnabled) { return; } // Override gaze before base.Update() updates the controllers if (mixedRealityGazeProviderHeadOverride != null && mixedRealityGazeProviderHeadOverride.UseHeadGazeOverride && WindowsMixedRealityUtilities.SpatialCoordinateSystem != null) { SpatialPointerPose pointerPose = SpatialPointerPose.TryGetAtTimestamp(WindowsMixedRealityUtilities.SpatialCoordinateSystem, PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.Now)); if (pointerPose != null) { HeadPose head = pointerPose.Head; if (head != null) { mixedRealityGazeProviderHeadOverride.OverrideHeadGaze(head.Position.ToUnityVector3(), head.ForwardDirection.ToUnityVector3()); } } } #if WINDOWS_UWP if (shouldSendVoiceEvents) { WindowsMixedRealityXRSDKGGVHand controller = GetOrAddVoiceController(); if (controller != null) { // RaiseOnInputDown for "select" controller.UpdateVoiceState(true); // RaiseOnInputUp for "select" controller.UpdateVoiceState(false); // On WMR, the voice recognizer does not actually register the phrase 'select' // when you add it to the speech commands profile. Therefore, simulate // the "select" voice command running to ensure that we get a select voice command // registered. This is used by FocusProvider to detect when the select pointer is active. Service?.RaiseSpeechCommandRecognized(controller.InputSource, RecognitionConfidenceLevel.High, TimeSpan.MinValue, DateTime.Now, new SpeechCommands("select", KeyCode.Alpha1, MixedRealityInputAction.None)); } shouldSendVoiceEvents = false; } #endif // WINDOWS_UWP base.Update(); } #endif // (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP #if WINDOWS_UWP /// public override void Disable() { WindowsMixedRealityUtilities.SpatialInteractionManager.SourcePressed -= SpatialInteractionManager_SourcePressed; if (voiceController != null) { RemoveControllerFromScene(voiceController); voiceController = null; } base.Disable(); } #endif // WINDOWS_UWP #endregion IMixedRealityDeviceManager Interface #region IMixedRealityCapabilityCheck Implementation /// public override bool CheckCapability(MixedRealityCapability capability) { if (WindowsApiChecker.IsMethodAvailable( "Windows.UI.Input.Spatial", "SpatialInteractionManager", "IsSourceKindSupported")) { #if WINDOWS_UWP switch (capability) { case MixedRealityCapability.ArticulatedHand: case MixedRealityCapability.GGVHand: return SpatialInteractionManager.IsSourceKindSupported(SpatialInteractionSourceKind.Hand); case MixedRealityCapability.MotionController: return SpatialInteractionManager.IsSourceKindSupported(SpatialInteractionSourceKind.Controller); } #endif // WINDOWS_UWP } else // Pre-Windows 10 1903. { if (XRSubsystemHelpers.DisplaySubsystem != null && !XRSubsystemHelpers.DisplaySubsystem.displayOpaque) { // HoloLens supports GGV hands return capability == MixedRealityCapability.GGVHand; } else { // Windows Mixed Reality immersive devices support motion controllers return capability == MixedRealityCapability.MotionController; } } return false; } #endregion IMixedRealityCapabilityCheck Implementation #region Controller Utilities #if HP_CONTROLLER_ENABLED private MotionControllerWatcher motionControllerWatcher; /// /// Dictionary to capture all active HP controllers detected /// private readonly Dictionary trackedMotionControllerStates = new Dictionary(); private readonly Dictionary activeMotionControllers = new Dictionary(); private static readonly ProfilerMarker GetOrAddControllerPerfMarker = new ProfilerMarker("[MRTK] WindwosMixedRealityXRSDKDeviceManager.GetOrAddController"); protected override GenericXRSDKController GetOrAddController(InputDevice inputDevice) { using (GetOrAddControllerPerfMarker.Auto()) { GenericXRSDKController detectedController = base.GetOrAddController(inputDevice); SupportedControllerType currentControllerType = GetCurrentControllerType(inputDevice); // Add the Motion Controller state if it's an HPMotionController if (currentControllerType == SupportedControllerType.HPMotionController) { lock (trackedMotionControllerStates) { uint controllerId = GetControllerId(inputDevice); if (trackedMotionControllerStates.ContainsKey(controllerId) && detectedController is HPMotionController hpController) { hpController.MotionControllerState = trackedMotionControllerStates[controllerId]; } } } return detectedController; } } private void AddTrackedMotionController(object sender, MotionController motionController) { lock (trackedMotionControllerStates) { uint controllerId = GetControllerId(motionController); trackedMotionControllerStates[controllerId] = new MotionControllerState(motionController); if (activeMotionControllers.ContainsKey(controllerId) && activeMotionControllers[controllerId] is HPMotionController hpController) { hpController.MotionControllerState = trackedMotionControllerStates[controllerId]; } } } private void RemoveTrackedMotionController(object sender, MotionController motionController) { lock (trackedMotionControllerStates) { uint controllerId = GetControllerId(motionController); trackedMotionControllerStates.Remove(controllerId); if (activeMotionControllers.ContainsKey(controllerId) && activeMotionControllers[controllerId] is HPMotionController hpController) { hpController.MotionControllerState = null; } } } // Creates a unique key for the controller based on its vendor ID, product ID, version number, and handedness private uint GetControllerId(uint handedness) { return handedness; } private uint GetControllerId(MotionController mc) { var handedness = (uint)(mc.Handedness == MotionControllerHandedness.Right ? 2 : (mc.Handedness == MotionControllerHandedness.Left ? 1 : 0)); return GetControllerId(handedness); } private uint GetControllerId(InputDevice inputDevice) { var handedness = (uint)(inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Right) ? 2 : (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Left) ? 1 : 0)); return GetControllerId(handedness); } #endif // HP_CONTROLLER_ENABLED /// protected override Type GetControllerType(SupportedControllerType supportedControllerType) { switch (supportedControllerType) { case SupportedControllerType.WindowsMixedReality: return typeof(WindowsMixedRealityXRSDKMotionController); case SupportedControllerType.HPMotionController: return typeof(HPMotionController); case SupportedControllerType.ArticulatedHand: return typeof(WindowsMixedRealityXRSDKArticulatedHand); case SupportedControllerType.GGVHand: return typeof(WindowsMixedRealityXRSDKGGVHand); default: return base.GetControllerType(supportedControllerType); } } /// protected override InputSourceType GetInputSourceType(SupportedControllerType supportedControllerType) { switch (supportedControllerType) { case SupportedControllerType.HPMotionController: case SupportedControllerType.WindowsMixedReality: return InputSourceType.Controller; case SupportedControllerType.ArticulatedHand: case SupportedControllerType.GGVHand: return InputSourceType.Hand; default: return base.GetInputSourceType(supportedControllerType); } } /// protected override SupportedControllerType GetCurrentControllerType(InputDevice inputDevice) { if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.HandTracking)) { if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Left) || inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Right)) { // If it's a hand with a reported handedness, assume HL2 articulated hand return SupportedControllerType.ArticulatedHand; } else { // Otherwise, assume HL1 hand return SupportedControllerType.GGVHand; } } if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Controller)) { // primary2DAxis represents the touchpad in Windows XR Plugin. // The HP motion controller doesn't have a touchpad, so we check for its existence in the feature usages. if (!inputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out _)) { return SupportedControllerType.HPMotionController; } else { return SupportedControllerType.WindowsMixedReality; } } return base.GetCurrentControllerType(inputDevice); } #endregion Controller Utilities #region SpatialInteractionManager events #if WINDOWS_UWP /// /// SDK Interaction Source Pressed Event handler. Used only for voice. /// /// SDK source pressed event arguments private void SpatialInteractionManager_SourcePressed(SpatialInteractionManager sender, SpatialInteractionSourceEventArgs args) { if (args.State.Source.Kind == SpatialInteractionSourceKind.Voice) { shouldSendVoiceEvents = true; } } private WindowsMixedRealityXRSDKGGVHand voiceController = null; private bool shouldSendVoiceEvents = false; private WindowsMixedRealityXRSDKGGVHand GetOrAddVoiceController() { if (voiceController != null) { return voiceController; } IMixedRealityInputSource inputSource = Service?.RequestNewGenericInputSource("Mixed Reality Voice", sourceType: InputSourceType.Voice); WindowsMixedRealityXRSDKGGVHand detectedController = new WindowsMixedRealityXRSDKGGVHand(TrackingState.NotTracked, Handedness.None, inputSource); if (!detectedController.Enabled) { // Controller failed to be setup correctly. // Return null so we don't raise the source detected. return null; } for (int i = 0; i < detectedController.InputSource?.Pointers?.Length; i++) { detectedController.InputSource.Pointers[i].Controller = detectedController; } Service?.RaiseSourceDetected(detectedController.InputSource, detectedController); voiceController = detectedController; return voiceController; } #endif // WINDOWS_UWP #endregion SpatialInteractionManager events } }