UILineRendererList.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /// Credit jack.sydorenko, firagon
  2. /// Sourced from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/
  3. /// Updated/Refactored from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/#post-2528050
  4. using System.Collections.Generic;
  5. namespace UnityEngine.UI.Extensions
  6. {
  7. [AddComponentMenu("UI/Extensions/Primitives/UILineRendererList")]
  8. [RequireComponent(typeof(RectTransform))]
  9. public class UILineRendererList : UIPrimitiveBase
  10. {
  11. private enum SegmentType
  12. {
  13. Start,
  14. Middle,
  15. End,
  16. Full,
  17. }
  18. public enum JoinType
  19. {
  20. Bevel,
  21. Miter
  22. }
  23. public enum BezierType
  24. {
  25. None,
  26. Quick,
  27. Basic,
  28. Improved,
  29. Catenary,
  30. }
  31. private const float MIN_MITER_JOIN = 15 * Mathf.Deg2Rad;
  32. // A bevel 'nice' join displaces the vertices of the line segment instead of simply rendering a
  33. // quad to connect the endpoints. This improves the look of textured and transparent lines, since
  34. // there is no overlapping.
  35. private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf.Deg2Rad;
  36. 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;
  37. private static Vector2[] startUvs, middleUvs, endUvs, fullUvs;
  38. [SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
  39. internal List<Vector2> m_points;
  40. // [SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
  41. //internal List<Vector2[]> m_segments;
  42. [SerializeField, Tooltip("Thickness of the line")]
  43. internal float lineThickness = 2;
  44. [SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
  45. internal bool relativeSize;
  46. [SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
  47. internal bool lineList;
  48. [SerializeField, Tooltip("Add end caps to each line\nMultiple caps when used with Line List")]
  49. internal bool lineCaps;
  50. [SerializeField, Tooltip("Resolution of the Bezier curve, different to line Resolution")]
  51. internal int bezierSegmentsPerCurve = 10;
  52. public float LineThickness
  53. {
  54. get { return lineThickness; }
  55. set { lineThickness = value; SetAllDirty(); }
  56. }
  57. public bool RelativeSize
  58. {
  59. get { return relativeSize; }
  60. set { relativeSize = value; SetAllDirty(); }
  61. }
  62. public bool LineList
  63. {
  64. get { return lineList; }
  65. set { lineList = value; SetAllDirty(); }
  66. }
  67. public bool LineCaps
  68. {
  69. get { return lineCaps; }
  70. set { lineCaps = value; SetAllDirty(); }
  71. }
  72. [Tooltip("The type of Join used between lines, Square/Mitre or Curved/Bevel")]
  73. public JoinType LineJoins = JoinType.Bevel;
  74. [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")]
  75. public BezierType BezierMode = BezierType.None;
  76. public int BezierSegmentsPerCurve
  77. {
  78. get { return bezierSegmentsPerCurve; }
  79. set { bezierSegmentsPerCurve = value; }
  80. }
  81. [HideInInspector]
  82. public bool drivenExternally = false;
  83. /// <summary>
  84. /// Points to be drawn in the line.
  85. /// </summary>
  86. /// <remarks>Don't add points to the list directly, use the add / remove functions</remarks>
  87. public List<Vector2> Points
  88. {
  89. get
  90. {
  91. return m_points;
  92. }
  93. set
  94. {
  95. if (m_points == value)
  96. return;
  97. m_points = value;
  98. SetAllDirty();
  99. }
  100. }
  101. // /// <summary>
  102. // /// List of Segments to be drawn.
  103. // /// </summary>
  104. // public List<Vector2[]> Segments
  105. //{
  106. // get
  107. // {
  108. // return m_segments;
  109. // }
  110. // set
  111. // {
  112. // m_segments = value;
  113. // SetAllDirty();
  114. // }
  115. //}
  116. public void AddPoint(Vector2 pointToAdd)
  117. {
  118. m_points.Add(pointToAdd);
  119. SetAllDirty();
  120. }
  121. public void RemovePoint(Vector2 pointToRemove)
  122. {
  123. m_points.Remove(pointToRemove);
  124. SetAllDirty();
  125. }
  126. public void ClearPoints()
  127. {
  128. m_points.Clear();
  129. SetAllDirty();
  130. }
  131. private void PopulateMesh(VertexHelper vh, List<Vector2> pointsToDraw)
  132. {
  133. //If Bezier is desired, pick the implementation
  134. if (BezierMode != BezierType.None && BezierMode != BezierType.Catenary && pointsToDraw.Count > 3) {
  135. BezierPath bezierPath = new BezierPath ();
  136. bezierPath.SetControlPoints (pointsToDraw);
  137. bezierPath.SegmentsPerCurve = bezierSegmentsPerCurve;
  138. List<Vector2> drawingPoints;
  139. switch (BezierMode) {
  140. case BezierType.Basic:
  141. drawingPoints = bezierPath.GetDrawingPoints0 ();
  142. break;
  143. case BezierType.Improved:
  144. drawingPoints = bezierPath.GetDrawingPoints1 ();
  145. break;
  146. default:
  147. drawingPoints = bezierPath.GetDrawingPoints2 ();
  148. break;
  149. }
  150. pointsToDraw = drawingPoints;
  151. }
  152. if (BezierMode == BezierType.Catenary && pointsToDraw.Count == 2) {
  153. CableCurve cable = new CableCurve (pointsToDraw);
  154. cable.slack = Resolution;
  155. cable.steps = BezierSegmentsPerCurve;
  156. pointsToDraw.Clear();
  157. pointsToDraw.AddRange(cable.Points());
  158. }
  159. if (ImproveResolution != ResolutionMode.None) {
  160. pointsToDraw = IncreaseResolution (pointsToDraw);
  161. }
  162. // scale based on the size of the rect or use absolute, this is switchable
  163. var sizeX = !relativeSize ? 1 : rectTransform.rect.width;
  164. var sizeY = !relativeSize ? 1 : rectTransform.rect.height;
  165. var offsetX = -rectTransform.pivot.x * sizeX;
  166. var offsetY = -rectTransform.pivot.y * sizeY;
  167. // Generate the quads that make up the wide line
  168. var segments = new List<UIVertex[]> ();
  169. if (lineList) {
  170. for (var i = 1; i < pointsToDraw.Count; i += 2) {
  171. var start = pointsToDraw [i - 1];
  172. var end = pointsToDraw [i];
  173. start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
  174. end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);
  175. if (lineCaps) {
  176. segments.Add (CreateLineCap (start, end, SegmentType.Start));
  177. }
  178. //segments.Add(CreateLineSegment(start, end, SegmentType.Full));
  179. segments.Add (CreateLineSegment (start, end, SegmentType.Middle));
  180. if (lineCaps) {
  181. segments.Add (CreateLineCap (start, end, SegmentType.End));
  182. }
  183. }
  184. } else {
  185. for (var i = 1; i < pointsToDraw.Count; i++) {
  186. var start = pointsToDraw [i - 1];
  187. var end = pointsToDraw [i];
  188. start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
  189. end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);
  190. if (lineCaps && i == 1) {
  191. segments.Add (CreateLineCap (start, end, SegmentType.Start));
  192. }
  193. segments.Add (CreateLineSegment (start, end, SegmentType.Middle));
  194. //segments.Add(CreateLineSegment(start, end, SegmentType.Full));
  195. if (lineCaps && i == pointsToDraw.Count - 1) {
  196. segments.Add (CreateLineCap (start, end, SegmentType.End));
  197. }
  198. }
  199. }
  200. // Add the line segments to the vertex helper, creating any joins as needed
  201. for (var i = 0; i < segments.Count; i++) {
  202. if (!lineList && i < segments.Count - 1) {
  203. var vec1 = segments [i] [1].position - segments [i] [2].position;
  204. var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
  205. var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;
  206. // Positive sign means the line is turning in a 'clockwise' direction
  207. var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);
  208. // Calculate the miter point
  209. var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
  210. var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
  211. var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;
  212. var joinType = LineJoins;
  213. if (joinType == JoinType.Miter) {
  214. // Make sure we can make a miter join without too many artifacts.
  215. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
  216. segments [i] [2].position = miterPointA;
  217. segments [i] [3].position = miterPointB;
  218. segments [i + 1] [0].position = miterPointB;
  219. segments [i + 1] [1].position = miterPointA;
  220. } else {
  221. joinType = JoinType.Bevel;
  222. }
  223. }
  224. if (joinType == JoinType.Bevel) {
  225. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
  226. if (sign < 0) {
  227. segments [i] [2].position = miterPointA;
  228. segments [i + 1] [1].position = miterPointA;
  229. } else {
  230. segments [i] [3].position = miterPointB;
  231. segments [i + 1] [0].position = miterPointB;
  232. }
  233. }
  234. var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
  235. vh.AddUIVertexQuad (join);
  236. }
  237. }
  238. vh.AddUIVertexQuad (segments [i]);
  239. }
  240. if (vh.currentVertCount > 64000) {
  241. Debug.LogError ("Max Verticies size is 64000, current mesh verticies count is [" + vh.currentVertCount + "] - Cannot Draw");
  242. vh.Clear ();
  243. return;
  244. }
  245. }
  246. protected override void OnPopulateMesh(VertexHelper vh)
  247. {
  248. if (m_points != null && m_points.Count > 0) {
  249. GeneratedUVs ();
  250. vh.Clear ();
  251. PopulateMesh (vh, m_points);
  252. }
  253. //else if (m_segments != null && m_segments.Count > 0) {
  254. // GeneratedUVs ();
  255. // vh.Clear ();
  256. // for (int s = 0; s < m_segments.Count; s++) {
  257. // Vector2[] pointsToDraw = m_segments [s];
  258. // PopulateMesh (vh, pointsToDraw);
  259. // }
  260. //}
  261. }
  262. private UIVertex[] CreateLineCap(Vector2 start, Vector2 end, SegmentType type)
  263. {
  264. if (type == SegmentType.Start)
  265. {
  266. var capStart = start - ((end - start).normalized * lineThickness / 2);
  267. return CreateLineSegment(capStart, start, SegmentType.Start);
  268. }
  269. else if (type == SegmentType.End)
  270. {
  271. var capEnd = end + ((end - start).normalized * lineThickness / 2);
  272. return CreateLineSegment(end, capEnd, SegmentType.End);
  273. }
  274. Debug.LogError("Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End");
  275. return null;
  276. }
  277. private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type)
  278. {
  279. Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
  280. var v1 = start - offset;
  281. var v2 = start + offset;
  282. var v3 = end + offset;
  283. var v4 = end - offset;
  284. //Return the VDO with the correct uvs
  285. switch (type)
  286. {
  287. case SegmentType.Start:
  288. return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
  289. case SegmentType.End:
  290. return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
  291. case SegmentType.Full:
  292. return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
  293. default:
  294. return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
  295. }
  296. }
  297. protected override void GeneratedUVs()
  298. {
  299. if (activeSprite != null)
  300. {
  301. var outer = Sprites.DataUtility.GetOuterUV(activeSprite);
  302. var inner = Sprites.DataUtility.GetInnerUV(activeSprite);
  303. UV_TOP_LEFT = new Vector2(outer.x, outer.y);
  304. UV_BOTTOM_LEFT = new Vector2(outer.x, outer.w);
  305. UV_TOP_CENTER_LEFT = new Vector2(inner.x, inner.y);
  306. UV_TOP_CENTER_RIGHT = new Vector2(inner.z, inner.y);
  307. UV_BOTTOM_CENTER_LEFT = new Vector2(inner.x, inner.w);
  308. UV_BOTTOM_CENTER_RIGHT = new Vector2(inner.z, inner.w);
  309. UV_TOP_RIGHT = new Vector2(outer.z, outer.y);
  310. UV_BOTTOM_RIGHT = new Vector2(outer.z, outer.w);
  311. }
  312. else
  313. {
  314. UV_TOP_LEFT = Vector2.zero;
  315. UV_BOTTOM_LEFT = new Vector2(0, 1);
  316. UV_TOP_CENTER_LEFT = new Vector2(0.5f, 0);
  317. UV_TOP_CENTER_RIGHT = new Vector2(0.5f, 0);
  318. UV_BOTTOM_CENTER_LEFT = new Vector2(0.5f, 1);
  319. UV_BOTTOM_CENTER_RIGHT = new Vector2(0.5f, 1);
  320. UV_TOP_RIGHT = new Vector2(1, 0);
  321. UV_BOTTOM_RIGHT = Vector2.one;
  322. }
  323. startUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_CENTER_LEFT, UV_TOP_CENTER_LEFT };
  324. middleUvs = new[] { UV_TOP_CENTER_LEFT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_CENTER_RIGHT };
  325. endUvs = new[] { UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_RIGHT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
  326. fullUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
  327. }
  328. protected override void ResolutionToNativeSize(float distance)
  329. {
  330. if (UseNativeSize)
  331. {
  332. m_Resolution = distance / (activeSprite.rect.width / pixelsPerUnit);
  333. lineThickness = activeSprite.rect.height / pixelsPerUnit;
  334. }
  335. }
  336. }
  337. }