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);
}
}
}