// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #if UNITY_WSA using Microsoft.MixedReality.Toolkit.Windows.Utilities; using UnityEngine.XR.WSA.Input; #endif // UNITY_WSA #if (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP using Microsoft.MixedReality.Toolkit.WindowsMixedReality; using System; using System.Collections.Generic; #endif // (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP #if WINDOWS_UWP using Windows.Devices.Haptics; using Windows.Foundation; using Windows.Perception; using Windows.Storage.Streams; using Windows.UI.Input.Spatial; #elif (UNITY_WSA && DOTNETWINRT_PRESENT) using Microsoft.Windows.Devices.Haptics; using Microsoft.Windows.Perception; using Microsoft.Windows.UI.Input.Spatial; #endif namespace Microsoft.MixedReality.Toolkit.Windows.Input { /// /// Extensions for the InteractionSource class to add haptics and expose the renderable model. /// public static class InteractionSourceExtensions { #if (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP /// /// Gets the current native SpatialInteractionSourceState for this InteractionSource. /// /// This InteractionSource to search for via the native Windows APIs. /// The current native SpatialInteractionSourceState. public static SpatialInteractionSourceState GetSpatialInteractionSourceState(this InteractionSource interactionSource) { IReadOnlyList sources = WindowsMixedRealityUtilities.SpatialInteractionManager?.GetDetectedSourcesAtTimestamp(PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.UtcNow)); for (var i = 0; i < sources?.Count; i++) { if (sources[i].Source.Id.Equals(interactionSource.id)) { return sources[i]; } } return null; } /// /// Gets the current native SpatialInteractionSource for this InteractionSource. /// /// The InteractionSource to search for via the native Windows APIs. /// The current native SpatialInteractionSource. public static SpatialInteractionSource GetSpatialInteractionSource(this InteractionSource interactionSource) => interactionSource.GetSpatialInteractionSourceState()?.Source; #endif // (UNITY_WSA && DOTNETWINRT_PRESENT) || WINDOWS_UWP #if UNITY_WSA private const string HapticsNamespace = "Windows.Devices.Haptics"; private const string SimpleHapticsController = "SimpleHapticsController"; private const string SendHapticFeedback = "SendHapticFeedback"; /// /// This value is standardized according to https://www.usb.org/sites/default/files/hutrr63b_-_haptics_page_redline_0.pdf. /// private const ushort ContinuousBuzzWaveform = 0x1004; private static readonly bool IsHapticsAvailable = WindowsApiChecker.IsMethodAvailable(HapticsNamespace, SimpleHapticsController, SendHapticFeedback); /// /// Start haptic feedback on the interaction source with the specified intensity. /// /// The source to start haptics on. /// The strength of the haptic feedback from 0.0 (no haptics) to 1.0 (maximum strength). public static bool StartHaptics(this InteractionSource interactionSource, float intensity) => interactionSource.StartHaptics(intensity, float.MaxValue); /// /// Start haptic feedback on the interaction source with the specified intensity and continue for the specified amount of time. /// /// The source to start haptics on. /// The strength of the haptic feedback from 0.0 (no haptics) to 1.0 (maximum strength). /// The time period expressed in seconds. public static bool StartHaptics(this InteractionSource interactionSource, float intensity, float durationInSeconds) { if (!IsHapticsAvailable) { return false; } #if WINDOWS_UWP || DOTNETWINRT_PRESENT SimpleHapticsController simpleHapticsController = interactionSource.GetSpatialInteractionSource()?.Controller.SimpleHapticsController; foreach (SimpleHapticsControllerFeedback hapticsFeedback in simpleHapticsController?.SupportedFeedback) { if (hapticsFeedback.Waveform.Equals(ContinuousBuzzWaveform)) { if (UnityEngine.Mathf.Approximately(durationInSeconds, float.MaxValue)) { simpleHapticsController.SendHapticFeedback(hapticsFeedback, intensity); } else { simpleHapticsController.SendHapticFeedbackForDuration(hapticsFeedback, intensity, TimeSpan.FromSeconds(durationInSeconds)); } return true; } } #endif // WINDOWS_UWP || DOTNETWINRT_PRESENT return false; } /// /// Stops haptics feedback on the specified interaction source. /// /// The source to stop haptics for. public static void StopHaptics(this InteractionSource interactionSource) { if (!IsHapticsAvailable) { return; } #if WINDOWS_UWP || DOTNETWINRT_PRESENT interactionSource.GetSpatialInteractionSource()?.Controller.SimpleHapticsController.StopFeedback(); #endif // WINDOWS_UWP || DOTNETWINRT_PRESENT } #endif // UNITY_WSA #if WINDOWS_UWP private const string SpatialNamespace = "Windows.UI.Input.Spatial"; private const string SpatialInteractionController = "SpatialInteractionController"; private const string TryGetRenderableModelAsyncName = "TryGetRenderableModelAsync"; private static readonly bool IsTryGetRenderableModelAvailable = WindowsApiChecker.IsMethodAvailable(SpatialNamespace, SpatialInteractionController, TryGetRenderableModelAsyncName); /// /// Attempts to call the Windows API for loading the controller renderable model at runtime. /// /// The source to try loading the model for. /// A stream of the glTF model for loading. /// Doesn't work in-editor. public static IAsyncOperation TryGetRenderableModelAsync(this InteractionSource interactionSource) { if (IsTryGetRenderableModelAvailable) { return interactionSource.GetSpatialInteractionSource()?.Controller.TryGetRenderableModelAsync(); } return null; } #endif // WINDOWS_UWP } }