namespace Zinnia.Visual { using System; using System.Collections.Generic; using UnityEngine; using Zinnia.Data.Type; using Zinnia.Extension; /// /// Renders points using s. /// public class PointsRenderer : MonoBehaviour { /// /// Contains all the data needed for a to render. /// [Serializable] public class PointsData { [Tooltip("Represents the start, i.e. the first rendered point.")] [SerializeField] private GameObject startPoint; /// /// Represents the start, i.e. the first rendered point. /// public GameObject StartPoint { get { return startPoint; } set { startPoint = value; } } [Tooltip("Whether the first point should be visible.")] [SerializeField] private bool isStartPointVisible; /// /// Whether the first point should be visible. /// public bool IsStartPointVisible { get { return isStartPointVisible; } set { isStartPointVisible = value; } } [Tooltip("Represents the segments between Start and End. This will get cloned to create all the segments.")] [SerializeField] private GameObject repeatedSegmentPoint; /// /// Represents the segments between and . This will get cloned to create all the segments. /// public GameObject RepeatedSegmentPoint { get { return repeatedSegmentPoint; } set { repeatedSegmentPoint = value; } } [Tooltip("Whether the repeated segment point(s) should be visible.")] [SerializeField] private bool isRepeatedSegmentPointVisible; /// /// Whether the repeated segment point(s) should be visible. /// public bool IsRepeatedSegmentPointVisible { get { return isRepeatedSegmentPointVisible; } set { isRepeatedSegmentPointVisible = value; } } [Tooltip("Represents the end, i.e. the last rendered point.")] [SerializeField] private GameObject endPoint; /// /// Represents the end, i.e. the last rendered point. /// public GameObject EndPoint { get { return endPoint; } set { endPoint = value; } } [Tooltip("Whether the end point should be visible.")] [SerializeField] private bool isEndPointVisible; /// /// Whether the end point should be visible. /// public bool IsEndPointVisible { get { return isEndPointVisible; } set { isEndPointVisible = value; } } [Tooltip("The points along the most recent cast.")] [SerializeField] private HeapAllocationFreeReadOnlyList points; /// /// The points along the most recent cast. /// public HeapAllocationFreeReadOnlyList Points { get { return points; } set { points = value; } } /// public override string ToString() { string[] titles = new string[] { "StartPoint", "IsStartPointVisible", "RepeatedSegmentPoint", "IsRepeatedSegmentPointVisible", "EndPoint", "IsEndPointVisible" }; object[] values = new object[] { StartPoint, IsStartPointVisible, RepeatedSegmentPoint, IsRepeatedSegmentPointVisible, EndPoint, IsEndPointVisible }; return StringExtensions.FormatForToString(titles, values); } } [Tooltip("The direction to scale the segment GameObjects in. Set axes to 0 to disable scaling on that axis.")] [SerializeField] private Vector3 segmentScaleDirection = Vector3.forward; /// /// The direction to scale the segment s in. Set axes to 0 to disable scaling on that axis. /// public Vector3 SegmentScaleDirection { get { return segmentScaleDirection; } set { segmentScaleDirection = value; } } [Tooltip("Represents the start, i.e. the first rendered point.")] [SerializeField] private GameObject start; /// /// Represents the start, i.e. the first rendered point. /// public GameObject Start { get { return start; } set { start = value; } } [Tooltip("Represents the segments between Start and End. This will get cloned to create all the segments.")] [SerializeField] private GameObject repeatedSegment; /// /// Represents the segments between and . This will get cloned to create all the segments. /// public GameObject RepeatedSegment { get { return repeatedSegment; } set { repeatedSegment = value; } } [Tooltip("Represents the end, i.e. the last rendered point.")] [SerializeField] private GameObject end; /// /// Represents the end, i.e. the last rendered point. /// public GameObject End { get { return end; } set { end = value; } } [Tooltip("The threshold the forward value can be within 0 to be able to be set.")] [SerializeField] private float forwardZeroThreshold = 0.000001f; /// /// The threshold the forward value can be within 0 to be able to be set. /// public float ForwardZeroThreshold { get { return forwardZeroThreshold; } set { forwardZeroThreshold = value; } } /// /// A collection of segment clones. /// protected readonly List segmentClones = new List(); /// /// Sets the x value. /// /// The value to set to. public virtual void SetSegmentScaleDirectionX(float value) { SegmentScaleDirection = new Vector3(value, SegmentScaleDirection.y, SegmentScaleDirection.z); } /// /// Sets the y value. /// /// The value to set to. public virtual void SetSegmentScaleDirectionY(float value) { SegmentScaleDirection = new Vector3(SegmentScaleDirection.x, value, SegmentScaleDirection.z); } /// /// Sets the z value. /// /// The value to set to. public virtual void SetSegmentScaleDirectionZ(float value) { SegmentScaleDirection = new Vector3(SegmentScaleDirection.x, SegmentScaleDirection.y, value); } /// /// Renders the given points. /// /// The data to render. public virtual void RenderData(PointsData data) { if (!this.IsValidState()) { return; } Start = data.StartPoint; End = data.EndPoint; if (RepeatedSegment != data.RepeatedSegmentPoint) { foreach (GameObject segmentClone in segmentClones) { Destroy(segmentClone); } segmentClones.Clear(); RepeatedSegment = data.RepeatedSegmentPoint; } UpdateNumberOfClones(data.Points, data.IsRepeatedSegmentPointVisible); UpdateElement(data.Points, 0, false, Start); UpdateElement(data.Points, data.Points.Count - 1, false, End); UpdateElement(data.Points, 0, true, RepeatedSegment); for (int index = 1; index <= segmentClones.Count; index++) { UpdateElement(data.Points, index, true, segmentClones[index - 1]); } } protected virtual void OnDisable() { foreach (GameObject segmentClone in segmentClones) { Destroy(segmentClone); } segmentClones.Clear(); if (Start != null) { Start.SetActive(false); } if (RepeatedSegment != null) { RepeatedSegment.SetActive(false); } if (End != null) { End.SetActive(false); } } /// /// Ensures the number of cloned elements matches the provided number of points. /// /// The points to create cloned elements for. /// Whether the points should be visible. protected virtual void UpdateNumberOfClones(IReadOnlyList points, bool isVisible) { if (RepeatedSegment == null) { return; } int targetCount = points.Count > 0 ? points.Count - 1 : 0; for (int index = segmentClones.Count - 1; index >= targetCount; index--) { Destroy(segmentClones[index]); segmentClones.RemoveAt(index); } if (!isVisible) { return; } for (int index = segmentClones.Count; index < targetCount - 1; index++) { segmentClones.Add(Instantiate(RepeatedSegment, RepeatedSegment.transform.parent)); } } /// /// Updates the element for a specific point. /// /// All points to render. /// The index of the point that is represented by the element. /// Whether the element is part of the line or alternatively it's representing a point. /// The to use for rendering. protected virtual void UpdateElement(IReadOnlyList points, int pointsIndex, bool isPartOfLine, GameObject renderElement) { if (renderElement == null || 0 > pointsIndex || pointsIndex >= points.Count) { return; } Vector3 targetPoint = points[pointsIndex]; Vector3 otherPoint = pointsIndex + 1 < points.Count ? points[pointsIndex + 1] : points[pointsIndex - 1]; Vector3 forward = otherPoint - targetPoint; Vector3 position = isPartOfLine ? targetPoint + 0.5f * forward : targetPoint; float scaleTarget = Mathf.Abs(Vector3.Distance(targetPoint, otherPoint)); renderElement.transform.position = position; if (!isPartOfLine) { return; } if (!forward.ApproxEquals(Vector3.zero, ForwardZeroThreshold)) { renderElement.transform.forward = forward; } Vector3 scale = renderElement.transform.lossyScale; for (int index = 0; index < 3; index++) { if (Math.Abs(SegmentScaleDirection[index]) >= float.Epsilon) { scale[index] = SegmentScaleDirection[index] * scaleTarget; } } renderElement.transform.SetGlobalScale(scale); } } }