// 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
}
}