293 lines
11 KiB
C#
293 lines
11 KiB
C#
/// Credit jack.sydorenko, firagon
|
|
/// Sourced from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/
|
|
/// Updated/Refactored from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/#post-2528050
|
|
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEngine.UI.Extensions
|
|
{
|
|
[AddComponentMenu("UI/Extensions/Primitives/UILineRenderer")]
|
|
public class UILineRenderer : UIPrimitiveBase
|
|
{
|
|
private enum SegmentType
|
|
{
|
|
Start,
|
|
Middle,
|
|
End,
|
|
}
|
|
|
|
public enum JoinType
|
|
{
|
|
Bevel,
|
|
Miter
|
|
}
|
|
public enum BezierType
|
|
{
|
|
None,
|
|
Quick,
|
|
Basic,
|
|
Improved,
|
|
}
|
|
|
|
private const float MIN_MITER_JOIN = 15 * Mathf.Deg2Rad;
|
|
|
|
// A bevel 'nice' join displaces the vertices of the line segment instead of simply rendering a
|
|
// quad to connect the endpoints. This improves the look of textured and transparent lines, since
|
|
// there is no overlapping.
|
|
private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf.Deg2Rad;
|
|
|
|
private static readonly Vector2 UV_TOP_LEFT = Vector2.zero;
|
|
private static readonly Vector2 UV_BOTTOM_LEFT = new Vector2(0, 1);
|
|
private static readonly Vector2 UV_TOP_CENTER = new Vector2(0.5f, 0);
|
|
private static readonly Vector2 UV_BOTTOM_CENTER = new Vector2(0.5f, 1);
|
|
private static readonly Vector2 UV_TOP_RIGHT = new Vector2(1, 0);
|
|
private static readonly Vector2 UV_BOTTOM_RIGHT = new Vector2(1, 1);
|
|
|
|
private static readonly Vector2[] startUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_CENTER, UV_TOP_CENTER };
|
|
private static readonly Vector2[] middleUvs = new[] { UV_TOP_CENTER, UV_BOTTOM_CENTER, UV_BOTTOM_CENTER, UV_TOP_CENTER };
|
|
private static readonly Vector2[] endUvs = new[] { UV_TOP_CENTER, UV_BOTTOM_CENTER, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
|
|
|
|
[SerializeField]
|
|
private Rect m_UVRect = new Rect(0f, 0f, 1f, 1f);
|
|
[SerializeField]
|
|
private Vector2[] m_points;
|
|
|
|
|
|
public float LineThickness = 2;
|
|
public bool UseMargins;
|
|
public Vector2 Margin;
|
|
public bool relativeSize;
|
|
|
|
public bool LineList = false;
|
|
public bool LineCaps = false;
|
|
public JoinType LineJoins = JoinType.Bevel;
|
|
|
|
public BezierType BezierMode = BezierType.None;
|
|
public int BezierSegmentsPerCurve = 10;
|
|
|
|
/// <summary>
|
|
/// UV rectangle used by the texture.
|
|
/// </summary>
|
|
public Rect uvRect
|
|
{
|
|
get
|
|
{
|
|
return m_UVRect;
|
|
}
|
|
set
|
|
{
|
|
if (m_UVRect == value)
|
|
return;
|
|
m_UVRect = value;
|
|
SetVerticesDirty();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Points to be drawn in the line.
|
|
/// </summary>
|
|
public Vector2[] Points
|
|
{
|
|
get
|
|
{
|
|
return m_points;
|
|
}
|
|
set
|
|
{
|
|
if (m_points == value)
|
|
return;
|
|
m_points = value;
|
|
SetAllDirty();
|
|
}
|
|
}
|
|
|
|
protected override void OnPopulateMesh(VertexHelper vh)
|
|
{
|
|
if (m_points == null)
|
|
return;
|
|
Vector2[] pointsToDraw = m_points;
|
|
//If Bezier is desired, pick the implementation
|
|
if (BezierMode != BezierType.None && m_points.Length > 3)
|
|
{
|
|
BezierPath bezierPath = new BezierPath();
|
|
|
|
bezierPath.SetControlPoints(pointsToDraw);
|
|
bezierPath.SegmentsPerCurve = BezierSegmentsPerCurve;
|
|
List<Vector2> drawingPoints;
|
|
switch (BezierMode)
|
|
{
|
|
case BezierType.Basic:
|
|
drawingPoints = bezierPath.GetDrawingPoints0();
|
|
break;
|
|
case BezierType.Improved:
|
|
drawingPoints = bezierPath.GetDrawingPoints1();
|
|
break;
|
|
default:
|
|
drawingPoints = bezierPath.GetDrawingPoints2();
|
|
break;
|
|
}
|
|
pointsToDraw = drawingPoints.ToArray();
|
|
}
|
|
|
|
var sizeX = rectTransform.rect.width;
|
|
var sizeY = rectTransform.rect.height;
|
|
var offsetX = -rectTransform.pivot.x * rectTransform.rect.width;
|
|
var offsetY = -rectTransform.pivot.y * rectTransform.rect.height;
|
|
|
|
// don't want to scale based on the size of the rect, so this is switchable now
|
|
if (!relativeSize)
|
|
{
|
|
sizeX = 1;
|
|
sizeY = 1;
|
|
}
|
|
|
|
if (UseMargins)
|
|
{
|
|
sizeX -= Margin.x;
|
|
sizeY -= Margin.y;
|
|
offsetX += Margin.x / 2f;
|
|
offsetY += Margin.y / 2f;
|
|
}
|
|
|
|
vh.Clear();
|
|
|
|
// Generate the quads that make up the wide line
|
|
var segments = new List<UIVertex[]>();
|
|
if (LineList)
|
|
{
|
|
for (var i = 1; i < pointsToDraw.Length; i += 2)
|
|
{
|
|
var start = pointsToDraw[i - 1];
|
|
var end = pointsToDraw[i];
|
|
start = new Vector2(start.x * sizeX + offsetX, start.y * sizeY + offsetY);
|
|
end = new Vector2(end.x * sizeX + offsetX, end.y * sizeY + offsetY);
|
|
|
|
if (LineCaps)
|
|
{
|
|
segments.Add(CreateLineCap(start, end, SegmentType.Start));
|
|
}
|
|
|
|
segments.Add(CreateLineSegment(start, end, SegmentType.Middle));
|
|
|
|
if (LineCaps)
|
|
{
|
|
segments.Add(CreateLineCap(start, end, SegmentType.End));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var i = 1; i < pointsToDraw.Length; i++)
|
|
{
|
|
var start = pointsToDraw[i - 1];
|
|
var end = pointsToDraw[i];
|
|
start = new Vector2(start.x * sizeX + offsetX, start.y * sizeY + offsetY);
|
|
end = new Vector2(end.x * sizeX + offsetX, end.y * sizeY + offsetY);
|
|
|
|
if (LineCaps && i == 1)
|
|
{
|
|
segments.Add(CreateLineCap(start, end, SegmentType.Start));
|
|
}
|
|
|
|
segments.Add(CreateLineSegment(start, end, SegmentType.Middle));
|
|
|
|
if (LineCaps && i == pointsToDraw.Length - 1)
|
|
{
|
|
segments.Add(CreateLineCap(start, end, SegmentType.End));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the line segments to the vertex helper, creating any joins as needed
|
|
for (var i = 0; i < segments.Count; i++)
|
|
{
|
|
if (!LineList && i < segments.Count - 1)
|
|
{
|
|
var vec1 = segments[i][1].position - segments[i][2].position;
|
|
var vec2 = segments[i + 1][2].position - segments[i + 1][1].position;
|
|
var angle = Vector2.Angle(vec1, vec2) * Mathf.Deg2Rad;
|
|
|
|
// Positive sign means the line is turning in a 'clockwise' direction
|
|
var sign = Mathf.Sign(Vector3.Cross(vec1.normalized, vec2.normalized).z);
|
|
|
|
// Calculate the miter point
|
|
var miterDistance = LineThickness / (2 * Mathf.Tan(angle / 2));
|
|
var miterPointA = segments[i][2].position - vec1.normalized * miterDistance * sign;
|
|
var miterPointB = segments[i][3].position + vec1.normalized * miterDistance * sign;
|
|
|
|
var joinType = LineJoins;
|
|
if (joinType == JoinType.Miter)
|
|
{
|
|
// Make sure we can make a miter join without too many artifacts.
|
|
if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN)
|
|
{
|
|
segments[i][2].position = miterPointA;
|
|
segments[i][3].position = miterPointB;
|
|
segments[i + 1][0].position = miterPointB;
|
|
segments[i + 1][1].position = miterPointA;
|
|
}
|
|
else
|
|
{
|
|
joinType = JoinType.Bevel;
|
|
}
|
|
}
|
|
|
|
if (joinType == JoinType.Bevel)
|
|
{
|
|
if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN)
|
|
{
|
|
if (sign < 0)
|
|
{
|
|
segments[i][2].position = miterPointA;
|
|
segments[i + 1][1].position = miterPointA;
|
|
}
|
|
else
|
|
{
|
|
segments[i][3].position = miterPointB;
|
|
segments[i + 1][0].position = miterPointB;
|
|
}
|
|
}
|
|
|
|
var join = new UIVertex[] { segments[i][2], segments[i][3], segments[i + 1][0], segments[i + 1][1] };
|
|
vh.AddUIVertexQuad(join);
|
|
}
|
|
}
|
|
vh.AddUIVertexQuad(segments[i]);
|
|
}
|
|
}
|
|
|
|
private UIVertex[] CreateLineCap(Vector2 start, Vector2 end, SegmentType type)
|
|
{
|
|
if (type == SegmentType.Start)
|
|
{
|
|
var capStart = start - ((end - start).normalized * LineThickness / 2);
|
|
return CreateLineSegment(capStart, start, SegmentType.Start);
|
|
}
|
|
else if (type == SegmentType.End)
|
|
{
|
|
var capEnd = end + ((end - start).normalized * LineThickness / 2);
|
|
return CreateLineSegment(end, capEnd, SegmentType.End);
|
|
}
|
|
|
|
Debug.LogError("Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End");
|
|
return null;
|
|
}
|
|
|
|
private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type)
|
|
{
|
|
var uvs = middleUvs;
|
|
if (type == SegmentType.Start)
|
|
uvs = startUvs;
|
|
else if (type == SegmentType.End)
|
|
uvs = endUvs;
|
|
|
|
Vector2 offset = new Vector2(start.y - end.y, end.x - start.x).normalized * LineThickness / 2;
|
|
var v1 = start - offset;
|
|
var v2 = start + offset;
|
|
var v3 = end + offset;
|
|
var v4 = end - offset;
|
|
return SetVbo(new[] { v1, v2, v3, v4 }, uvs);
|
|
}
|
|
|
|
}
|
|
} |