2016-05-17 20:26:50 +08:00
/// Credit jack.sydorenko, firagon
2015-08-30 07:31:59 +08:00
/// Sourced from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/
2016-05-17 20:26:50 +08:00
/// Updated/Refactored from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/#post-2528050
2015-08-30 07:31:59 +08:00
using System.Collections.Generic ;
namespace UnityEngine.UI.Extensions
{
2017-05-05 21:25:48 +08:00
[AddComponentMenu("UI/Extensions/Primitives/UILineRenderer")]
2017-05-08 07:18:06 +08:00
[RequireComponent(typeof(RectTransform))]
public class UILineRenderer : UIPrimitiveBase
2017-02-17 20:54:40 +08:00
{
private enum SegmentType
{
Start ,
2016-05-17 20:26:50 +08:00
Middle ,
End ,
2017-07-25 04:36:23 +08:00
Full ,
2017-02-17 20:54:40 +08:00
}
2016-05-17 20:26:50 +08:00
2017-02-17 20:54:40 +08:00
public enum JoinType
{
Bevel ,
2016-05-17 20:26:50 +08:00
Miter
2017-02-17 20:54:40 +08:00
}
public enum BezierType
{
None ,
2016-05-24 03:57:56 +08:00
Quick ,
Basic ,
Improved ,
2017-08-05 03:01:02 +08:00
Catenary ,
}
2016-05-17 20:26:50 +08:00
2017-02-17 20:54:40 +08:00
private const float MIN_MITER_JOIN = 15 * Mathf . Deg2Rad ;
2016-05-17 20:26:50 +08:00
2017-02-17 20:54:40 +08:00
// 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.
2016-05-17 20:26:50 +08:00
private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf . Deg2Rad ;
2017-07-25 04:36:23 +08:00
private static Vector2 UV_TOP_LEFT , UV_BOTTOM_LEFT , UV_TOP_CENTER_LEFT , UV_TOP_CENTER_RIGHT , UV_BOTTOM_CENTER_LEFT , UV_BOTTOM_CENTER_RIGHT , UV_TOP_RIGHT , UV_BOTTOM_RIGHT ;
private static Vector2 [ ] startUvs , middleUvs , endUvs , fullUvs ;
2017-02-17 20:54:40 +08:00
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
2017-07-08 16:24:09 +08:00
internal Vector2 [ ] m_points ;
2017-08-18 04:38:31 +08:00
[SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
internal List < Vector2 [ ] > m_segments ;
2017-02-17 20:54:40 +08:00
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Thickness of the line")]
2017-07-08 16:24:09 +08:00
internal float lineThickness = 2 ;
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
2017-07-08 16:24:09 +08:00
internal bool relativeSize ;
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
2017-07-08 16:24:09 +08:00
internal bool lineList ;
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Add end caps to each line\nMultiple caps when used with Line List")]
2017-07-08 16:24:09 +08:00
internal bool lineCaps ;
2017-08-05 03:01:02 +08:00
[SerializeField, Tooltip("Resolution of the Bezier curve, different to line Resolution")]
2017-07-08 16:24:09 +08:00
internal int bezierSegmentsPerCurve = 10 ;
2017-06-25 18:56:46 +08:00
public float LineThickness
{
get { return lineThickness ; }
set { lineThickness = value ; SetAllDirty ( ) ; }
}
public bool RelativeSize
{
get { return relativeSize ; }
set { relativeSize = value ; SetAllDirty ( ) ; }
}
public bool LineList
{
get { return lineList ; }
set { lineList = value ; SetAllDirty ( ) ; }
}
public bool LineCaps
{
get { return lineCaps ; }
set { lineCaps = value ; SetAllDirty ( ) ; }
}
2017-02-17 20:54:40 +08:00
2017-08-05 03:01:02 +08:00
[Tooltip("The type of Join used between lines, Square/Mitre or Curved/Bevel")]
2017-02-17 20:54:40 +08:00
public JoinType LineJoins = JoinType . Bevel ;
2017-08-05 03:01:02 +08:00
[Tooltip("Bezier method to apply to line, see docs for options\nCan't be used in conjunction with Resolution as Bezier already changes the resolution")]
public BezierType BezierMode = BezierType . None ;
2017-06-25 18:56:46 +08:00
public int BezierSegmentsPerCurve
{
get { return bezierSegmentsPerCurve ; }
set { bezierSegmentsPerCurve = value ; }
}
2017-02-17 20:54:40 +08:00
2017-06-22 07:54:02 +08:00
[HideInInspector]
public bool drivenExternally = false ;
2017-02-17 20:54:40 +08:00
/// <summary>
/// Points to be drawn in the line.
/// </summary>
2016-05-17 20:26:50 +08:00
public Vector2 [ ] Points
2017-02-17 20:54:40 +08:00
{
get
{
return m_points ;
}
set
{
2023-02-07 22:35:43 +08:00
if ( m_points = = value ) return ;
if ( value = = null | | value . Length = = 0 )
{
m_points = new Vector2 [ 1 ] ;
}
else
{
m_points = value ;
}
SetAllDirty ( ) ;
2017-02-17 20:54:40 +08:00
}
}
2017-08-18 04:38:31 +08:00
/// <summary>
/// List of Segments to be drawn.
/// </summary>
public List < Vector2 [ ] > Segments
2017-02-17 20:54:40 +08:00
{
2017-08-18 04:38:31 +08:00
get
{
return m_segments ;
}
set
2017-02-17 20:54:40 +08:00
{
2017-08-18 04:38:31 +08:00
m_segments = value ;
SetAllDirty ( ) ;
}
}
private void PopulateMesh ( VertexHelper vh , Vector2 [ ] pointsToDraw )
{
//If Bezier is desired, pick the implementation
if ( BezierMode ! = BezierType . None & & BezierMode ! = BezierType . Catenary & & pointsToDraw . Length > 3 ) {
BezierPath bezierPath = new BezierPath ( ) ;
2017-02-17 20:54:40 +08:00
2017-08-18 04:38:31 +08:00
bezierPath . SetControlPoints ( pointsToDraw ) ;
2017-06-25 18:56:46 +08:00
bezierPath . SegmentsPerCurve = bezierSegmentsPerCurve ;
2017-02-17 20:54:40 +08:00
List < Vector2 > drawingPoints ;
2017-08-18 04:38:31 +08:00
switch ( BezierMode ) {
case BezierType . Basic :
drawingPoints = bezierPath . GetDrawingPoints0 ( ) ;
2017-02-17 20:54:40 +08:00
break ;
2017-08-18 04:38:31 +08:00
case BezierType . Improved :
drawingPoints = bezierPath . GetDrawingPoints1 ( ) ;
2017-02-17 20:54:40 +08:00
break ;
2017-08-18 04:38:31 +08:00
default :
drawingPoints = bezierPath . GetDrawingPoints2 ( ) ;
2017-02-17 20:54:40 +08:00
break ;
}
2017-08-18 04:38:31 +08:00
pointsToDraw = drawingPoints . ToArray ( ) ;
}
if ( BezierMode = = BezierType . Catenary & & pointsToDraw . Length = = 2 ) {
CableCurve cable = new CableCurve ( pointsToDraw ) ;
2020-07-09 03:38:28 +08:00
cable . slack = Resolution ;
2017-08-18 04:38:31 +08:00
cable . steps = BezierSegmentsPerCurve ;
pointsToDraw = cable . Points ( ) ;
2017-02-17 20:54:40 +08:00
}
2017-08-18 04:38:31 +08:00
if ( ImproveResolution ! = ResolutionMode . None ) {
pointsToDraw = IncreaseResolution ( pointsToDraw ) ;
}
// scale based on the size of the rect or use absolute, this is switchable
var sizeX = ! relativeSize ? 1 : rectTransform . rect . width ;
var sizeY = ! relativeSize ? 1 : rectTransform . rect . height ;
var offsetX = - rectTransform . pivot . x * sizeX ;
var offsetY = - rectTransform . pivot . y * sizeY ;
2017-02-17 20:54:40 +08:00
// Generate the quads that make up the wide line
2017-08-18 04:38:31 +08:00
var segments = new List < UIVertex [ ] > ( ) ;
if ( lineList ) {
2021-05-11 01:38:31 +08:00
//Loop through list in line pairs, skipping drawing between lines
2017-08-18 04:38:31 +08:00
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 ) ) ;
2017-02-17 20:54:40 +08:00
}
2021-05-11 01:38:31 +08:00
// Originally, UV's had to be wrapped per segment to ensure textures rendered correctly, however when tested in 2019.4, this no longer seems to be an issue.
segments . Add ( CreateLineSegment ( start , end , SegmentType . Middle ) ) ;
2017-02-17 20:54:40 +08:00
2017-08-18 04:38:31 +08:00
if ( lineCaps ) {
segments . Add ( CreateLineCap ( start , end , SegmentType . End ) ) ;
2017-02-17 20:54:40 +08:00
}
}
2017-08-18 04:38:31 +08:00
} else {
2021-05-11 01:38:31 +08:00
//Draw full lines
2017-08-18 04:38:31 +08:00
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 ) ) ;
2017-02-17 20:54:40 +08:00
}
2017-08-18 04:38:31 +08:00
segments . Add ( CreateLineSegment ( start , end , SegmentType . Middle ) ) ;
2017-02-17 20:54:40 +08:00
2017-08-18 04:38:31 +08:00
if ( lineCaps & & i = = pointsToDraw . Length - 1 ) {
segments . Add ( CreateLineCap ( start , end , SegmentType . End ) ) ;
2017-02-17 20:54:40 +08:00
}
}
}
// Add the line segments to the vertex helper, creating any joins as needed
2017-08-18 04:38:31 +08:00
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 ;
2017-02-17 20:54:40 +08:00
// Positive sign means the line is turning in a 'clockwise' direction
2017-08-18 04:38:31 +08:00
var sign = Mathf . Sign ( Vector3 . Cross ( vec1 . normalized , vec2 . normalized ) . z ) ;
2015-08-30 07:31:59 +08:00
2017-02-17 20:54:40 +08:00
// Calculate the miter point
2017-08-18 04:38:31 +08:00
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 ;
2015-08-30 07:31:59 +08:00
2017-02-17 20:54:40 +08:00
var joinType = LineJoins ;
2017-08-18 04:38:31 +08:00
if ( joinType = = JoinType . Miter ) {
2017-02-17 20:54:40 +08:00
// Make sure we can make a miter join without too many artifacts.
2017-08-18 04:38:31 +08:00
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 {
2017-02-17 20:54:40 +08:00
joinType = JoinType . Bevel ;
}
}
2017-08-18 04:38:31 +08:00
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 ;
2017-02-17 20:54:40 +08:00
}
}
2017-08-18 04:38:31 +08:00
var join = new UIVertex [ ] { segments [ i ] [ 2 ] , segments [ i ] [ 3 ] , segments [ i + 1 ] [ 0 ] , segments [ i + 1 ] [ 1 ] } ;
vh . AddUIVertexQuad ( join ) ;
2017-02-17 20:54:40 +08:00
}
}
2017-08-18 04:38:31 +08:00
vh . AddUIVertexQuad ( segments [ i ] ) ;
}
if ( vh . currentVertCount > 64000 ) {
2020-07-09 03:38:28 +08:00
Debug . LogError ( "Max Verticies size is 64000, current mesh verticies count is [" + vh . currentVertCount + "] - Cannot Draw" ) ;
2017-08-18 04:38:31 +08:00
vh . Clear ( ) ;
return ;
}
}
protected override void OnPopulateMesh ( VertexHelper vh )
{
if ( m_points ! = null & & m_points . Length > 0 ) {
GeneratedUVs ( ) ;
vh . Clear ( ) ;
PopulateMesh ( vh , m_points ) ;
2017-02-17 20:54:40 +08:00
}
2023-06-29 05:00:16 +08:00
if ( m_segments ! = null & & m_segments . Count > 0 ) {
2017-08-18 04:38:31 +08:00
GeneratedUVs ( ) ;
vh . Clear ( ) ;
for ( int s = 0 ; s < m_segments . Count ; s + + ) {
Vector2 [ ] pointsToDraw = m_segments [ s ] ;
PopulateMesh ( vh , pointsToDraw ) ;
}
}
2017-06-12 16:02:46 +08:00
}
2017-02-17 20:54:40 +08:00
private UIVertex [ ] CreateLineCap ( Vector2 start , Vector2 end , SegmentType type )
{
if ( type = = SegmentType . Start )
{
2017-06-25 18:56:46 +08:00
var capStart = start - ( ( end - start ) . normalized * lineThickness / 2 ) ;
2017-02-17 20:54:40 +08:00
return CreateLineSegment ( capStart , start , SegmentType . Start ) ;
}
else if ( type = = SegmentType . End )
{
2017-06-25 18:56:46 +08:00
var capEnd = end + ( ( end - start ) . normalized * lineThickness / 2 ) ;
2017-02-17 20:54:40 +08:00
return CreateLineSegment ( end , capEnd , SegmentType . End ) ;
}
Debug . LogError ( "Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End" ) ;
return null ;
}
2019-09-01 21:31:59 +08:00
private UIVertex [ ] CreateLineSegment ( Vector2 start , Vector2 end , SegmentType type , UIVertex [ ] previousVert = null )
2017-02-17 20:54:40 +08:00
{
2017-06-25 18:56:46 +08:00
Vector2 offset = new Vector2 ( ( start . y - end . y ) , end . x - start . x ) . normalized * lineThickness / 2 ;
2017-07-25 04:36:23 +08:00
2019-09-01 21:31:59 +08:00
Vector2 v1 = Vector2 . zero ;
Vector2 v2 = Vector2 . zero ;
if ( previousVert ! = null ) {
v1 = new Vector2 ( previousVert [ 3 ] . position . x , previousVert [ 3 ] . position . y ) ;
v2 = new Vector2 ( previousVert [ 2 ] . position . x , previousVert [ 2 ] . position . y ) ;
} else {
v1 = start - offset ;
v2 = start + offset ;
}
2017-02-17 20:54:40 +08:00
var v3 = end + offset ;
var v4 = end - offset ;
2017-05-08 07:18:06 +08:00
//Return the VDO with the correct uvs
switch ( type )
{
case SegmentType . Start :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , startUvs ) ;
case SegmentType . End :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , endUvs ) ;
2017-07-25 04:36:23 +08:00
case SegmentType . Full :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , fullUvs ) ;
2017-05-08 07:18:06 +08:00
default :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , middleUvs ) ;
}
2017-02-17 20:54:40 +08:00
}
2017-07-25 04:36:23 +08:00
protected override void GeneratedUVs ( )
{
if ( activeSprite ! = null )
{
var outer = Sprites . DataUtility . GetOuterUV ( activeSprite ) ;
var inner = Sprites . DataUtility . GetInnerUV ( activeSprite ) ;
UV_TOP_LEFT = new Vector2 ( outer . x , outer . y ) ;
UV_BOTTOM_LEFT = new Vector2 ( outer . x , outer . w ) ;
UV_TOP_CENTER_LEFT = new Vector2 ( inner . x , inner . y ) ;
UV_TOP_CENTER_RIGHT = new Vector2 ( inner . z , inner . y ) ;
UV_BOTTOM_CENTER_LEFT = new Vector2 ( inner . x , inner . w ) ;
UV_BOTTOM_CENTER_RIGHT = new Vector2 ( inner . z , inner . w ) ;
UV_TOP_RIGHT = new Vector2 ( outer . z , outer . y ) ;
UV_BOTTOM_RIGHT = new Vector2 ( outer . z , outer . w ) ;
}
else
{
UV_TOP_LEFT = Vector2 . zero ;
UV_BOTTOM_LEFT = new Vector2 ( 0 , 1 ) ;
UV_TOP_CENTER_LEFT = new Vector2 ( 0.5f , 0 ) ;
UV_TOP_CENTER_RIGHT = new Vector2 ( 0.5f , 0 ) ;
UV_BOTTOM_CENTER_LEFT = new Vector2 ( 0.5f , 1 ) ;
UV_BOTTOM_CENTER_RIGHT = new Vector2 ( 0.5f , 1 ) ;
UV_TOP_RIGHT = new Vector2 ( 1 , 0 ) ;
UV_BOTTOM_RIGHT = Vector2 . one ;
}
startUvs = new [ ] { UV_TOP_LEFT , UV_BOTTOM_LEFT , UV_BOTTOM_CENTER_LEFT , UV_TOP_CENTER_LEFT } ;
middleUvs = new [ ] { UV_TOP_CENTER_LEFT , UV_BOTTOM_CENTER_LEFT , UV_BOTTOM_CENTER_RIGHT , UV_TOP_CENTER_RIGHT } ;
endUvs = new [ ] { UV_TOP_CENTER_RIGHT , UV_BOTTOM_CENTER_RIGHT , UV_BOTTOM_RIGHT , UV_TOP_RIGHT } ;
fullUvs = new [ ] { UV_TOP_LEFT , UV_BOTTOM_LEFT , UV_BOTTOM_RIGHT , UV_TOP_RIGHT } ;
}
protected override void ResolutionToNativeSize ( float distance )
{
if ( UseNativeSize )
{
m_Resolution = distance / ( activeSprite . rect . width / pixelsPerUnit ) ;
lineThickness = activeSprite . rect . height / pixelsPerUnit ;
}
}
2019-11-26 02:49:01 +08:00
private int GetSegmentPointCount ( )
{
if ( Segments ? . Count > 0 )
{
int pointCount = 0 ;
foreach ( var segment in Segments )
{
pointCount + = segment . Length ;
}
return pointCount ;
}
return Points . Length ;
}
/// <summary>
/// Get the Vector2 position of a line index
/// </summary>
/// <remarks>
/// Positive numbers should be used to specify Index and Segment
/// </remarks>
2020-07-09 03:38:28 +08:00
/// <param name="index">Required Index of the point, starting from point 1</param>
2019-11-26 02:49:01 +08:00
/// <param name="segmentIndex">(optional) Required Segment the point is held in, Starting from Segment 1</param>
/// <returns>Vector2 position of the point within UI Space</returns>
public Vector2 GetPosition ( int index , int segmentIndex = 0 )
{
if ( segmentIndex > 0 )
{
return Segments [ segmentIndex - 1 ] [ index - 1 ] ;
}
else if ( Segments . Count > 0 )
{
var segmentIndexCount = 0 ;
var indexCount = index ;
foreach ( var segment in Segments )
{
if ( indexCount - segment . Length > 0 )
{
indexCount - = segment . Length ;
segmentIndexCount + = 1 ;
}
else
{
break ;
}
}
return Segments [ segmentIndexCount ] [ indexCount - 1 ] ;
}
else
{
return Points [ index - 1 ] ;
}
}
/// <summary>
/// Get the Vector2 position of a line within a specific segment
/// </summary>
2020-07-09 03:38:28 +08:00
/// <param name="index">Required Index of the point, starting from point 1</param>
2019-11-26 02:49:01 +08:00
/// <param name="segmentIndex"> Required Segment the point is held in, Starting from Segment 1</param>
/// <returns>Vector2 position of the point within UI Space</returns>
public Vector2 GetPositionBySegment ( int index , int segment )
{
return Segments [ segment ] [ index - 1 ] ;
}
/// <summary>
/// Get the closest point between two given Vector2s from a given Vector2 point
/// </summary>
2020-07-09 03:38:28 +08:00
/// <param name="p1">Starting position</param>
2019-11-26 02:49:01 +08:00
/// <param name="p2">End position</param>
/// <param name="p3">Desired / Selected point</param>
/// <returns>Closest Vector2 position of the target within UI Space</returns>
public Vector2 GetClosestPoint ( Vector2 p1 , Vector2 p2 , Vector2 p3 )
{
Vector2 from_p1_to_p3 = p3 - p1 ;
Vector2 from_p1_to_p2 = p2 - p1 ;
float dot = Vector2 . Dot ( from_p1_to_p3 , from_p1_to_p2 . normalized ) ;
dot / = from_p1_to_p2 . magnitude ;
float t = Mathf . Clamp01 ( dot ) ;
return p1 + from_p1_to_p2 * t ;
}
2021-05-11 01:38:31 +08:00
protected override void OnEnable ( )
{
base . OnEnable ( ) ;
2023-02-07 22:35:43 +08:00
if ( m_points = = null | | m_points ? . Length = = 0 )
2021-05-11 01:38:31 +08:00
{
m_points = new Vector2 [ 1 ] ;
}
}
2017-06-25 18:56:46 +08:00
}
2023-06-29 05:00:16 +08:00
}