320 lines
13 KiB
C#
320 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Input;
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Profiling;
|
|
using UnityEngine;
|
|
using UnityEngine.XR;
|
|
|
|
#if XR_MANAGEMENT_ENABLED
|
|
using UnityEngine.XR.Management;
|
|
#endif // XR_MANAGEMENT_ENABLED
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.XRSDK.Input
|
|
{
|
|
/// <summary>
|
|
/// Manages XR SDK devices.
|
|
/// </summary>
|
|
[MixedRealityDataProvider(
|
|
typeof(IMixedRealityInputSystem),
|
|
SupportedPlatforms.WindowsStandalone | SupportedPlatforms.MacStandalone | SupportedPlatforms.LinuxStandalone | SupportedPlatforms.WindowsUniversal,
|
|
"XR SDK Device Manager",
|
|
supportedUnityXRPipelines: SupportedUnityXRPipelines.XRSDK)]
|
|
public class XRSDKDeviceManager : BaseInputDeviceManager, IMixedRealityCapabilityCheck
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="inputSystem">The <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/> instance that receives data from this provider.</param>
|
|
/// <param name="name">Friendly name of the service.</param>
|
|
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
|
|
/// <param name="profile">The service's configuration profile.</param>
|
|
public XRSDKDeviceManager(
|
|
IMixedRealityInputSystem inputSystem,
|
|
string name = null,
|
|
uint priority = DefaultPriority,
|
|
BaseMixedRealityProfile profile = null) : base(inputSystem, name, priority, profile) { }
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool CheckCapability(MixedRealityCapability capability)
|
|
{
|
|
// The XR SDK platform supports motion controllers.
|
|
return (capability == MixedRealityCapability.MotionController);
|
|
}
|
|
|
|
protected readonly Dictionary<InputDevice, GenericXRSDKController> ActiveControllers = new Dictionary<InputDevice, GenericXRSDKController>();
|
|
|
|
private readonly List<InputDevice> inputDevices = new List<InputDevice>();
|
|
private readonly List<InputDevice> inputDevicesSubset = new List<InputDevice>();
|
|
private readonly List<InputDevice> lastInputDevices = new List<InputDevice>();
|
|
|
|
protected virtual List<InputDeviceCharacteristics> DesiredInputCharacteristics { get; set; } = new List<InputDeviceCharacteristics>()
|
|
{
|
|
InputDeviceCharacteristics.Controller,
|
|
InputDeviceCharacteristics.HandTracking
|
|
};
|
|
|
|
#if XR_MANAGEMENT_ENABLED
|
|
/// <summary>
|
|
/// Checks if the active loader has a specific name. Used in cases where the loader class is internal, like WindowsMRLoader.
|
|
/// </summary>
|
|
/// <param name="loaderName">The string name to compare against the active loader.</param>
|
|
/// <returns>True if the active loader has the same name as the parameter.</returns>
|
|
[Obsolete("Use XRSDKLoaderHelpers instead.")]
|
|
protected virtual bool IsLoaderActive(string loaderName) => LoaderHelpers.IsLoaderActive(loaderName) ?? false;
|
|
|
|
/// <summary>
|
|
/// Checks if the active loader is of a specific type. Used in cases where the loader class is accessible, like OculusLoader.
|
|
/// </summary>
|
|
/// <typeparam name="T">The loader class type to check against the active loader.</typeparam>
|
|
/// <returns>True if the active loader is of the specified type.</returns>
|
|
[Obsolete("Use XRSDKLoaderHelpers instead.")]
|
|
protected virtual bool IsLoaderActive<T>() where T : XRLoader => LoaderHelpers.IsLoaderActive<T>() ?? false;
|
|
#endif // XR_MANAGEMENT_ENABLED
|
|
|
|
private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.Update");
|
|
|
|
public override void Enable()
|
|
{
|
|
base.Enable();
|
|
|
|
inputDevices.Clear();
|
|
foreach (InputDeviceCharacteristics inputDeviceCharacteristics in DesiredInputCharacteristics)
|
|
{
|
|
InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, inputDevicesSubset);
|
|
foreach (InputDevice device in inputDevicesSubset)
|
|
{
|
|
if (!inputDevices.Contains(device))
|
|
{
|
|
inputDevices.Add(device);
|
|
}
|
|
}
|
|
}
|
|
|
|
InputDevices.deviceConnected += InputDevices_deviceConnected;
|
|
InputDevices.deviceDisconnected += InputDevices_deviceDisconnected;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Update()
|
|
{
|
|
using (UpdatePerfMarker.Auto())
|
|
{
|
|
if (!IsEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
base.Update();
|
|
|
|
if (XRSubsystemHelpers.InputSubsystem == null || !XRSubsystemHelpers.InputSubsystem.running)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (InputDevice device in inputDevices)
|
|
{
|
|
if (device.isValid)
|
|
{
|
|
GenericXRSDKController controller = GetOrAddController(device);
|
|
|
|
if (controller != null && lastInputDevices.Contains(device))
|
|
{
|
|
// Remove devices from our previously tracked list as we update them.
|
|
// This will allow us to remove all stale devices that were tracked
|
|
// last frame but not this one.
|
|
lastInputDevices.Remove(device);
|
|
controller.UpdateController(device);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (InputDevice device in lastInputDevices)
|
|
{
|
|
RemoveController(device);
|
|
}
|
|
|
|
lastInputDevices.Clear();
|
|
lastInputDevices.AddRange(inputDevices);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Disable()
|
|
{
|
|
InputDevices.deviceConnected -= InputDevices_deviceConnected;
|
|
InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected;
|
|
|
|
var controllersCopy = ActiveControllers.ToReadOnlyCollection();
|
|
foreach (KeyValuePair<InputDevice, GenericXRSDKController> controller in controllersCopy)
|
|
{
|
|
RemoveController(controller.Key);
|
|
}
|
|
|
|
base.Disable();
|
|
}
|
|
|
|
private void InputDevices_deviceConnected(InputDevice obj)
|
|
{
|
|
bool characteristicsMatch = (obj.characteristics & (InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.HandTracking)) > 0;
|
|
if (characteristicsMatch && !inputDevices.Contains(obj))
|
|
{
|
|
inputDevices.Add(obj);
|
|
}
|
|
}
|
|
|
|
private void InputDevices_deviceDisconnected(InputDevice obj)
|
|
{
|
|
inputDevices.Remove(obj);
|
|
}
|
|
|
|
#region Controller Utilities
|
|
|
|
private static readonly ProfilerMarker GetOrAddControllerPerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.GetOrAddController");
|
|
|
|
/// <summary>
|
|
/// Gets or adds a controller using the InputDevice name provided.
|
|
/// </summary>
|
|
/// <param name="inputDevice">The InputDevice from XR SDK.</param>
|
|
/// <returns>The controller reference.</returns>
|
|
protected virtual GenericXRSDKController GetOrAddController(InputDevice inputDevice)
|
|
{
|
|
using (GetOrAddControllerPerfMarker.Auto())
|
|
{
|
|
// If a device is already registered with the ID provided, just return it.
|
|
if (ActiveControllers.ContainsKey(inputDevice))
|
|
{
|
|
var controller = ActiveControllers[inputDevice];
|
|
Debug.Assert(controller != null);
|
|
return controller;
|
|
}
|
|
|
|
Handedness controllingHand;
|
|
|
|
if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Left))
|
|
{
|
|
controllingHand = Handedness.Left;
|
|
}
|
|
else if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Right))
|
|
{
|
|
controllingHand = Handedness.Right;
|
|
}
|
|
else
|
|
{
|
|
controllingHand = Handedness.None;
|
|
}
|
|
|
|
SupportedControllerType currentControllerType = GetCurrentControllerType(inputDevice);
|
|
Type controllerType = GetControllerType(currentControllerType);
|
|
|
|
if (controllerType == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
InputSourceType inputSourceType = GetInputSourceType(currentControllerType);
|
|
|
|
IMixedRealityPointer[] pointers = RequestPointers(currentControllerType, controllingHand);
|
|
IMixedRealityInputSource inputSource = Service?.RequestNewGenericInputSource($"{currentControllerType} Controller {controllingHand}", pointers, inputSourceType);
|
|
GenericXRSDKController detectedController = Activator.CreateInstance(controllerType, TrackingState.NotTracked, controllingHand, inputSource, null) as GenericXRSDKController;
|
|
|
|
if (detectedController == null || !detectedController.Enabled)
|
|
{
|
|
// Controller failed to be set up correctly.
|
|
Debug.LogError($"Failed to create {controllerType.Name} controller");
|
|
|
|
// 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;
|
|
}
|
|
|
|
ActiveControllers.Add(inputDevice, detectedController);
|
|
|
|
Service?.RaiseSourceDetected(detectedController.InputSource, detectedController);
|
|
|
|
return detectedController;
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker RemoveControllerPerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.RemoveController");
|
|
|
|
/// <summary>
|
|
/// Gets the current controller type for the InputDevice name provided.
|
|
/// </summary>
|
|
/// <param name="inputDevice">The InputDevice from XR SDK.</param>
|
|
protected virtual void RemoveController(InputDevice inputDevice)
|
|
{
|
|
using (RemoveControllerPerfMarker.Auto())
|
|
{
|
|
if (ActiveControllers.TryGetValue(inputDevice, out GenericXRSDKController controller))
|
|
{
|
|
if (controller != null)
|
|
{
|
|
RemoveControllerFromScene(controller);
|
|
}
|
|
|
|
ActiveControllers.Remove(inputDevice);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the controller from the scene and handles any additional cleanup
|
|
/// </summary>
|
|
protected void RemoveControllerFromScene(GenericXRSDKController controller)
|
|
{
|
|
Service?.RaiseSourceLost(controller.InputSource, controller);
|
|
|
|
RecyclePointers(controller.InputSource);
|
|
|
|
var visualizer = controller.Visualizer;
|
|
|
|
if (!visualizer.IsNull() &&
|
|
visualizer.GameObjectProxy != null)
|
|
{
|
|
visualizer.GameObjectProxy.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the concrete type of the detected controller, based on the <see cref="Toolkit.Input.SupportedControllerType"/> and defined per-platform.
|
|
/// </summary>
|
|
/// <param name="supportedControllerType">The current controller type.</param>
|
|
/// <returns>The concrete type of the currently detected controller.</returns>
|
|
protected virtual Type GetControllerType(SupportedControllerType supportedControllerType)
|
|
{
|
|
return typeof(GenericXRSDKController);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the <see cref="Toolkit.Input.InputSourceType"/> of the currently detected controller, based on the <see cref="Toolkit.Input.SupportedControllerType"/>.
|
|
/// </summary>
|
|
/// <param name="supportedControllerType">The current controller type.</param>
|
|
/// <returns>The enum value of the currently detected controller's InputSource type.</returns>
|
|
protected virtual InputSourceType GetInputSourceType(SupportedControllerType supportedControllerType)
|
|
{
|
|
return InputSourceType.Controller;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current controller type for the InputDevice name provided.
|
|
/// </summary>
|
|
/// <param name="inputDevice">The InputDevice from XR SDK.</param>
|
|
/// <returns>The supported controller type.</returns>
|
|
protected virtual SupportedControllerType GetCurrentControllerType(InputDevice inputDevice)
|
|
{
|
|
Debug.Log($"{inputDevice.name} does not have a defined controller type, falling back to generic controller type");
|
|
return SupportedControllerType.GenericUnity;
|
|
}
|
|
|
|
#endregion Controller Utilities
|
|
}
|
|
}
|