2019-03-09 21:26:11 +08:00
/// 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/UILineRendererList")]
[RequireComponent(typeof(RectTransform))]
public class UILineRendererList : UIPrimitiveBase
{
private enum SegmentType
{
Start ,
Middle ,
End ,
Full ,
}
public enum JoinType
{
Bevel ,
Miter
}
public enum BezierType
{
None ,
Quick ,
Basic ,
Improved ,
Catenary ,
}
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 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 ;
[SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
internal List < Vector2 > m_points ;
// [SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
//internal List<Vector2[]> m_segments;
[SerializeField, Tooltip("Thickness of the line")]
internal float lineThickness = 2 ;
[SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
internal bool relativeSize ;
[SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
internal bool lineList ;
[SerializeField, Tooltip("Add end caps to each line\nMultiple caps when used with Line List")]
internal bool lineCaps ;
[SerializeField, Tooltip("Resolution of the Bezier curve, different to line Resolution")]
internal int bezierSegmentsPerCurve = 10 ;
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 ( ) ; }
}
[Tooltip("The type of Join used between lines, Square/Mitre or Curved/Bevel")]
public JoinType LineJoins = JoinType . Bevel ;
[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 ;
public int BezierSegmentsPerCurve
{
get { return bezierSegmentsPerCurve ; }
set { bezierSegmentsPerCurve = value ; }
}
[HideInInspector]
public bool drivenExternally = false ;
/// <summary>
/// Points to be drawn in the line.
/// </summary>
/// <remarks>Don't add points to the list directly, use the add / remove functions</remarks>
public List < Vector2 > Points
{
get
{
return m_points ;
}
set
{
if ( m_points = = value )
return ;
m_points = value ;
SetAllDirty ( ) ;
}
}
public void AddPoint ( Vector2 pointToAdd )
{
m_points . Add ( pointToAdd ) ;
SetAllDirty ( ) ;
}
public void RemovePoint ( Vector2 pointToRemove )
{
m_points . Remove ( pointToRemove ) ;
SetAllDirty ( ) ;
}
public void ClearPoints ( )
{
m_points . Clear ( ) ;
SetAllDirty ( ) ;
}
private void PopulateMesh ( VertexHelper vh , List < Vector2 > pointsToDraw )
{
//If Bezier is desired, pick the implementation
if ( BezierMode ! = BezierType . None & & BezierMode ! = BezierType . Catenary & & pointsToDraw . Count > 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 ;
}
if ( BezierMode = = BezierType . Catenary & & pointsToDraw . Count = = 2 ) {
CableCurve cable = new CableCurve ( pointsToDraw ) ;
2020-07-09 03:38:28 +08:00
cable . slack = Resolution ;
2019-03-09 21:26:11 +08:00
cable . steps = BezierSegmentsPerCurve ;
pointsToDraw . Clear ( ) ;
pointsToDraw . AddRange ( cable . Points ( ) ) ;
}
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 ;
// Generate the quads that make up the wide line
var segments = new List < UIVertex [ ] > ( ) ;
if ( lineList ) {
for ( var i = 1 ; i < pointsToDraw . Count ; 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.Full));
segments . Add ( CreateLineSegment ( start , end , SegmentType . Middle ) ) ;
if ( lineCaps ) {
segments . Add ( CreateLineCap ( start , end , SegmentType . End ) ) ;
}
}
} else {
for ( var i = 1 ; i < pointsToDraw . Count ; 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 ) ) ;
//segments.Add(CreateLineSegment(start, end, SegmentType.Full));
if ( lineCaps & & i = = pointsToDraw . Count - 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 ] ) ;
}
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" ) ;
2019-03-09 21:26:11 +08:00
vh . Clear ( ) ;
return ;
}
}
protected override void OnPopulateMesh ( VertexHelper vh )
{
if ( m_points ! = null & & m_points . Count > 0 ) {
GeneratedUVs ( ) ;
vh . Clear ( ) ;
PopulateMesh ( vh , m_points ) ;
}
}
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 )
{
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 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 ) ;
case SegmentType . Full :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , fullUvs ) ;
default :
return SetVbo ( new [ ] { v1 , v2 , v3 , v4 } , middleUvs ) ;
}
}
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 ;
}
}
}
}