380 lines
13 KiB
C#
380 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Input;
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using Microsoft.MixedReality.Toolkit.XRSDK.Input;
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.XR;
|
|
|
|
#if OCULUS_ENABLED
|
|
using Unity.XR.Oculus;
|
|
#endif // OCULUS_ENABLED
|
|
|
|
#if OCULUSINTEGRATION_PRESENT
|
|
using System.Collections.Generic;
|
|
#endif // OCULUSINTEGRATION_PRESENT
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.XRSDK.Oculus.Input
|
|
{
|
|
/// <summary>
|
|
/// Manages XR SDK devices on the Oculus platform.
|
|
/// </summary>
|
|
[MixedRealityDataProvider(
|
|
typeof(IMixedRealityInputSystem),
|
|
SupportedPlatforms.WindowsStandalone | SupportedPlatforms.Android,
|
|
"XR SDK Oculus Device Manager",
|
|
"Oculus/XRSDK/Profiles/DefaultOculusXRSDKDeviceManagerProfile.asset",
|
|
"MixedRealityToolkit.Providers",
|
|
true,
|
|
SupportedUnityXRPipelines.XRSDK)]
|
|
public class OculusXRSDKDeviceManager : XRSDKDeviceManager
|
|
{
|
|
/// <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 OculusXRSDKDeviceManager(
|
|
IMixedRealityInputSystem inputSystem,
|
|
string name = null,
|
|
uint priority = DefaultPriority,
|
|
BaseMixedRealityProfile profile = null) : base(inputSystem, name, priority, profile) { }
|
|
|
|
#if !OCULUSINTEGRATION_PRESENT && UNITY_EDITOR && UNITY_ANDROID
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
#if !UNITY_2020_1_OR_NEWER
|
|
UnityEngine.Debug.Log(@"Detected a potential deployment issue for the Oculus Quest. In order to use hand tracking with the Oculus Quest, download the Oculus Integration Package from the Unity Asset Store and run the Integration tool before deploying.
|
|
The tool can be found under <i>Mixed Reality > Toolkit > Utilities > Oculus > Integrate Oculus Integration Unity Modules</i>");
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if OCULUSINTEGRATION_PRESENT
|
|
private readonly Dictionary<Handedness, OculusHand> trackedHands = new Dictionary<Handedness, OculusHand>();
|
|
|
|
private OVRCameraRig cameraRig;
|
|
internal OVRControllerHelper leftControllerHelper;
|
|
internal OVRControllerHelper rightControllerHelper;
|
|
|
|
private OVRHand rightHand;
|
|
private OVRSkeleton rightSkeleton;
|
|
|
|
private OVRHand leftHand;
|
|
private OVRSkeleton leftSkeleton;
|
|
|
|
/// <summary>
|
|
/// The profile that contains settings for the Oculus XRSDK Device Manager input data provider. This profile is nested under
|
|
/// Input > Input Data Providers > Oculus XRSDK Device Manager in the MixedRealityToolkit object in the hierarchy.
|
|
/// </summary>
|
|
private OculusXRSDKDeviceManagerProfile SettingsProfile => ConfigurationProfile as OculusXRSDKDeviceManagerProfile;
|
|
#endif
|
|
|
|
#region IMixedRealityCapabilityCheck Implementation
|
|
|
|
/// <inheritdoc />
|
|
public override bool CheckCapability(MixedRealityCapability capability)
|
|
{
|
|
#if OCULUSINTEGRATION_PRESENT
|
|
if (capability == MixedRealityCapability.ArticulatedHand)
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
return capability == MixedRealityCapability.MotionController;
|
|
}
|
|
|
|
#endregion IMixedRealityCapabilityCheck Implementation
|
|
|
|
#region Controller Utilities
|
|
|
|
/// <inheritdoc />
|
|
protected override Type GetControllerType(SupportedControllerType supportedControllerType)
|
|
{
|
|
switch (supportedControllerType)
|
|
{
|
|
case SupportedControllerType.ArticulatedHand:
|
|
return typeof(OculusHand);
|
|
case SupportedControllerType.OculusTouch:
|
|
return typeof(OculusXRSDKTouchController);
|
|
default:
|
|
return base.GetControllerType(supportedControllerType);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override InputSourceType GetInputSourceType(SupportedControllerType supportedControllerType)
|
|
{
|
|
switch (supportedControllerType)
|
|
{
|
|
case SupportedControllerType.OculusTouch:
|
|
return InputSourceType.Controller;
|
|
case SupportedControllerType.ArticulatedHand:
|
|
return InputSourceType.Hand;
|
|
default:
|
|
return base.GetInputSourceType(supportedControllerType);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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 articulated hand
|
|
return SupportedControllerType.ArticulatedHand;
|
|
}
|
|
}
|
|
|
|
if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Controller))
|
|
{
|
|
return SupportedControllerType.OculusTouch;
|
|
}
|
|
|
|
return base.GetCurrentControllerType(inputDevice);
|
|
}
|
|
|
|
#endregion Controller Utilities
|
|
|
|
private bool? IsActiveLoader =>
|
|
#if OCULUS_ENABLED
|
|
LoaderHelpers.IsLoaderActive<OculusLoader>();
|
|
#else
|
|
false;
|
|
#endif // OCULUS_ENABLED
|
|
|
|
/// <inheritdoc/>
|
|
public override void Enable()
|
|
{
|
|
if (!IsActiveLoader.HasValue)
|
|
{
|
|
IsEnabled = false;
|
|
EnableIfLoaderBecomesActive();
|
|
return;
|
|
}
|
|
else if (!IsActiveLoader.Value)
|
|
{
|
|
IsEnabled = false;
|
|
return;
|
|
}
|
|
|
|
#if OCULUSINTEGRATION_PRESENT
|
|
SetupInput();
|
|
ConfigurePerformancePreferences();
|
|
#endif // OCULUSINTEGRATION_PRESENT
|
|
|
|
base.Enable();
|
|
}
|
|
|
|
private async void EnableIfLoaderBecomesActive()
|
|
{
|
|
await new WaitUntil(() => IsActiveLoader.HasValue);
|
|
if (IsActiveLoader.Value)
|
|
{
|
|
Enable();
|
|
}
|
|
}
|
|
|
|
#if OCULUSINTEGRATION_PRESENT
|
|
/// <inheritdoc/>
|
|
public override void Update()
|
|
{
|
|
if (!IsEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
base.Update();
|
|
|
|
if (OVRPlugin.GetHandTrackingEnabled())
|
|
{
|
|
UpdateHands();
|
|
}
|
|
else
|
|
{
|
|
RemoveAllHandDevices();
|
|
}
|
|
}
|
|
|
|
|
|
private void SetupInput()
|
|
{
|
|
cameraRig = GameObject.FindObjectOfType<OVRCameraRig>();
|
|
if (cameraRig == null)
|
|
{
|
|
var mainCamera = CameraCache.Main;
|
|
|
|
// Instantiate camera rig as a child of the MixedRealityPlayspace
|
|
|
|
var cameraRigObject = GameObject.Instantiate(SettingsProfile.OVRCameraRigPrefab);
|
|
cameraRig = cameraRigObject.GetComponent<OVRCameraRig>();
|
|
|
|
// Ensure all related game objects are configured
|
|
cameraRig.EnsureGameObjectIntegrity();
|
|
|
|
if (mainCamera != null)
|
|
{
|
|
// We already had a main camera MRTK probably started using, let's replace the CenterEyeAnchor MainCamera with it
|
|
GameObject prefabMainCamera = cameraRig.trackingSpace.Find("CenterEyeAnchor").gameObject;
|
|
prefabMainCamera.SetActive(false);
|
|
mainCamera.transform.SetParent(cameraRig.trackingSpace.transform);
|
|
mainCamera.name = prefabMainCamera.name;
|
|
GameObject.Destroy(prefabMainCamera);
|
|
}
|
|
cameraRig.transform.SetParent(MixedRealityPlayspace.Transform);
|
|
}
|
|
else
|
|
{
|
|
// Ensure all related game objects are configured
|
|
cameraRig.EnsureGameObjectIntegrity();
|
|
}
|
|
|
|
bool useAvatarHands = SettingsProfile.RenderAvatarHandsWithControllers;
|
|
// If using Avatar hands, initialize the local avatar controller
|
|
if (useAvatarHands)
|
|
{
|
|
GameObject.Instantiate(SettingsProfile.LocalAvatarPrefab, cameraRig.trackingSpace);
|
|
}
|
|
|
|
|
|
var ovrControllerHelpers = cameraRig.GetComponentsInChildren<OVRControllerHelper>();
|
|
foreach (var ovrControllerHelper in ovrControllerHelpers)
|
|
{
|
|
switch (ovrControllerHelper.m_controller)
|
|
{
|
|
case OVRInput.Controller.LTouch:
|
|
leftControllerHelper = ovrControllerHelper;
|
|
break;
|
|
case OVRInput.Controller.RTouch:
|
|
rightControllerHelper = ovrControllerHelper;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
var ovrHands = cameraRig.GetComponentsInChildren<OVRHand>();
|
|
foreach (var ovrHand in ovrHands)
|
|
{
|
|
// Manage Hand skeleton data
|
|
var skeletonDataProvider = ovrHand as OVRSkeleton.IOVRSkeletonDataProvider;
|
|
var skeletonType = skeletonDataProvider.GetSkeletonType();
|
|
|
|
var ovrSkeleton = ovrHand.GetComponent<OVRSkeleton>();
|
|
if (ovrSkeleton == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (skeletonType)
|
|
{
|
|
case OVRSkeleton.SkeletonType.HandLeft:
|
|
leftHand = ovrHand;
|
|
leftSkeleton = ovrSkeleton;
|
|
break;
|
|
case OVRSkeleton.SkeletonType.HandRight:
|
|
rightHand = ovrHand;
|
|
rightSkeleton = ovrSkeleton;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ConfigurePerformancePreferences()
|
|
{
|
|
SettingsProfile.ApplyConfiguredPerformanceSettings();
|
|
}
|
|
|
|
#region Hand Utilities
|
|
|
|
protected void UpdateHands()
|
|
{
|
|
UpdateHand(rightHand, rightSkeleton, Handedness.Right);
|
|
UpdateHand(leftHand, leftSkeleton, Handedness.Left);
|
|
}
|
|
|
|
protected void UpdateHand(OVRHand ovrHand, OVRSkeleton ovrSkeleton, Handedness handedness)
|
|
{
|
|
if (ovrHand.IsTracked)
|
|
{
|
|
var hand = GetOrAddHand(handedness, ovrHand);
|
|
hand.UpdateController(ovrHand, ovrSkeleton, cameraRig.trackingSpace);
|
|
}
|
|
else
|
|
{
|
|
RemoveHandDevice(handedness);
|
|
}
|
|
}
|
|
|
|
private OculusHand GetOrAddHand(Handedness handedness, OVRHand ovrHand)
|
|
{
|
|
if (trackedHands.ContainsKey(handedness))
|
|
{
|
|
return trackedHands[handedness];
|
|
}
|
|
|
|
// Add new hand
|
|
var pointers = RequestPointers(SupportedControllerType.ArticulatedHand, handedness);
|
|
var inputSourceType = InputSourceType.Hand;
|
|
|
|
var inputSource = Service?.RequestNewGenericInputSource($"Oculus Quest {handedness} Hand", pointers, inputSourceType);
|
|
|
|
|
|
OculusHand handDevice = new OculusHand(TrackingState.Tracked, handedness, inputSource);
|
|
handDevice.InitializeHand(ovrHand, SettingsProfile);
|
|
|
|
for (int i = 0; i < handDevice.InputSource?.Pointers?.Length; i++)
|
|
{
|
|
handDevice.InputSource.Pointers[i].Controller = handDevice;
|
|
}
|
|
|
|
Service?.RaiseSourceDetected(handDevice.InputSource, handDevice);
|
|
|
|
trackedHands.Add(handedness, handDevice);
|
|
|
|
return handDevice;
|
|
}
|
|
|
|
private void RemoveHandDevice(Handedness handedness)
|
|
{
|
|
if (trackedHands.TryGetValue(handedness, out OculusHand hand))
|
|
{
|
|
RemoveHandDevice(hand);
|
|
}
|
|
}
|
|
|
|
private void RemoveAllHandDevices()
|
|
{
|
|
if (trackedHands.Count == 0) return;
|
|
|
|
// Create a new list to avoid causing an error removing items from a list currently being iterated on.
|
|
foreach (var hand in new List<OculusHand>(trackedHands.Values))
|
|
{
|
|
RemoveHandDevice(hand);
|
|
}
|
|
trackedHands.Clear();
|
|
}
|
|
|
|
private void RemoveHandDevice(OculusHand handDevice)
|
|
{
|
|
if (handDevice == null) return;
|
|
|
|
CoreServices.InputSystem?.RaiseSourceLost(handDevice.InputSource, handDevice);
|
|
trackedHands.Remove(handDevice.ControllerHandedness);
|
|
|
|
RecyclePointers(handDevice.InputSource);
|
|
}
|
|
|
|
#endregion
|
|
#endif // OCULUSINTEGRATION_PRESENT
|
|
}
|
|
}
|