// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #if UNITY_WSA using UnityEngine; #if !UNITY_2017_2_OR_NEWER using UnityEngine.VR.WSA.Input; #else using UnityEngine.XR.WSA.Input; #if !UNITY_EDITOR using System; using System.Collections.Generic; using Windows.Devices.Haptics; using Windows.Foundation; using Windows.Perception; using Windows.Storage.Streams; using Windows.UI.Input.Spatial; #elif UNITY_EDITOR_WIN using System.Runtime.InteropServices; #endif #endif #endif namespace HoloToolkit.Unity { /// <summary> /// Extensions for the InteractionSource class to add haptics and expose the renderable model. /// </summary> public static class InteractionSourceExtensions { #if UNITY_2017_2_OR_NEWER #if UNITY_EDITOR_WIN && UNITY_WSA [DllImport("EditorMotionController")] private static extern bool StartHaptics([In] uint controllerId, [In] float intensity, [In] float durationInSeconds); [DllImport("EditorMotionController")] private static extern bool StopHaptics([In] uint controllerId); #endif // UNITY_EDITOR_WIN && UNITY_WSA // This value is standardized according to www.usb.org/developers/hidpage/HUTRR63b_-_Haptics_Page_Redline.pdf private const ushort ContinuousBuzzWaveform = 0x1004; #if UNITY_WSA public static void StartHaptics(this InteractionSource interactionSource, float intensity) { interactionSource.StartHaptics(intensity, float.MaxValue); } public static void StartHaptics(this InteractionSource interactionSource, float intensity, float durationInSeconds) { if (!WindowsApiChecker.UniversalApiContractV4_IsAvailable && !Application.isEditor) { return; } #if !UNITY_EDITOR UnityEngine.WSA.Application.InvokeOnUIThread(() => { IReadOnlyList<SpatialInteractionSourceState> sources = SpatialInteractionManager.GetForCurrentView().GetDetectedSourcesAtTimestamp(PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.Now)); foreach (SpatialInteractionSourceState sourceState in sources) { if (sourceState.Source.Id.Equals(interactionSource.id)) { SimpleHapticsController simpleHapticsController = sourceState.Source.Controller.SimpleHapticsController; foreach (SimpleHapticsControllerFeedback hapticsFeedback in simpleHapticsController.SupportedFeedback) { if (hapticsFeedback.Waveform.Equals(ContinuousBuzzWaveform)) { if (durationInSeconds.Equals(float.MaxValue)) { simpleHapticsController.SendHapticFeedback(hapticsFeedback, intensity); } else { simpleHapticsController.SendHapticFeedbackForDuration(hapticsFeedback, intensity, TimeSpan.FromSeconds(durationInSeconds)); } return; } } } } }, true); #elif UNITY_EDITOR_WIN StartHaptics(interactionSource.id, intensity, durationInSeconds); #endif // !UNITY_EDITOR } public static void StopHaptics(this InteractionSource interactionSource) { if (!WindowsApiChecker.UniversalApiContractV4_IsAvailable && !Application.isEditor) { return; } #if !UNITY_EDITOR UnityEngine.WSA.Application.InvokeOnUIThread(() => { IReadOnlyList<SpatialInteractionSourceState> sources = SpatialInteractionManager.GetForCurrentView().GetDetectedSourcesAtTimestamp(PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.Now)); foreach (SpatialInteractionSourceState sourceState in sources) { if (sourceState.Source.Id.Equals(interactionSource.id)) { sourceState.Source.Controller.SimpleHapticsController.StopFeedback(); } } }, true); #elif UNITY_EDITOR_WIN StopHaptics(interactionSource.id); #endif // !UNITY_EDITOR } #if !UNITY_EDITOR public static IAsyncOperation<IRandomAccessStreamWithContentType> TryGetRenderableModelAsync(this InteractionSource interactionSource) { IAsyncOperation<IRandomAccessStreamWithContentType> returnValue = null; if (WindowsApiChecker.UniversalApiContractV5_IsAvailable) { UnityEngine.WSA.Application.InvokeOnUIThread(() => { IReadOnlyList<SpatialInteractionSourceState> sources = SpatialInteractionManager.GetForCurrentView().GetDetectedSourcesAtTimestamp(PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.Now)); foreach (SpatialInteractionSourceState sourceState in sources) { if (sourceState.Source.Id.Equals(interactionSource.id)) { returnValue = sourceState.Source.Controller.TryGetRenderableModelAsync(); } } }, true); } return returnValue; } #endif // !UNITY_EDITOR #endif // UNITY_WSA #endif // UNITY_2017_2_OR_NEWER } }