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