mixedreality/com.microsoft.mixedreality..../SDK/Experimental/Features/Utilities/StabilizationPlaneModifier.cs

444 lines
17 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using UnityEngine;
#if UNITY_WSA && !UNITY_2020_1_OR_NEWER
using UnityEngine.XR.WSA;
#endif // UNITY_WSA && !UNITY_2020_1_OR_NEWER
namespace Microsoft.MixedReality.Toolkit.Experimental.Utilities
{
/// <summary>
/// StablizationPlaneOverride is a class used to describe the plane to be used by the StabilizationPlaneModifier class
/// </summary>
[Serializable]
public struct StabilizationPlaneOverride
{
/// <summary>
/// Center of the plane
/// </summary>
public Vector3 Center;
/// <summary>
/// Normal of the plane
/// </summary>
public Vector3 Normal;
}
/// <summary>
/// StabilizationPlaneModifier handles the setting of the stabilization plane in several different modes.
/// It does this via handling the platform call to HolographicPlatformSettings::SetFocusPointForFrame
/// Using StabilizationPlaneModifier will override DepthLSR. This is automatically enabled via the depth buffer sharing in Unity build settings
/// StabilizationPlaneModifier is recommended for HoloLens 1, can be used for HoloLens 2, and does a no op for WMR
/// </summary>
[AddComponentMenu("Scripts/MRTK/SDK/StabilizationPlaneModifier")]
public class StabilizationPlaneModifier : MonoBehaviour
{
[Serializable]
private enum StabilizationPlaneMode
{
/// <summary>
/// Does not call SetFocusPoint
/// </summary>
Off,
/// <summary>
/// Submits plane at a fixed distance based on DefaultPlaneDistance field along the users gaze.
/// </summary>
Fixed,
/// <summary>
/// Submits the plane at a fixed position along the users gaze based on the TargetOverride property.
/// </summary>
TargetOverride,
/// <summary>
/// Submits the plane based on the OverridePlane property.
/// </summary>
PlaneOverride,
/// <summary>
/// Submits plane along the users gaze at the position of gaze hit.
/// </summary>
GazeHit
}
[SerializeField, Tooltip("Choose mode for stabilization plane.\n1) Fixed- Submits plane at a fixed distance based on DefaultPlaneDistance field along the users gaze.\n2) Gaze Hit- Submits plane along the users gaze at the position of gaze hit.\n3) Target Override- Submits the plane at a fixed position along the users gaze based on the TargetOverride property.\n4) Plane Override- Submits the plane based on the OverridePlane property.\n5) Off- Does not call SetFocusPoint")]
private StabilizationPlaneMode mode = StabilizationPlaneMode.Off;
[SerializeField, Tooltip("When lerping, use unscaled time. This is useful for apps that have a pause mechanism or otherwise adjust the game timescale.")]
private bool useUnscaledTime = true;
[SerializeField, Tooltip("Lerp speed when moving focus point closer.")]
private float lerpPowerCloser = 4.0f;
[SerializeField, Tooltip("Lerp speed when moving focus point farther away.")]
private float lerpPowerFarther = 7.0f;
[SerializeField, Tooltip("Used to temporarily override the location of the stabilization plane.")]
private Transform targetOverride;
public Transform TargetOverride
{
get
{
return targetOverride;
}
set
{
if (targetOverride != value)
{
targetOverride = value;
if (targetOverride)
{
targetOverridePreviousPosition = targetOverride.position;
}
}
}
}
[SerializeField, Tooltip("Keeps track of position-based velocity for the target object.")]
private bool trackVelocity;
public bool TrackVelocity
{
get
{
return trackVelocity;
}
set
{
trackVelocity = value;
if (TargetOverride)
{
targetOverridePreviousPosition = TargetOverride.position;
}
}
}
[SerializeField, Tooltip("Default distance to set plane if plane is gaze-locked or if no object is hit.")]
private float defaultPlaneDistance = 2.0f;
[SerializeField, Tooltip("Visualize the plane at runtime.")]
#pragma warning disable 414 // Field is used only in UNITY_EDITOR contexts
private bool drawGizmos = false;
#pragma warning restore 414
[SerializeField, Tooltip("Override plane to use. Usually used to set plane to a slate like a menu.")]
private StabilizationPlaneOverride overridePlane;
/// <summary>
/// Override plane to use. Usually used to set plane to a slate like a menu.
/// </summary>
public StabilizationPlaneOverride OverridePlane
{
get => overridePlane;
set => overridePlane = value;
}
/// <summary>
/// Position of the plane in world space.
/// </summary>
private Vector3 planePosition;
/// <summary>
/// Current distance of the plane from the user's head. Only used when not using the target override
/// or the GazeManager to set the plane's position.
/// </summary>
private float currentPlaneDistance = 4.0f;
/// <summary>
/// Tracks the previous position of the target override object. Used if velocity is being tracked.
/// </summary>
private Vector3 targetOverridePreviousPosition;
#if UNITY_EDITOR
/// <summary>
/// Used for representing latest plane drawn as gizmo
/// </summary>
private StabilizationPlaneOverride debugPlane;
/// <summary>
/// Used for representing the debug mesh
/// </summary>
private GameObject debugMesh;
/// <summary>
/// Debug mesh filter
/// </summary>
private MeshFilter debugMeshFilter;
#endif
private void Awake()
{
#if UNITY_EDITOR
debugMesh = GameObject.CreatePrimitive(PrimitiveType.Quad);
debugMesh.hideFlags |= HideFlags.HideInHierarchy;
debugMesh.SetActive(false);
debugMeshFilter = debugMesh.GetComponent<MeshFilter>();
#endif
TrackVelocity = trackVelocity;
TargetOverride = targetOverride;
}
/// <summary>
/// Updates the focus point for every frame after all objects have finished moving.
/// </summary>
private void LateUpdate()
{
float deltaTime = useUnscaledTime
? Time.unscaledDeltaTime
: Time.deltaTime;
switch (mode)
{
case StabilizationPlaneMode.Fixed:
ConfigureFixedDistancePlane(deltaTime);
break;
case StabilizationPlaneMode.GazeHit:
ConfigureGazeManagerPlane(deltaTime);
break;
case StabilizationPlaneMode.PlaneOverride:
ConfigureOverridePlane(deltaTime);
break;
case StabilizationPlaneMode.TargetOverride:
ConfigureTransformOverridePlane(deltaTime);
break;
case StabilizationPlaneMode.Off:
default:
break;
}
}
/// <summary>
/// Gets the origin of the gaze for purposes of placing the stabilization plane
/// </summary>
private Vector3 GazeOrigin
{
get
{
var gazeProvider = CoreServices.InputSystem?.GazeProvider;
if (gazeProvider.IsNotNull() && gazeProvider.Enabled)
{
return gazeProvider.GazeOrigin;
}
return CameraCache.Main.transform.position;
}
}
/// <summary>
/// Gets the direction of the gaze for purposes of placing the stabilization plane
/// </summary>
private Vector3 GazeNormal
{
get
{
var gazeProvider = CoreServices.InputSystem?.GazeProvider;
if (gazeProvider.IsNotNull() && gazeProvider.Enabled)
{
return gazeProvider.GazeDirection;
}
return CameraCache.Main.transform.forward;
}
}
/// <summary>
/// Gets the position hit on the object the user is gazing at, if gaze tracking is supported.
/// </summary>
/// <param name="hitPosition">The position at which gaze ray intersects with an object.</param>
/// <returns>True if gaze is supported and an object was hit by gaze, otherwise false.</returns>
private bool TryGetGazeHitPosition(out Vector3 hitPosition)
{
var gazeProvider = CoreServices.InputSystem?.GazeProvider;
if (gazeProvider.IsNotNull() && gazeProvider.Enabled &&
gazeProvider.HitInfo.raycastValid)
{
hitPosition = gazeProvider.HitPosition;
return true;
}
hitPosition = Vector3.zero;
return false;
}
/// <summary>
/// Configures the stabilization plane to update its position based on an object in the scene.
/// </summary>
private void ConfigureTransformOverridePlane(float deltaTime)
{
Vector3 gazeDirection = ConfigureOverridePlaneHelper(TargetOverride.position, deltaTime);
#if UNITY_EDITOR
debugPlane.Center = planePosition;
debugPlane.Normal = -gazeDirection;
#endif
}
private void ConfigureOverridePlane(float deltaTime)
{
ConfigureOverridePlaneHelper(OverridePlane.Center, deltaTime);
#if UNITY_EDITOR
debugPlane.Center = planePosition;
debugPlane.Normal = -OverridePlane.Normal;
#endif
}
private Vector3 ConfigureOverridePlaneHelper(Vector3 position, float deltaTime)
{
planePosition = position;
Vector3 velocity = Vector3.zero;
if (TrackVelocity)
{
velocity = UpdateVelocity(deltaTime);
}
Vector3 gazeOrigin = GazeOrigin;
Vector3 gazeToPlane = planePosition - gazeOrigin;
float focusPointDistance = gazeToPlane.magnitude;
float lerpPower = focusPointDistance > currentPlaneDistance ? lerpPowerFarther
: lerpPowerCloser;
// Smoothly move the focus point from previous hit position to new position.
currentPlaneDistance = Mathf.Lerp(currentPlaneDistance, focusPointDistance, lerpPower * deltaTime);
gazeToPlane.Normalize();
planePosition = gazeOrigin + (gazeToPlane * currentPlaneDistance);
#if UNITY_2019_3_OR_NEWER
XRSubsystemHelpers.DisplaySubsystem?.SetFocusPlane(planePosition, OverridePlane.Normal, velocity);
#endif // UNITY_2019_3_OR_NEWER
#if UNITY_WSA && !UNITY_2020_1_OR_NEWER
// Ensure compatibility with the pre-2019.3 XR architecture for customers / platforms
// with legacy requirements.
if (XRSubsystemHelpers.DisplaySubsystem == null)
{
#pragma warning disable 0618
// Place the plane at the desired depth in front of the user and billboard it to the gaze origin.
HolographicSettings.SetFocusPointForFrame(planePosition, OverridePlane.Normal, velocity);
#pragma warning restore 0618
}
#endif // UNITY_WSA && !UNITY_2020_1_OR_NEWER
return gazeToPlane;
}
/// <summary>
/// Configures the stabilization plane to update its position based on what your gaze intersects in the scene.
/// </summary>
private void ConfigureGazeManagerPlane(float deltaTime)
{
Vector3 gazeOrigin = GazeOrigin;
Vector3 gazeDirection = GazeNormal;
// Calculate the delta between gaze origin's position and current hit position. If no object is hit, use default distance.
float focusPointDistance;
Vector3 gazeHitPosition;
if (TryGetGazeHitPosition(out gazeHitPosition))
{
focusPointDistance = (gazeOrigin - gazeHitPosition).magnitude;
}
else
{
focusPointDistance = defaultPlaneDistance;
}
float lerpPower = focusPointDistance > currentPlaneDistance ? lerpPowerFarther
: lerpPowerCloser;
// Smoothly move the focus point from previous hit position to new position.
currentPlaneDistance = Mathf.Lerp(currentPlaneDistance, focusPointDistance, lerpPower * deltaTime);
planePosition = gazeOrigin + (gazeDirection * currentPlaneDistance);
#if UNITY_EDITOR
debugPlane.Center = planePosition;
debugPlane.Normal = -gazeDirection;
#else
#if UNITY_2019_3_OR_NEWER
XRSubsystemHelpers.DisplaySubsystem?.SetFocusPlane(planePosition, -gazeDirection, Vector3.zero);
#endif // UNITY_2019_3_OR_NEWER
#if UNITY_WSA && !UNITY_2020_1_OR_NEWER
// Ensure compatibility with the pre-2019.3 XR architecture for customers / platforms
// with legacy requirements.
if (XRSubsystemHelpers.DisplaySubsystem == null)
{
#pragma warning disable 0618
HolographicSettings.SetFocusPointForFrame(planePosition, -gazeDirection, Vector3.zero);
#pragma warning restore 0618
}
#endif // UNITY_WSA && !UNITY_2020_1_OR_NEWER
#endif
}
/// <summary>
/// Configures the stabilization plane to update based on a fixed distance away from you.
/// </summary>
private void ConfigureFixedDistancePlane(float deltaTime)
{
Vector3 gazeOrigin = GazeOrigin;
Vector3 gazeNormal = GazeNormal;
float lerpPower = defaultPlaneDistance > currentPlaneDistance ? lerpPowerFarther
: lerpPowerCloser;
// Smoothly move the focus point from previous hit position to new position.
currentPlaneDistance = Mathf.Lerp(currentPlaneDistance, defaultPlaneDistance, lerpPower * deltaTime);
planePosition = gazeOrigin + (gazeNormal * currentPlaneDistance);
#if UNITY_EDITOR
debugPlane.Center = planePosition;
debugPlane.Normal = -gazeNormal;
#else
#if UNITY_2019_3_OR_NEWER
XRSubsystemHelpers.DisplaySubsystem?.SetFocusPlane(planePosition, -gazeNormal, Vector3.zero);
#endif // UNITY_2019_3_OR_NEWER
#if UNITY_WSA && !UNITY_2020_1_OR_NEWER
// Ensure compatibility with the pre-2019.3 XR architecture for customers / platforms
// with legacy requirements.
if (XRSubsystemHelpers.DisplaySubsystem == null)
{
#pragma warning disable 0618
HolographicSettings.SetFocusPointForFrame(planePosition, -gazeNormal, Vector3.zero);
#pragma warning restore 0618
}
#endif // UNITY_WSA && !UNITY_2020_1_OR_NEWER
#endif
}
/// <summary>
/// Tracks the velocity of the target object to be used as a hint for the plane stabilization.
/// </summary>
private Vector3 UpdateVelocity(float deltaTime)
{
// Roughly calculate the velocity based on previous position, current position, and frame time.
Vector3 velocity = (TargetOverride.position - targetOverridePreviousPosition) / deltaTime;
targetOverridePreviousPosition = TargetOverride.position;
return velocity;
}
#if UNITY_EDITOR
/// <summary>
/// When in editor, draws a magenta quad that visually represents the stabilization plane.
/// </summary>
private void OnDrawGizmos()
{
if (Application.isPlaying && drawGizmos && mode != StabilizationPlaneMode.Off)
{
Gizmos.color = Color.green;
Gizmos.DrawWireMesh(debugMeshFilter.sharedMesh, debugPlane.Center, Quaternion.LookRotation(debugPlane.Normal));
Gizmos.DrawRay(debugPlane.Center, debugPlane.Normal);
}
}
#endif
}
}