// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using UnityEngine; namespace Microsoft.MixedReality.Toolkit.Utilities { /// /// Base class for Mixed Reality Line Renderers. /// [ExecuteAlways] public abstract class BaseMixedRealityLineRenderer : MonoBehaviour { [SerializeField] [Tooltip("The line data this component will render")] protected BaseMixedRealityLineDataProvider lineDataSource; /// /// The data provider component that provides the positioning source information for the LineRenderer. /// public BaseMixedRealityLineDataProvider LineDataSource { get { if (lineDataSource == null) { lineDataSource = GetComponent(); if (lineDataSource != null) { var lineDataType = lineDataSource.GetType(); if (lineDataType == typeof(RectangleLineDataProvider)) { StepMode = StepMode.FromSource; } } } if (lineDataSource == null) { Debug.LogError($"Missing a Line Data Provider on {gameObject.name}"); enabled = false; } return lineDataSource; } set { lineDataSource = value; enabled = lineDataSource != null; } } [Header("Visual Settings")] [SerializeField] [Tooltip("Color gradient applied to line's normalized length")] private Gradient lineColor = new Gradient(); /// /// Color gradient applied to line's normalized length /// public Gradient LineColor { get => lineColor; set => lineColor = value; } [SerializeField] private AnimationCurve lineWidth = AnimationCurve.Linear(0f, 1f, 1f, 1f); public AnimationCurve LineWidth { get => lineWidth; set => lineWidth = value; } [Range(0.01f, 10f)] [SerializeField] private float widthMultiplier = 0.01f; public float WidthMultiplier { get => widthMultiplier; set => widthMultiplier = Mathf.Clamp(value, 0f, 10f); } [Header("Offsets")] [Range(0f, 10f)] [SerializeField] [Tooltip("Normalized offset for color gradient")] private float colorOffset = 0f; /// /// Normalized offset for color gradient /// public float ColorOffset { get => colorOffset; set => colorOffset = Mathf.Clamp(value, 0f, 10f); } [Range(0f, 10f)] [SerializeField] [Tooltip("Normalized offset for width curve")] private float widthOffset = 0f; /// /// Normalized offset for width curve /// public float WidthOffset { get => widthOffset; set => widthOffset = Mathf.Clamp(value, 0f, 10f); } [Header("Point Placement")] [SerializeField] [Tooltip("Method for gathering points along line. Interpolated uses normalized length. FromSource uses line's base points. (FromSource may not look right for all LineDataProvider types.)")] private StepMode stepMode = StepMode.Interpolated; /// /// Method for gathering points along line. Interpolated uses normalized length. FromSource uses line's base points. (FromSource may not look right for all LineDataProvider types.) /// public StepMode StepMode { get => stepMode; set => stepMode = value; } [Range(2, 2048)] [SerializeField] [Tooltip("Number of steps to interpolate along line in Interpolated step mode")] private int lineStepCount = 16; [SerializeField] [Tooltip("Method for distributing rendered points along line. Auto lets the implementation decide. None means normalized distribution. DistanceSingleValue ensures uniform distribution. DistanceCurveValue enables custom distribution.")] private PointDistributionMode pointDistributionMode = PointDistributionMode.Auto; /// /// Method for distributing rendered points along line. /// public PointDistributionMode PointDistributionMode { get => pointDistributionMode; set => pointDistributionMode = value; } [SerializeField] [Tooltip("Minimum distance between points distributed along curve. Used when PointDistributionMode is set to DistanceSingleValue. Total points capped by LineStepCount.")] private float customPointDistributionLength = 0.1f; [SerializeField] [Tooltip("Custom function for distributing points along curve.Used when DistanceCurveValue is set to Distance. Total points set by LineStepCount.")] private AnimationCurve customPointDistributionCurve = AnimationCurve.Linear(0, 0, 1, 1); /// /// Number of steps to interpolate along line in Interpolated step mode /// public int LineStepCount { get => lineStepCount; set => lineStepCount = Mathf.Clamp(value, 2, 2048); } /// /// Get the Color along the normalized length of the line. /// protected virtual Color GetColor(float normalizedLength) { if (LineColor == null) { LineColor = new Gradient(); } return LineColor.Evaluate(Mathf.Repeat(normalizedLength + colorOffset, 1f)); } /// /// Get the width of the line along the normalized length of the line. /// protected virtual float GetWidth(float normalizedLength) { if (LineWidth == null) { LineWidth = AnimationCurve.Linear(0f, 1f, 1f, 1f); } return LineWidth.Evaluate(Mathf.Repeat(normalizedLength + widthOffset, 1f)) * widthMultiplier; } /// /// Gets the normalized distance along the line path (range 0 to 1) going the given number of steps provided /// /// Number of steps to take "walking" along the curve protected virtual float GetNormalizedPointAlongLine(int stepNum) { float normalizedDistance = 0; switch (pointDistributionMode) { case PointDistributionMode.None: case PointDistributionMode.Auto: // Normalized length along line normalizedDistance = (1f / (LineStepCount - 1)) * stepNum; break; case PointDistributionMode.DistanceCurveValue: // Use curve to interpret value normalizedDistance = (1f / (LineStepCount - 1)) * stepNum; normalizedDistance = customPointDistributionCurve.Evaluate(normalizedDistance); break; case PointDistributionMode.DistanceSingleValue: // Get the normalized distance along curve float totalWorldLength = customPointDistributionLength * stepNum; normalizedDistance = lineDataSource.GetNormalizedLengthFromWorldLength(totalWorldLength); break; } return normalizedDistance; } private void LateUpdate() { UpdateLine(); } /// /// Executes every Unity LateUpdate(). Any property updates or frame updates should occur here to update the line data source. /// protected abstract void UpdateLine(); #region Gizmos #if UNITY_EDITOR protected virtual void OnDrawGizmos() { if (UnityEditor.Selection.activeGameObject == gameObject || Application.isPlaying) { return; } if (lineDataSource == null) { lineDataSource = gameObject.GetComponent(); } if (lineDataSource == null || !lineDataSource.enabled) { return; } GizmosDrawLineRenderer(); } private void GizmosDrawLineRenderer() { if (stepMode == StepMode.FromSource) { GizmosDrawLineFromSource(); } else { GizmosDrawLineInterpolated(); } } private void GizmosDrawLineFromSource() { Vector3 firstPos = lineDataSource.GetPoint(0); Vector3 lastPos = firstPos; Color gColor = GetColor(0); gColor.a = 0.15f; Gizmos.color = gColor; Gizmos.DrawSphere(firstPos, GetWidth(0) * 0.5f); for (int i = 1; i < lineDataSource.PointCount; i++) { float normalizedLength = (1f / lineDataSource.PointCount) * i; Vector3 currentPos = lineDataSource.GetPoint(i); gColor = GetColor(normalizedLength); Gizmos.color = gColor.Invert(); Gizmos.DrawLine(lastPos, currentPos); gColor.a = 0.15f; Gizmos.color = gColor; Gizmos.DrawSphere(currentPos, GetWidth(normalizedLength) * 0.5f); lastPos = currentPos; } if (lineDataSource.Loops) { Gizmos.color = gColor.Invert(); Gizmos.DrawLine(lastPos, firstPos); } } private void GizmosDrawLineInterpolated() { Vector3 firstPos = lineDataSource.GetPoint(0f); Vector3 lastPos = firstPos; Color gColor = GetColor(0f); gColor.a = 0.15f; Gizmos.color = gColor; Gizmos.DrawSphere(firstPos, GetWidth(0f) * 0.5f); for (int i = 1; i <= lineStepCount; i++) { float normalizedLength = (1f / lineStepCount) * i; Vector3 currentPos = lineDataSource.GetPoint(normalizedLength); gColor = GetColor(normalizedLength); Gizmos.color = gColor.Invert(); Gizmos.DrawLine(lastPos, currentPos); gColor.a = 0.15f; Gizmos.color = gColor; Gizmos.DrawSphere(currentPos, GetWidth(normalizedLength) * 0.5f); lastPos = currentPos; } if (lineDataSource.Loops) { Gizmos.color = gColor.Invert(); Gizmos.DrawLine(lastPos, firstPos); } } #endif #endregion } }