UILineRenderer.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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/UILineRenderer")]
  8. [RequireComponent(typeof(RectTransform))]
  9. public class UILineRenderer : 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 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. public Vector2[] Points
  87. {
  88. get
  89. {
  90. return m_points;
  91. }
  92. set
  93. {
  94. if (m_points == value)
  95. return;
  96. m_points = value;
  97. SetAllDirty();
  98. }
  99. }
  100. /// <summary>
  101. /// List of Segments to be drawn.
  102. /// </summary>
  103. public List<Vector2[]> Segments
  104. {
  105. get
  106. {
  107. return m_segments;
  108. }
  109. set
  110. {
  111. m_segments = value;
  112. SetAllDirty();
  113. }
  114. }
  115. private void PopulateMesh(VertexHelper vh, Vector2[] pointsToDraw)
  116. {
  117. //If Bezier is desired, pick the implementation
  118. if (BezierMode != BezierType.None && BezierMode != BezierType.Catenary && pointsToDraw.Length > 3) {
  119. BezierPath bezierPath = new BezierPath ();
  120. bezierPath.SetControlPoints (pointsToDraw);
  121. bezierPath.SegmentsPerCurve = bezierSegmentsPerCurve;
  122. List<Vector2> drawingPoints;
  123. switch (BezierMode) {
  124. case BezierType.Basic:
  125. drawingPoints = bezierPath.GetDrawingPoints0 ();
  126. break;
  127. case BezierType.Improved:
  128. drawingPoints = bezierPath.GetDrawingPoints1 ();
  129. break;
  130. default:
  131. drawingPoints = bezierPath.GetDrawingPoints2 ();
  132. break;
  133. }
  134. pointsToDraw = drawingPoints.ToArray ();
  135. }
  136. if (BezierMode == BezierType.Catenary && pointsToDraw.Length == 2) {
  137. CableCurve cable = new CableCurve (pointsToDraw);
  138. cable.slack = Resolution;
  139. cable.steps = BezierSegmentsPerCurve;
  140. pointsToDraw = cable.Points ();
  141. }
  142. if (ImproveResolution != ResolutionMode.None) {
  143. pointsToDraw = IncreaseResolution (pointsToDraw);
  144. }
  145. // scale based on the size of the rect or use absolute, this is switchable
  146. var sizeX = !relativeSize ? 1 : rectTransform.rect.width;
  147. var sizeY = !relativeSize ? 1 : rectTransform.rect.height;
  148. var offsetX = -rectTransform.pivot.x * sizeX;
  149. var offsetY = -rectTransform.pivot.y * sizeY;
  150. // Generate the quads that make up the wide line
  151. var segments = new List<UIVertex[]> ();
  152. if (lineList) {
  153. //Loop through list in line pairs, skipping drawing between lines
  154. for (var i = 1; i < pointsToDraw.Length; i += 2) {
  155. var start = pointsToDraw [i - 1];
  156. var end = pointsToDraw [i];
  157. start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
  158. end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);
  159. if (lineCaps) {
  160. segments.Add (CreateLineCap (start, end, SegmentType.Start));
  161. }
  162. // 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.
  163. segments.Add(CreateLineSegment(start, end, SegmentType.Middle));
  164. if (lineCaps) {
  165. segments.Add (CreateLineCap (start, end, SegmentType.End));
  166. }
  167. }
  168. } else {
  169. //Draw full lines
  170. for (var i = 1; i < pointsToDraw.Length; i++) {
  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 && i == 1) {
  176. segments.Add (CreateLineCap (start, end, SegmentType.Start));
  177. }
  178. segments.Add (CreateLineSegment (start, end, SegmentType.Middle));
  179. if (lineCaps && i == pointsToDraw.Length - 1) {
  180. segments.Add (CreateLineCap (start, end, SegmentType.End));
  181. }
  182. }
  183. }
  184. // Add the line segments to the vertex helper, creating any joins as needed
  185. for (var i = 0; i < segments.Count; i++) {
  186. if (!lineList && i < segments.Count - 1) {
  187. var vec1 = segments [i] [1].position - segments [i] [2].position;
  188. var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
  189. var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;
  190. // Positive sign means the line is turning in a 'clockwise' direction
  191. var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);
  192. // Calculate the miter point
  193. var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
  194. var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
  195. var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;
  196. var joinType = LineJoins;
  197. if (joinType == JoinType.Miter) {
  198. // Make sure we can make a miter join without too many artifacts.
  199. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
  200. segments [i] [2].position = miterPointA;
  201. segments [i] [3].position = miterPointB;
  202. segments [i + 1] [0].position = miterPointB;
  203. segments [i + 1] [1].position = miterPointA;
  204. } else {
  205. joinType = JoinType.Bevel;
  206. }
  207. }
  208. if (joinType == JoinType.Bevel) {
  209. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
  210. if (sign < 0) {
  211. segments [i] [2].position = miterPointA;
  212. segments [i + 1] [1].position = miterPointA;
  213. } else {
  214. segments [i] [3].position = miterPointB;
  215. segments [i + 1] [0].position = miterPointB;
  216. }
  217. }
  218. var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
  219. vh.AddUIVertexQuad (join);
  220. }
  221. }
  222. vh.AddUIVertexQuad (segments [i]);
  223. }
  224. if (vh.currentVertCount > 64000) {
  225. Debug.LogError ("Max Verticies size is 64000, current mesh verticies count is [" + vh.currentVertCount + "] - Cannot Draw");
  226. vh.Clear ();
  227. return;
  228. }
  229. }
  230. protected override void OnPopulateMesh(VertexHelper vh)
  231. {
  232. if (m_points != null && m_points.Length > 0) {
  233. GeneratedUVs ();
  234. vh.Clear ();
  235. PopulateMesh (vh, m_points);
  236. }
  237. else if (m_segments != null && m_segments.Count > 0) {
  238. GeneratedUVs ();
  239. vh.Clear ();
  240. for (int s = 0; s < m_segments.Count; s++) {
  241. Vector2[] pointsToDraw = m_segments [s];
  242. PopulateMesh (vh, pointsToDraw);
  243. }
  244. }
  245. }
  246. private UIVertex[] CreateLineCap(Vector2 start, Vector2 end, SegmentType type)
  247. {
  248. if (type == SegmentType.Start)
  249. {
  250. var capStart = start - ((end - start).normalized * lineThickness / 2);
  251. return CreateLineSegment(capStart, start, SegmentType.Start);
  252. }
  253. else if (type == SegmentType.End)
  254. {
  255. var capEnd = end + ((end - start).normalized * lineThickness / 2);
  256. return CreateLineSegment(end, capEnd, SegmentType.End);
  257. }
  258. Debug.LogError("Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End");
  259. return null;
  260. }
  261. private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type, UIVertex[] previousVert = null)
  262. {
  263. Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
  264. Vector2 v1 = Vector2.zero;
  265. Vector2 v2 = Vector2.zero;
  266. if (previousVert != null) {
  267. v1 = new Vector2(previousVert[3].position.x, previousVert[3].position.y);
  268. v2 = new Vector2(previousVert[2].position.x, previousVert[2].position.y);
  269. } else {
  270. v1 = start - offset;
  271. v2 = start + offset;
  272. }
  273. var v3 = end + offset;
  274. var v4 = end - offset;
  275. //Return the VDO with the correct uvs
  276. switch (type)
  277. {
  278. case SegmentType.Start:
  279. return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
  280. case SegmentType.End:
  281. return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
  282. case SegmentType.Full:
  283. return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
  284. default:
  285. return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
  286. }
  287. }
  288. protected override void GeneratedUVs()
  289. {
  290. if (activeSprite != null)
  291. {
  292. var outer = Sprites.DataUtility.GetOuterUV(activeSprite);
  293. var inner = Sprites.DataUtility.GetInnerUV(activeSprite);
  294. UV_TOP_LEFT = new Vector2(outer.x, outer.y);
  295. UV_BOTTOM_LEFT = new Vector2(outer.x, outer.w);
  296. UV_TOP_CENTER_LEFT = new Vector2(inner.x, inner.y);
  297. UV_TOP_CENTER_RIGHT = new Vector2(inner.z, inner.y);
  298. UV_BOTTOM_CENTER_LEFT = new Vector2(inner.x, inner.w);
  299. UV_BOTTOM_CENTER_RIGHT = new Vector2(inner.z, inner.w);
  300. UV_TOP_RIGHT = new Vector2(outer.z, outer.y);
  301. UV_BOTTOM_RIGHT = new Vector2(outer.z, outer.w);
  302. }
  303. else
  304. {
  305. UV_TOP_LEFT = Vector2.zero;
  306. UV_BOTTOM_LEFT = new Vector2(0, 1);
  307. UV_TOP_CENTER_LEFT = new Vector2(0.5f, 0);
  308. UV_TOP_CENTER_RIGHT = new Vector2(0.5f, 0);
  309. UV_BOTTOM_CENTER_LEFT = new Vector2(0.5f, 1);
  310. UV_BOTTOM_CENTER_RIGHT = new Vector2(0.5f, 1);
  311. UV_TOP_RIGHT = new Vector2(1, 0);
  312. UV_BOTTOM_RIGHT = Vector2.one;
  313. }
  314. startUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_CENTER_LEFT, UV_TOP_CENTER_LEFT };
  315. middleUvs = new[] { UV_TOP_CENTER_LEFT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_CENTER_RIGHT };
  316. endUvs = new[] { UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_RIGHT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
  317. fullUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
  318. }
  319. protected override void ResolutionToNativeSize(float distance)
  320. {
  321. if (UseNativeSize)
  322. {
  323. m_Resolution = distance / (activeSprite.rect.width / pixelsPerUnit);
  324. lineThickness = activeSprite.rect.height / pixelsPerUnit;
  325. }
  326. }
  327. private int GetSegmentPointCount()
  328. {
  329. if (Segments?.Count > 0)
  330. {
  331. int pointCount = 0;
  332. foreach (var segment in Segments)
  333. {
  334. pointCount += segment.Length;
  335. }
  336. return pointCount;
  337. }
  338. return Points.Length;
  339. }
  340. /// <summary>
  341. /// Get the Vector2 position of a line index
  342. /// </summary>
  343. /// <remarks>
  344. /// Positive numbers should be used to specify Index and Segment
  345. /// </remarks>
  346. /// <param name="index">Required Index of the point, starting from point 1</param>
  347. /// <param name="segmentIndex">(optional) Required Segment the point is held in, Starting from Segment 1</param>
  348. /// <returns>Vector2 position of the point within UI Space</returns>
  349. public Vector2 GetPosition(int index, int segmentIndex = 0)
  350. {
  351. if (segmentIndex > 0)
  352. {
  353. return Segments[segmentIndex - 1][index - 1];
  354. }
  355. else if (Segments.Count > 0)
  356. {
  357. var segmentIndexCount = 0;
  358. var indexCount = index;
  359. foreach (var segment in Segments)
  360. {
  361. if (indexCount - segment.Length > 0)
  362. {
  363. indexCount -= segment.Length;
  364. segmentIndexCount += 1;
  365. }
  366. else
  367. {
  368. break;
  369. }
  370. }
  371. return Segments[segmentIndexCount][indexCount - 1];
  372. }
  373. else
  374. {
  375. return Points[index - 1];
  376. }
  377. }
  378. /// <summary>
  379. /// Get the Vector2 position of a line within a specific segment
  380. /// </summary>
  381. /// <param name="index">Required Index of the point, starting from point 1</param>
  382. /// <param name="segmentIndex"> Required Segment the point is held in, Starting from Segment 1</param>
  383. /// <returns>Vector2 position of the point within UI Space</returns>
  384. public Vector2 GetPositionBySegment(int index, int segment)
  385. {
  386. return Segments[segment][index - 1];
  387. }
  388. /// <summary>
  389. /// Get the closest point between two given Vector2s from a given Vector2 point
  390. /// </summary>
  391. /// <param name="p1">Starting position</param>
  392. /// <param name="p2">End position</param>
  393. /// <param name="p3">Desired / Selected point</param>
  394. /// <returns>Closest Vector2 position of the target within UI Space</returns>
  395. public Vector2 GetClosestPoint(Vector2 p1, Vector2 p2, Vector2 p3)
  396. {
  397. Vector2 from_p1_to_p3 = p3 - p1;
  398. Vector2 from_p1_to_p2 = p2 - p1;
  399. float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
  400. dot /= from_p1_to_p2.magnitude;
  401. float t = Mathf.Clamp01(dot);
  402. return p1 + from_p1_to_p2 * t;
  403. }
  404. protected override void OnEnable()
  405. {
  406. base.OnEnable();
  407. if (m_points.Length == 0)
  408. {
  409. m_points = new Vector2[1];
  410. }
  411. }
  412. }
  413. }