Tanoda
CUIGraphic.cs
Go to the documentation of this file.
1
3
4using System.Collections.Generic;
5
6#if UNITY_EDITOR
7using UnityEditor;
8#endif
9
11{
12 [RequireComponent(typeof(RectTransform))]
13 [RequireComponent(typeof(Graphic))]
14 [DisallowMultipleComponent]
15 [AddComponentMenu("UI/Effects/Extensions/Curly UI Graphic")]
16 public class CUIGraphic : BaseMeshEffect
17 {
18 // Describing the properties that are shared by all objects of this class
19 #region Nature
20
21 readonly public static int bottomCurveIdx = 0;
22 readonly public static int topCurveIdx = 1;
23
24 #endregion
25
29 #region Description
30
31 [Tooltip("Set true to make the curve/morph to work. Set false to quickly see the original UI.")]
32 [SerializeField]
33 protected bool isCurved = true;
34 public bool IsCurved
35 {
36 get
37 {
38 return isCurved;
39 }
40 }
41
42 [Tooltip("Set true to dynamically change the curve according to the dynamic change of the UI layout")]
43 [SerializeField]
44 protected bool isLockWithRatio = true;
45 public bool IsLockWithRatio
46 {
47 get
48 {
49 return isLockWithRatio;
50 }
51 }
52
53 [Tooltip("Pick a higher resolution to improve the quality of the curved graphic.")]
54 [SerializeField]
55 [Range(0.01f, 30.0f)]
56 protected float resolution = 5.0f;
57
58 #endregion
59
63 #region Links
64
65 protected RectTransform rectTrans;
66 public RectTransform RectTrans
67 {
68 get
69 {
70 return rectTrans;
71 }
72 }
73
74 [Tooltip("Put in the Graphic you want to curve/morph here.")]
75 [SerializeField]
76 protected Graphic uiGraphic;
77 public Graphic UIGraphic
78 {
79 get
80 {
81 return uiGraphic;
82 }
83 }
84 [Tooltip("Put in the reference Graphic that will be used to tune the bezier curves. Think button image and text.")]
85 [SerializeField]
88 {
89 get
90 {
91 return refCUIGraphic;
92 }
93 }
94
95 [Tooltip("Do not touch this unless you are sure what you are doing. The curves are (re)generated automatically.")]
96 [SerializeField]
99 {
100 get
101 {
102 return refCurves;
103 }
104 }
105
106 [HideInInspector]
107 [SerializeField]
110 {
111 get
112 {
114 }
115 }
116
117#if UNITY_EDITOR
118
119 public CUIBezierCurve[] EDITOR_RefCurves
120 {
121 set
122 {
123 refCurves = value;
124 }
125 }
126
127 public Vector3_Array2D[] EDITOR_RefCurvesControlRatioPoints
128 {
129 set
130 {
132 }
133 }
134
135#endif
136
137 #endregion
138
139 // Methods that are used often.
140 #region Reuse
141
142 protected List<UIVertex> reuse_quads = new List<UIVertex>();
143
144 #endregion
145
146 #region Action
147
148 protected void solveDoubleEquationWithVector(float _x_1, float _y_1, float _x_2, float _y_2, Vector3 _constant_1, Vector3 _contant_2, out Vector3 _x, out Vector3 _y)
149 {
150 Vector3 f;
151 float g;
152
153 if (Mathf.Abs(_x_1) > Mathf.Abs(_x_2))
154 {
155 f = _constant_1 * _x_2 / _x_1;
156 g = _y_1 * _x_2 / _x_1;
157 _y = (_contant_2 - f) / (_y_2 - g);
158 if (_x_2 != 0)
159 _x = (f - g * _y) / _x_2;
160 else
161 _x = (_constant_1 - _y_1 * _y) / _x_1;
162 }
163 else
164 {
165 f = _contant_2 * _x_1 / _x_2;
166 g = _y_2 * _x_1 / _x_2;
167 _x = (_constant_1 - f) / (_y_1 - g);
168 if (_x_1 != 0)
169 _y = (f - g * _x) / _x_1;
170 else
171 _y = (_contant_2 - _y_2 * _x) / _x_2;
172 }
173 }
174
175
176 protected UIVertex uiVertexLerp(UIVertex _a, UIVertex _b, float _time)
177 {
178 UIVertex tmpUIVertex = new UIVertex();
179
180 tmpUIVertex.position = Vector3.Lerp(_a.position, _b.position, _time);
181 tmpUIVertex.normal = Vector3.Lerp(_a.normal, _b.normal, _time);
182 tmpUIVertex.tangent = Vector3.Lerp(_a.tangent, _b.tangent, _time);
183 tmpUIVertex.uv0 = Vector2.Lerp(_a.uv0, _b.uv0, _time);
184 tmpUIVertex.uv1 = Vector2.Lerp(_a.uv1, _b.uv1, _time);
185 tmpUIVertex.color = Color.Lerp(_a.color, _b.color, _time);
186
187 return tmpUIVertex;
188 }
189
193 protected UIVertex uiVertexBerp(UIVertex v_bottomLeft, UIVertex v_topLeft, UIVertex v_topRight, UIVertex v_bottomRight, float _xTime, float _yTime)
194 {
195 UIVertex topX = uiVertexLerp(v_topLeft, v_topRight, _xTime);
196 UIVertex bottomX = uiVertexLerp(v_bottomLeft, v_bottomRight, _xTime);
197 return uiVertexLerp(bottomX, topX, _yTime);
198 }
199
200 protected void tessellateQuad(List<UIVertex> _quads, int _thisQuadIdx)
201 {
202 UIVertex v_bottomLeft = _quads[_thisQuadIdx];
203 UIVertex v_topLeft = _quads[_thisQuadIdx + 1];
204 UIVertex v_topRight = _quads[_thisQuadIdx + 2];
205 UIVertex v_bottomRight = _quads[_thisQuadIdx + 3];
206
207 float quadSize = 100.0f / resolution;
208
209 int heightQuadEdgeNum = Mathf.Max(1, Mathf.CeilToInt((v_topLeft.position - v_bottomLeft.position).magnitude / quadSize));
210 int widthQuadEdgeNum = Mathf.Max(1, Mathf.CeilToInt((v_topRight.position - v_topLeft.position).magnitude / quadSize));
211
212 int quadIdx = 0;
213
214 for (int x = 0; x < widthQuadEdgeNum; x++)
215 {
216 for (int y = 0; y < heightQuadEdgeNum; y++, quadIdx++)
217 {
218 _quads.Add(new UIVertex());
219 _quads.Add(new UIVertex());
220 _quads.Add(new UIVertex());
221 _quads.Add(new UIVertex());
222
223 float xRatio = (float)x / widthQuadEdgeNum;
224 float yRatio = (float)y / heightQuadEdgeNum;
225 float xPlusOneRatio = (float)(x + 1) / widthQuadEdgeNum;
226 float yPlusOneRatio = (float)(y + 1) / heightQuadEdgeNum;
227
228 _quads[_quads.Count - 4] = uiVertexBerp(v_bottomLeft, v_topLeft, v_topRight, v_bottomRight, xRatio, yRatio);
229 _quads[_quads.Count - 3] = uiVertexBerp(v_bottomLeft, v_topLeft, v_topRight, v_bottomRight, xRatio, yPlusOneRatio);
230 _quads[_quads.Count - 2] = uiVertexBerp(v_bottomLeft, v_topLeft, v_topRight, v_bottomRight, xPlusOneRatio, yPlusOneRatio);
231 _quads[_quads.Count - 1] = uiVertexBerp(v_bottomLeft, v_topLeft, v_topRight, v_bottomRight, xPlusOneRatio, yRatio);
232
233 }
234 }
235 }
236
237 protected void tessellateGraphic(List<UIVertex> _verts)
238 {
239 for (int v = 0; v < _verts.Count; v += 6)
240 {
241 reuse_quads.Add(_verts[v]); // bottom left
242 reuse_quads.Add(_verts[v + 1]); // top left
243 reuse_quads.Add(_verts[v + 2]); // top right
244 // verts[3] is redundant, top right
245 reuse_quads.Add(_verts[v + 4]); // bottom right
246 // verts[5] is redundant, bottom left
247 }
248
249 int oriQuadNum = reuse_quads.Count / 4;
250 for (int q = 0; q < oriQuadNum; q++)
251 {
253 }
254
255 // remove original quads
256 reuse_quads.RemoveRange(0, oriQuadNum * 4);
257
258 _verts.Clear();
259
260 // process new quads and turn them into triangles
261 for (int q = 0; q < reuse_quads.Count; q += 4)
262 {
263 _verts.Add(reuse_quads[q]);
264 _verts.Add(reuse_quads[q + 1]);
265 _verts.Add(reuse_quads[q + 2]);
266 _verts.Add(reuse_quads[q + 2]);
267 _verts.Add(reuse_quads[q + 3]);
268 _verts.Add(reuse_quads[q]);
269 }
270
271 reuse_quads.Clear();
272 }
273
274 #endregion
275
276 // Events are for handling reoccurring function calls that react to the changes of the environment.
277 #region Events
278
279 protected override void OnRectTransformDimensionsChange()
280 {
281 if (isLockWithRatio)
282 {
284 }
285 }
286
287 public void Refresh()
288 {
289 ReportSet();
290
291 // we use local position as the true value. Ratio position follows it, so it should be updated when refresh
292
293 for (int c = 0; c < refCurves.Length; c++)
294 {
295
296 CUIBezierCurve curve = refCurves[c];
297
298 if (curve.ControlPoints != null)
299 {
300
301 Vector3[] controlPoints = curve.ControlPoints;
302
303 for (int p = 0; p < CUIBezierCurve.CubicBezierCurvePtNum; p++)
304 {
305
306#if UNITY_EDITOR
307 Undo.RecordObject(this, "Move Point");
308#endif
309
310 Vector3 ratioPoint = controlPoints[p];
311
312 ratioPoint.x = (ratioPoint.x + rectTrans.rect.width * rectTrans.pivot.x) / rectTrans.rect.width;
313 ratioPoint.y = (ratioPoint.y + rectTrans.rect.height * rectTrans.pivot.y) / rectTrans.rect.height;
314
315 refCurvesControlRatioPoints[c][p] = ratioPoint;
316 }
317 }
318 }
319
320 //uiText.SetAllDirty();
321 // need this to refresh the UI text, SetAllDirty does not seem to work for all cases
322 if (uiGraphic != null)
323 {
324 uiGraphic.enabled = false;
325 uiGraphic.enabled = true;
326 }
327 }
328
329 #endregion
330
331 // Methods that change the behaviour of the object.
332 #region Flash-Phase
333
334 protected override void Awake()
335 {
336 base.Awake();
338
339 }
340 protected override void OnEnable()
341 {
342 base.OnEnable();
344
345 }
346
347 #endregion
348
349 #region Configurations
350
354 public virtual void ReportSet()
355 {
356
357 if (rectTrans == null)
358 rectTrans = GetComponent<RectTransform>();
359
360 if (refCurves == null)
361 refCurves = new CUIBezierCurve[2];
362
363 bool isCurvesReady = true;
364
365 for (int c = 0; c < 2; c++)
366 {
367 isCurvesReady = isCurvesReady & refCurves[c] != null;
368 }
369
370 isCurvesReady = isCurvesReady & refCurves.Length == 2;
371
372 if (!isCurvesReady)
373 {
374 CUIBezierCurve[] curves = refCurves;
375
376 for (int c = 0; c < 2; c++)
377 {
378 if (refCurves[c] == null)
379 {
380 GameObject go = new GameObject();
381 go.transform.SetParent(transform);
382 go.transform.localPosition = Vector3.zero;
383 go.transform.localEulerAngles = Vector3.zero;
384
385 if (c == 0)
386 {
387 go.name = "BottomRefCurve";
388 }
389 else
390 {
391 go.name = "TopRefCurve";
392 }
393
394 curves[c] = go.AddComponent<CUIBezierCurve>();
395
396 }
397 else
398 {
399 curves[c] = refCurves[c];
400 }
401 curves[c].ReportSet();
402 }
403
404 refCurves = curves;
405 }
406
407 if (refCurvesControlRatioPoints == null)
408 {
410
411 for (int c = 0; c < refCurves.Length; c++)
412 {
413 {
414 refCurvesControlRatioPoints[c].array = new Vector3[refCurves[c].ControlPoints.Length];
415 }
416 }
417
419 Refresh();
420 }
421
422 for (int c = 0; c < 2; c++)
423 {
425 }
426 }
427
428 public void FixTextToRectTrans()
429 {
430 for (int c = 0; c < refCurves.Length; c++)
431 {
432 CUIBezierCurve curve = refCurves[c];
433
434 for (int p = 0; p < CUIBezierCurve.CubicBezierCurvePtNum; p++)
435 {
436 if (curve.ControlPoints != null)
437 {
438 Vector3[] controlPoints = curve.ControlPoints;
439
440 if (c == 0)
441 {
442 controlPoints[p].y = -rectTrans.rect.height * rectTrans.pivot.y;
443 }
444 else
445 {
446 controlPoints[p].y = rectTrans.rect.height - rectTrans.rect.height * rectTrans.pivot.y;
447 }
448
449 controlPoints[p].x = rectTrans.rect.width * p / (CUIBezierCurve.CubicBezierCurvePtNum - 1);
450 controlPoints[p].x -= rectTrans.rect.width * rectTrans.pivot.x;
451
452 controlPoints[p].z = 0;
453 }
454 }
455 }
456 }
457
459 {
460 // compute the position ratio of this rect transform in perspective of reference rect transform
461
462 Vector3 posDeltaBetweenBottomLeftCorner = rectTrans.localPosition;// Difference between pivot
463
464 posDeltaBetweenBottomLeftCorner.x += -rectTrans.rect.width * rectTrans.pivot.x + (refCUIGraphic.rectTrans.rect.width * refCUIGraphic.rectTrans.pivot.x);
465 posDeltaBetweenBottomLeftCorner.y += -rectTrans.rect.height * rectTrans.pivot.y + (refCUIGraphic.rectTrans.rect.height * refCUIGraphic.rectTrans.pivot.y);
466 //posDeltaBetweenBottomLeftCorner.z = rectTrans.localPosition.z;
467
468 Vector3 bottomLeftPosRatio = new Vector3(posDeltaBetweenBottomLeftCorner.x / refCUIGraphic.RectTrans.rect.width, posDeltaBetweenBottomLeftCorner.y / refCUIGraphic.RectTrans.rect.height, posDeltaBetweenBottomLeftCorner.z);
469 Vector3 topRightPosRatio = new Vector3((posDeltaBetweenBottomLeftCorner.x + rectTrans.rect.width) / refCUIGraphic.RectTrans.rect.width, (posDeltaBetweenBottomLeftCorner.y + rectTrans.rect.height) / refCUIGraphic.RectTrans.rect.height, posDeltaBetweenBottomLeftCorner.z);
470
471 refCurves[0].ControlPoints[0] = refCUIGraphic.GetBCurveSandwichSpacePoint(bottomLeftPosRatio.x, bottomLeftPosRatio.y) - rectTrans.localPosition;
472 refCurves[0].ControlPoints[3] = refCUIGraphic.GetBCurveSandwichSpacePoint(topRightPosRatio.x, bottomLeftPosRatio.y) - rectTrans.localPosition;
473
474 refCurves[1].ControlPoints[0] = refCUIGraphic.GetBCurveSandwichSpacePoint(bottomLeftPosRatio.x, topRightPosRatio.y) - rectTrans.localPosition;
475 refCurves[1].ControlPoints[3] = refCUIGraphic.GetBCurveSandwichSpacePoint(topRightPosRatio.x, topRightPosRatio.y) - rectTrans.localPosition;
476
477 // use two sample points from the reference curves to find the second and third controls points for this curves
478 for (int c = 0; c < refCurves.Length; c++)
479 {
480 CUIBezierCurve curve = refCurves[c];
481
482 float yTime = c == 0 ? bottomLeftPosRatio.y : topRightPosRatio.y;
483
484 Vector3 leftPoint = refCUIGraphic.GetBCurveSandwichSpacePoint(bottomLeftPosRatio.x, yTime);
485 Vector3 rightPoint = refCUIGraphic.GetBCurveSandwichSpacePoint(topRightPosRatio.x, yTime);
486
487 float quarter = 0.25f,
488 threeQuarter = 0.75f;
489
490 Vector3 quarterPoint = refCUIGraphic.GetBCurveSandwichSpacePoint((bottomLeftPosRatio.x * 0.75f + topRightPosRatio.x * 0.25f) / 1.0f, yTime);
491 Vector3 threeQuaterPoint = refCUIGraphic.GetBCurveSandwichSpacePoint((bottomLeftPosRatio.x * 0.25f + topRightPosRatio.x * 0.75f) / 1.0f, yTime);
492
493 float x_1 = 3 * threeQuarter * threeQuarter * quarter, // (1 - t)(1 - t)t
494 y_1 = 3 * threeQuarter * quarter * quarter,
495 x_2 = 3 * quarter * quarter * threeQuarter,
496 y_2 = 3 * quarter * threeQuarter * threeQuarter;
497
498 Vector3 contant_1 = quarterPoint - Mathf.Pow(threeQuarter, 3) * leftPoint - Mathf.Pow(quarter, 3) * rightPoint,
499 contant_2 = threeQuaterPoint - Mathf.Pow(quarter, 3) * leftPoint - Mathf.Pow(threeQuarter, 3) * rightPoint,
500 p1,
501 p2;
502
503 solveDoubleEquationWithVector(x_1, y_1, x_2, y_2, contant_1, contant_2, out p1, out p2);
504
505 curve.ControlPoints[1] = p1 - rectTrans.localPosition;
506 curve.ControlPoints[2] = p2 - rectTrans.localPosition;
507
508 }
509 // use tangent and start and end time to derive control point 2 and 3
510 }
511
512 public override void ModifyMesh(Mesh _mesh)
513 {
514
515 if (!IsActive())
516 return;
517
518 using (VertexHelper vh = new VertexHelper(_mesh))
519 {
520 ModifyMesh(vh);
521 vh.FillMesh(_mesh);
522 }
523
524 }
525
526 public override void ModifyMesh(VertexHelper _vh)
527 {
528
529 if (!IsActive())
530 return;
531
532 List<UIVertex> vertexList = new List<UIVertex>();
533 _vh.GetUIVertexStream(vertexList);
534
535 modifyVertices(vertexList);
536
537 _vh.Clear();
538 _vh.AddUIVertexTriangleStream(vertexList);
539 }
540
541 protected virtual void modifyVertices(List<UIVertex> _verts)
542 {
543 if (!IsActive())
544 return;
545
546 tessellateGraphic(_verts);
547
548 if (!isCurved)
549 {
550 return;
551 }
552
553 for (int index = 0; index < _verts.Count; index++)
554 {
555 var uiVertex = _verts[index];
556
557 // finding the horizontal ratio position (0.0 - 1.0) of a vertex
558 float horRatio = (uiVertex.position.x + rectTrans.rect.width * rectTrans.pivot.x) / rectTrans.rect.width;
559 float verRatio = (uiVertex.position.y + rectTrans.rect.height * rectTrans.pivot.y) / rectTrans.rect.height;
560
561 //Vector3 pos = Vector3.Lerp(refCurves[0].GetPoint(horRatio), refCurves[1].GetPoint(horRatio), verRatio);
562 Vector3 pos = GetBCurveSandwichSpacePoint(horRatio, verRatio);
563
564 uiVertex.position.x = pos.x;
565 uiVertex.position.y = pos.y;
566 uiVertex.position.z = pos.z;
567
568 _verts[index] = uiVertex;
569 }
570 }
571
573 {
574 ReportSet();
575
576 for (int c = 0; c < refCurves.Length; c++)
577 {
578 CUIBezierCurve curve = refCurves[c];
579
580#if UNITY_EDITOR
581 Undo.RecordObject(curve, "Move Rect");
582#endif
583
584 for (int p = 0; p < refCurves[c].ControlPoints.Length; p++)
585 {
586
587 Vector3 newPt = refCurvesControlRatioPoints[c][p];
588
589 newPt.x = newPt.x * rectTrans.rect.width - rectTrans.rect.width * rectTrans.pivot.x;
590 newPt.y = newPt.y * rectTrans.rect.height - rectTrans.rect.height * rectTrans.pivot.y;
591
592 curve.ControlPoints[p] = newPt;
593
594 }
595 }
596 }
597
598 #endregion
599
600 // Methods that serves other objects
601 #region Services
602
603 public Vector3 GetBCurveSandwichSpacePoint(float _xTime, float _yTime)
604 {
605 //return Vector3.Lerp(refCurves[0].GetPoint(_xTime), refCurves[1].GetPoint(_xTime), _yTime);
606 return refCurves[0].GetPoint(_xTime) * (1 - _yTime) + refCurves[1].GetPoint(_xTime) * _yTime; // use a custom made lerp so that the value is not clamped between 0 and 1
607 }
608
609 public Vector3 GetBCurveSandwichSpaceTangent(float _xTime, float _yTime)
610 {
611 return refCurves[0].GetTangent(_xTime) * (1 - _yTime) + refCurves[1].GetTangent(_xTime) * _yTime;
612 }
613
614 #endregion
615
616 }
617}
UnityEngine.Color Color
Definition: TestScript.cs:32
Assume to be a cubic bezier curve at the moment.
Vector3 GetPoint(float _time)
call this to get a sample
void tessellateQuad(List< UIVertex > _quads, int _thisQuadIdx)
Definition: CUIGraphic.cs:200
readonly static int topCurveIdx
Definition: CUIGraphic.cs:22
override void OnRectTransformDimensionsChange()
Definition: CUIGraphic.cs:279
Vector3_Array2D[] refCurvesControlRatioPoints
Definition: CUIGraphic.cs:108
bool isCurved
Describing the properties of this object.
Definition: CUIGraphic.cs:33
UIVertex uiVertexLerp(UIVertex _a, UIVertex _b, float _time)
Definition: CUIGraphic.cs:176
virtual void ReportSet()
Check, prepare and set everything needed.
Definition: CUIGraphic.cs:354
Vector3 GetBCurveSandwichSpacePoint(float _xTime, float _yTime)
Definition: CUIGraphic.cs:603
void solveDoubleEquationWithVector(float _x_1, float _y_1, float _x_2, float _y_2, Vector3 _constant_1, Vector3 _contant_2, out Vector3 _x, out Vector3 _y)
Definition: CUIGraphic.cs:148
Vector3_Array2D[] RefCurvesControlRatioPoints
Definition: CUIGraphic.cs:110
readonly static int bottomCurveIdx
Definition: CUIGraphic.cs:21
RectTransform rectTrans
Reference to other objects that are needed by this object.
Definition: CUIGraphic.cs:65
virtual void modifyVertices(List< UIVertex > _verts)
Definition: CUIGraphic.cs:541
void tessellateGraphic(List< UIVertex > _verts)
Definition: CUIGraphic.cs:237
UIVertex uiVertexBerp(UIVertex v_bottomLeft, UIVertex v_topLeft, UIVertex v_topRight, UIVertex v_bottomRight, float _xTime, float _yTime)
Bilinear Interpolation
Definition: CUIGraphic.cs:193
override void ModifyMesh(VertexHelper _vh)
Definition: CUIGraphic.cs:526
Vector3 GetBCurveSandwichSpaceTangent(float _xTime, float _yTime)
Definition: CUIGraphic.cs:609
override void ModifyMesh(Mesh _mesh)
Definition: CUIGraphic.cs:512
Credit Erdener Gonenc - @PixelEnvision.