// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Physics;
using Microsoft.MixedReality.Toolkit.Utilities;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Serialization;
namespace Microsoft.MixedReality.Toolkit.Input
{
///
/// A simple line pointer for drawing lines from the input source origin to the current pointer position.
///
[AddComponentMenu("Scripts/MRTK/SDK/LinePointer")]
public class LinePointer : BaseControllerPointer
{
[SerializeField]
protected Gradient LineColorSelected = new Gradient();
[SerializeField]
protected Gradient LineColorValid = new Gradient();
[SerializeField]
protected Gradient LineColorInvalid = new Gradient();
[SerializeField]
protected Gradient LineColorNoTarget = new Gradient();
[SerializeField]
protected Gradient LineColorLockFocus = new Gradient();
[SerializeField, FormerlySerializedAs("lineBase")]
private BaseMixedRealityLineDataProvider lineDataProvider;
///
/// The Line Data Provider driving this pointer.
///
public BaseMixedRealityLineDataProvider LineBase => lineDataProvider;
[SerializeField]
[Tooltip("If no line renderers are specified, this array will be auto-populated on startup.")]
private BaseMixedRealityLineRenderer[] lineRenderers;
///
/// The current line renderers that this pointer is utilizing.
///
///
/// If no line renderers are specified, this array will be auto-populated on startup.
///
public BaseMixedRealityLineRenderer[] LineRenderers => lineRenderers;
///
public override bool IsInteractionEnabled =>
// If IsTracked is not true, then we don't have position data yet (or have stale data),
// so remain disabled until we know where to appear (not just at the origin).
IsFocusLocked || (IsTracked && Controller.IsNotNull() && Controller.IsInPointingPose && base.IsInteractionEnabled);
private Vector3 lineStartPoint;
private Vector3 lineEndPoint;
private void CheckInitialization()
{
if (lineDataProvider == null)
{
lineDataProvider = GetComponent();
}
if (lineDataProvider == null)
{
Debug.LogError($"No Mixed Reality Line Data Provider found on {gameObject.name}. Did you forget to add a Line Data provider?");
}
if (lineDataProvider != null && (lineRenderers == null || lineRenderers.Length == 0))
{
lineRenderers = lineDataProvider.GetComponentsInChildren();
}
if (lineRenderers == null || lineRenderers.Length == 0)
{
Debug.LogError($"No Mixed Reality Line Renderers found on {gameObject.name}. Did you forget to add a Mixed Reality Line Renderer?");
}
for (int i = 0; i < lineRenderers.Length; i++)
{
lineRenderers[i].enabled = true;
}
}
#region MonoBehaviour Implementation
protected override void OnEnable()
{
base.OnEnable();
CheckInitialization();
}
protected override void OnDisable()
{
base.OnDisable();
for (int i = 0; i < lineRenderers.Length; i++)
{
lineRenderers[i].enabled = false;
}
}
#endregion MonoBehaviour Implementation
#region IMixedRealityPointer Implementation
private static readonly ProfilerMarker OnPreSceneQueryPerfMarker = new ProfilerMarker("[MRTK] LinePointer.OnPreSceneQuery");
///
public override void OnPreSceneQuery()
{
using (OnPreSceneQueryPerfMarker.Auto())
{
if (!IsInteractionEnabled)
{
return;
}
PreUpdateLineRenderers();
UpdateRays();
}
}
private static readonly ProfilerMarker OnPostSceneQueryPerfMarker = new ProfilerMarker("[MRTK] LinePointer.OnPostSceneQuery");
///
public override void OnPostSceneQuery()
{
using (OnPostSceneQueryPerfMarker.Auto())
{
base.OnPostSceneQuery();
bool isEnabled = IsInteractionEnabled;
lineDataProvider.enabled = isEnabled;
if (BaseCursor != null)
{
BaseCursor.SetVisibility(isEnabled);
}
PostUpdateLineRenderers();
}
}
private static readonly ProfilerMarker PreUpdateLineRenderersPerfMarker = new ProfilerMarker("[MRTK] LinePointer.PreUpdateLineRenderers");
protected virtual void PreUpdateLineRenderers()
{
using (PreUpdateLineRenderersPerfMarker.Auto())
{
Debug.Assert(lineDataProvider != null);
lineDataProvider.UpdateMatrix();
// Set our first and last points on the Line Renderer
if (IsFocusLocked && IsTargetPositionLockedOnFocusLock && Result != null)
{
// Make the final point 'stick' to the target at the distance of the target
SetLinePoints(Position, Result.Details.Point);
}
else
{
SetLinePoints(Position, Position + Rotation * Vector3.forward * DefaultPointerExtent);
}
}
}
private static readonly ProfilerMarker PostUpdateLineRenderersPerfMarker = new ProfilerMarker("[MRTK] LinePointer.PostUpdateLineRenderers");
protected virtual void PostUpdateLineRenderers()
{
using (PostUpdateLineRenderersPerfMarker.Auto())
{
if (!IsInteractionEnabled)
{
return;
}
// The distance the ray travels through the world before it hits something. Measured in world-units (as opposed to normalized distance).
float clearWorldLength;
Gradient lineColor = LineColorNoTarget;
if (Result?.CurrentPointerTarget != null)
{
// We hit something
clearWorldLength = Result.Details.RayDistance;
lineColor = IsSelectPressed || IsGrabPressed ? LineColorSelected : LineColorValid;
}
else
{
clearWorldLength = DefaultPointerExtent;
lineColor = IsSelectPressed || IsGrabPressed ? LineColorSelected : LineColorNoTarget;
}
if (IsFocusLocked)
{
lineColor = LineColorLockFocus;
}
int maxClampLineSteps = 2;
for (int i = 0; i < LineRenderers.Length; i++)
{
// Renderers are enabled by default if line is enabled
maxClampLineSteps = Mathf.Max(maxClampLineSteps, LineRenderers[i].LineStepCount);
LineRenderers[i].LineColor = lineColor;
}
// Used to ensure the line doesn't extend beyond the cursor
float cursorOffsetWorldLength = (BaseCursor != null) ? BaseCursor.SurfaceCursorDistance : 0;
// Readjust the Line renderer's endpoint to match the cursor's position if it is focus locked to a target
if (IsFocusLocked && IsTargetPositionLockedOnFocusLock && Result != null)
{
SetLinePoints(Position, Result.Details.Point + Rotation * Vector3.back * cursorOffsetWorldLength);
}
// If focus is locked, we're sticking to the target
// So don't clamp the world length, the line data's end point is already set to the world cursor
if (IsFocusLocked && IsTargetPositionLockedOnFocusLock)
{
lineDataProvider.LineEndClamp = 1;
}
else
{
// Otherwise clamp the line end by the clear distance
float clearLocalLength = lineDataProvider.GetNormalizedLengthFromWorldLength(clearWorldLength - cursorOffsetWorldLength, maxClampLineSteps);
lineDataProvider.LineEndClamp = clearLocalLength;
}
}
}
private static readonly ProfilerMarker UpdateRaysPerfMarker = new ProfilerMarker("[MRTK] LinePointer.UpdateRays");
protected virtual void UpdateRays()
{
using (UpdateRaysPerfMarker.Auto())
{
const int LineLength = 1;
// Make sure our array will hold
if (Rays == null || Rays.Length != LineLength)
{
Rays = new RayStep[LineLength];
}
Rays[0].UpdateRayStep(ref lineStartPoint, ref lineEndPoint);
}
}
protected void SetLinePoints(Vector3 startPoint, Vector3 endPoint)
{
lineStartPoint = startPoint;
lineEndPoint = endPoint;
lineDataProvider.FirstPoint = startPoint;
lineDataProvider.LastPoint = endPoint;
}
#endregion IMixedRealityPointer Implementation
}
}