Tanoda
UILineRenderer.cs
Go to the documentation of this file.
1
4
5using System.Collections.Generic;
6
8{
9 [AddComponentMenu("UI/Extensions/Primitives/UILineRenderer")]
10 [RequireComponent(typeof(RectTransform))]
12 {
13 private enum SegmentType
14 {
15 Start,
16 Middle,
17 End,
18 Full,
19 }
20
21 public enum JoinType
22 {
23 Bevel,
24 Miter
25 }
26
27 public enum BezierType
28 {
29 None,
30 Quick,
31 Basic,
32 Improved,
33 Catenary,
34 }
35
36 private const float MIN_MITER_JOIN = 15 * Mathf.Deg2Rad;
37
38 // A bevel 'nice' join displaces the vertices of the line segment instead of simply rendering a
39 // quad to connect the endpoints. This improves the look of textured and transparent lines, since
40 // there is no overlapping.
41 private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf.Deg2Rad;
42
43 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;
44 private static Vector2[] startUvs, middleUvs, endUvs, fullUvs;
45
46 [SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
47 internal Vector2[] m_points;
48 [SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
49 internal List<Vector2[]> m_segments;
50
51 [SerializeField, Tooltip("Thickness of the line")]
52 internal float lineThickness = 2;
53 [SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
54 internal bool relativeSize;
55 [SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
56 internal bool lineList;
57 [SerializeField, Tooltip("Add end caps to each line\nMultiple caps when used with Line List")]
58 internal bool lineCaps;
59 [SerializeField, Tooltip("Resolution of the Bezier curve, different to line Resolution")]
60 internal int bezierSegmentsPerCurve = 10;
61
62 public float LineThickness
63 {
64 get { return lineThickness; }
65 set { lineThickness = value; SetAllDirty(); }
66 }
67
68 public bool RelativeSize
69 {
70 get { return relativeSize; }
71 set { relativeSize = value; SetAllDirty(); }
72 }
73
74 public bool LineList
75 {
76 get { return lineList; }
77 set { lineList = value; SetAllDirty(); }
78 }
79
80 public bool LineCaps
81 {
82 get { return lineCaps; }
83 set { lineCaps = value; SetAllDirty(); }
84 }
85
86 [Tooltip("The type of Join used between lines, Square/Mitre or Curved/Bevel")]
87 public JoinType LineJoins = JoinType.Bevel;
88
89 [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")]
91
93 {
94 get { return bezierSegmentsPerCurve; }
95 set { bezierSegmentsPerCurve = value; }
96 }
97
98 [HideInInspector]
99 public bool drivenExternally = false;
100
101
105 public Vector2[] Points
106 {
107 get
108 {
109 return m_points;
110 }
111
112 set
113 {
114 if (m_points == value)
115 return;
116 m_points = value;
117 SetAllDirty();
118 }
119 }
120
124 public List<Vector2[]> Segments
125 {
126 get
127 {
128 return m_segments;
129 }
130
131 set
132 {
133 m_segments = value;
134 SetAllDirty();
135 }
136 }
137
138 private void PopulateMesh(VertexHelper vh, Vector2[] pointsToDraw)
139 {
140 //If Bezier is desired, pick the implementation
141 if (BezierMode != BezierType.None && BezierMode != BezierType.Catenary && pointsToDraw.Length > 3) {
142 BezierPath bezierPath = new BezierPath ();
143
144 bezierPath.SetControlPoints (pointsToDraw);
145 bezierPath.SegmentsPerCurve = bezierSegmentsPerCurve;
146 List<Vector2> drawingPoints;
147 switch (BezierMode) {
148 case BezierType.Basic:
149 drawingPoints = bezierPath.GetDrawingPoints0 ();
150 break;
151 case BezierType.Improved:
152 drawingPoints = bezierPath.GetDrawingPoints1 ();
153 break;
154 default:
155 drawingPoints = bezierPath.GetDrawingPoints2 ();
156 break;
157 }
158
159 pointsToDraw = drawingPoints.ToArray ();
160 }
161 if (BezierMode == BezierType.Catenary && pointsToDraw.Length == 2) {
162 CableCurve cable = new CableCurve (pointsToDraw);
163 cable.slack = Resolution;
164 cable.steps = BezierSegmentsPerCurve;
165 pointsToDraw = cable.Points ();
166 }
167
168 if (ImproveResolution != ResolutionMode.None) {
169 pointsToDraw = IncreaseResolution (pointsToDraw);
170 }
171
172 // scale based on the size of the rect or use absolute, this is switchable
173 var sizeX = !relativeSize ? 1 : rectTransform.rect.width;
174 var sizeY = !relativeSize ? 1 : rectTransform.rect.height;
175 var offsetX = -rectTransform.pivot.x * sizeX;
176 var offsetY = -rectTransform.pivot.y * sizeY;
177
178 // Generate the quads that make up the wide line
179 var segments = new List<UIVertex[]> ();
180 if (lineList) {
181 for (var i = 1; i < pointsToDraw.Length; i += 2) {
182 var start = pointsToDraw [i - 1];
183 var end = pointsToDraw [i];
184 start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
185 end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);
186
187 if (lineCaps) {
188 segments.Add (CreateLineCap (start, end, SegmentType.Start));
189 }
190
191 segments.Add(CreateLineSegment(start, end, SegmentType.Middle, segments.Count > 1 ? segments[segments.Count - 2] : null));
192
193 if (lineCaps) {
194 segments.Add (CreateLineCap (start, end, SegmentType.End));
195 }
196 }
197 } else {
198 for (var i = 1; i < pointsToDraw.Length; i++) {
199 var start = pointsToDraw [i - 1];
200 var end = pointsToDraw [i];
201 start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
202 end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);
203
204 if (lineCaps && i == 1) {
205 segments.Add (CreateLineCap (start, end, SegmentType.Start));
206 }
207
208 segments.Add (CreateLineSegment (start, end, SegmentType.Middle));
209
210 if (lineCaps && i == pointsToDraw.Length - 1) {
211 segments.Add (CreateLineCap (start, end, SegmentType.End));
212 }
213 }
214 }
215
216 // Add the line segments to the vertex helper, creating any joins as needed
217 for (var i = 0; i < segments.Count; i++) {
218 if (!lineList && i < segments.Count - 1) {
219 var vec1 = segments [i] [1].position - segments [i] [2].position;
220 var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
221 var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;
222
223 // Positive sign means the line is turning in a 'clockwise' direction
224 var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);
225
226 // Calculate the miter point
227 var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
228 var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
229 var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;
230
231 var joinType = LineJoins;
232 if (joinType == JoinType.Miter) {
233 // Make sure we can make a miter join without too many artifacts.
234 if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
235 segments [i] [2].position = miterPointA;
236 segments [i] [3].position = miterPointB;
237 segments [i + 1] [0].position = miterPointB;
238 segments [i + 1] [1].position = miterPointA;
239 } else {
240 joinType = JoinType.Bevel;
241 }
242 }
243
244 if (joinType == JoinType.Bevel) {
245 if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
246 if (sign < 0) {
247 segments [i] [2].position = miterPointA;
248 segments [i + 1] [1].position = miterPointA;
249 } else {
250 segments [i] [3].position = miterPointB;
251 segments [i + 1] [0].position = miterPointB;
252 }
253 }
254
255 var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
256 vh.AddUIVertexQuad (join);
257 }
258 }
259
260 vh.AddUIVertexQuad (segments [i]);
261 }
262 if (vh.currentVertCount > 64000) {
263 Debug.LogError ("Max Verticies size is 64000, current mesh verticies count is [" + vh.currentVertCount + "] - Cannot Draw");
264 vh.Clear ();
265 return;
266 }
267
268 }
269
270 protected override void OnPopulateMesh(VertexHelper vh)
271 {
272 if (m_points != null && m_points.Length > 0) {
273 GeneratedUVs ();
274 vh.Clear ();
275
276 PopulateMesh (vh, m_points);
277
278 }
279 else if (m_segments != null && m_segments.Count > 0) {
280 GeneratedUVs ();
281 vh.Clear ();
282
283 for (int s = 0; s < m_segments.Count; s++) {
284 Vector2[] pointsToDraw = m_segments [s];
285 PopulateMesh (vh, pointsToDraw);
286 }
287 }
288
289
290 }
291
292 private UIVertex[] CreateLineCap(Vector2 start, Vector2 end, SegmentType type)
293 {
294 if (type == SegmentType.Start)
295 {
296 var capStart = start - ((end - start).normalized * lineThickness / 2);
297 return CreateLineSegment(capStart, start, SegmentType.Start);
298 }
299 else if (type == SegmentType.End)
300 {
301 var capEnd = end + ((end - start).normalized * lineThickness / 2);
302 return CreateLineSegment(end, capEnd, SegmentType.End);
303 }
304
305 Debug.LogError("Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End");
306 return null;
307 }
308
309 private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type, UIVertex[] previousVert = null)
310 {
311 Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
312
313 Vector2 v1 = Vector2.zero;
314 Vector2 v2 = Vector2.zero;
315 if (previousVert != null) {
316 v1 = new Vector2(previousVert[3].position.x, previousVert[3].position.y);
317 v2 = new Vector2(previousVert[2].position.x, previousVert[2].position.y);
318 } else {
319 v1 = start - offset;
320 v2 = start + offset;
321 }
322
323 var v3 = end + offset;
324 var v4 = end - offset;
325 //Return the VDO with the correct uvs
326 switch (type)
327 {
328 case SegmentType.Start:
329 return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
330 case SegmentType.End:
331 return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
332 case SegmentType.Full:
333 return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
334 default:
335 return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
336 }
337 }
338
339 protected override void GeneratedUVs()
340 {
341 if (activeSprite != null)
342 {
343 var outer = Sprites.DataUtility.GetOuterUV(activeSprite);
344 var inner = Sprites.DataUtility.GetInnerUV(activeSprite);
345 UV_TOP_LEFT = new Vector2(outer.x, outer.y);
346 UV_BOTTOM_LEFT = new Vector2(outer.x, outer.w);
347 UV_TOP_CENTER_LEFT = new Vector2(inner.x, inner.y);
348 UV_TOP_CENTER_RIGHT = new Vector2(inner.z, inner.y);
349 UV_BOTTOM_CENTER_LEFT = new Vector2(inner.x, inner.w);
350 UV_BOTTOM_CENTER_RIGHT = new Vector2(inner.z, inner.w);
351 UV_TOP_RIGHT = new Vector2(outer.z, outer.y);
352 UV_BOTTOM_RIGHT = new Vector2(outer.z, outer.w);
353 }
354 else
355 {
356 UV_TOP_LEFT = Vector2.zero;
357 UV_BOTTOM_LEFT = new Vector2(0, 1);
358 UV_TOP_CENTER_LEFT = new Vector2(0.5f, 0);
359 UV_TOP_CENTER_RIGHT = new Vector2(0.5f, 0);
360 UV_BOTTOM_CENTER_LEFT = new Vector2(0.5f, 1);
361 UV_BOTTOM_CENTER_RIGHT = new Vector2(0.5f, 1);
362 UV_TOP_RIGHT = new Vector2(1, 0);
363 UV_BOTTOM_RIGHT = Vector2.one;
364 }
365
366
367 startUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_CENTER_LEFT, UV_TOP_CENTER_LEFT };
368 middleUvs = new[] { UV_TOP_CENTER_LEFT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_CENTER_RIGHT };
369 endUvs = new[] { UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_RIGHT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
370 fullUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
371 }
372
373 protected override void ResolutionToNativeSize(float distance)
374 {
375 if (UseNativeSize)
376 {
377 m_Resolution = distance / (activeSprite.rect.width / pixelsPerUnit);
378 lineThickness = activeSprite.rect.height / pixelsPerUnit;
379 }
380 }
381
382 private int GetSegmentPointCount()
383 {
384 if (Segments?.Count > 0)
385 {
386 int pointCount = 0;
387 foreach (var segment in Segments)
388 {
389 pointCount += segment.Length;
390 }
391 return pointCount;
392 }
393 return Points.Length;
394 }
395
405 public Vector2 GetPosition(int index, int segmentIndex = 0)
406 {
407 if (segmentIndex > 0)
408 {
409 return Segments[segmentIndex - 1][index - 1];
410 }
411 else if (Segments.Count > 0)
412 {
413 var segmentIndexCount = 0;
414 var indexCount = index;
415 foreach (var segment in Segments)
416 {
417 if (indexCount - segment.Length > 0)
418 {
419 indexCount -= segment.Length;
420 segmentIndexCount += 1;
421 }
422 else
423 {
424 break;
425 }
426 }
427 return Segments[segmentIndexCount][indexCount - 1];
428 }
429 else
430 {
431 return Points[index - 1];
432 }
433 }
434
441 public Vector2 GetPositionBySegment(int index, int segment)
442 {
443 return Segments[segment][index - 1];
444 }
445
453 public Vector2 GetClosestPoint(Vector2 p1, Vector2 p2, Vector2 p3)
454 {
455 Vector2 from_p1_to_p3 = p3 - p1;
456 Vector2 from_p1_to_p2 = p2 - p1;
457 float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
458 dot /= from_p1_to_p2.magnitude;
459 float t = Mathf.Clamp01(dot);
460 return p1 + from_p1_to_p2 * t;
461 }
462 }
463}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
void SetControlPoints(List< Vector2 > newControlPoints)
Definition: BezierPath.cs:51
Vector2[] Points
Points to be drawn in the line.
override void ResolutionToNativeSize(float distance)
override void OnPopulateMesh(VertexHelper vh)
Vector2 GetPosition(int index, int segmentIndex=0)
Get the Vector2 position of a line index
List< Vector2[]> Segments
List of Segments to be drawn.
Vector2 GetClosestPoint(Vector2 p1, Vector2 p2, Vector2 p3)
Get the closest point between two given Vector2s from a given Vector2 point
Vector2 GetPositionBySegment(int index, int segment)
Get the Vector2 position of a line within a specific segment
Vector2[] IncreaseResolution(Vector2[] input)
UIVertex[] SetVbo(Vector2[] vertices, Vector2[] uvs)
Credit Erdener Gonenc - @PixelEnvision.