CurvedUITMP.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System.Collections.Generic;
  4. #if CURVEDUI_TMP || TMP_PRESENT
  5. using TMPro;
  6. #endif
  7. //To use this class you have to add CURVEDUI_TMP to your define symbols. You can do it in project settings.
  8. //To learn how to do it visit http://docs.unity3d.com/Manual/PlatformDependentCompilation.html and search for "Platform Custom Defines"
  9. namespace CurvedUI
  10. {
  11. [ExecuteInEditMode]
  12. public class CurvedUITMP : MonoBehaviour
  13. {
  14. #if CURVEDUI_TMP || TMP_PRESENT
  15. //internal
  16. private CurvedUIVertexEffect crvdVE;
  17. private TextMeshProUGUI tmpText;
  18. private CurvedUISettings mySettings;
  19. private List<UIVertex> m_UIVerts = new List<UIVertex>();
  20. private UIVertex m_tempVertex;
  21. private CurvedUITMPSubmesh m_tempSubMsh;
  22. private Vector2 savedSize;
  23. private Vector3 savedUp;
  24. private Vector3 savedPos;
  25. private Vector3 savedLocalScale;
  26. private Vector3 savedGlobalScale;
  27. private List<CurvedUITMPSubmesh> subMeshes = new List<CurvedUITMPSubmesh>();
  28. //flags
  29. public bool Dirty = false; // set this to true to force mesh update.
  30. private bool curvingRequired = false;
  31. private bool tesselationRequired = false;
  32. private bool quitting = false;
  33. //mesh data
  34. private Vector3[] vertices;
  35. //These are commented here and throught the script,
  36. //cause CurvedUI operates only on vertex positions,
  37. //but left here for future-proofing against some TMP features.
  38. //private Color32[] colors32;
  39. //private Vector2[] uv;
  40. //private Vector2[] uv2;
  41. //private Vector2[] uv3;
  42. //private Vector2[] uv4;
  43. //private Vector3[] normals;
  44. //private Vector4[] tangents;
  45. //private int[] indices;
  46. #region LIFECYCLE
  47. void Start()
  48. {
  49. if (mySettings == null)
  50. mySettings = GetComponentInParent<CurvedUISettings>();
  51. }
  52. void OnEnable()
  53. {
  54. FindTMP();
  55. if (tmpText)
  56. {
  57. tmpText.RegisterDirtyMaterialCallback(TesselationRequiredCallback);
  58. TMPro_EventManager.TEXT_CHANGED_EVENT.Add(TMPTextChangedCallback);
  59. tmpText.SetText(tmpText.text);
  60. }
  61. #if UNITY_EDITOR
  62. if (!Application.isPlaying)
  63. UnityEditor.EditorApplication.update += LateUpdate;
  64. #endif
  65. }
  66. void OnDisable()
  67. {
  68. #if UNITY_EDITOR
  69. if (!Application.isPlaying)
  70. UnityEditor.EditorApplication.update -= LateUpdate;
  71. #endif
  72. if (tmpText)
  73. {
  74. tmpText.UnregisterDirtyMaterialCallback(TesselationRequiredCallback);
  75. TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(TMPTextChangedCallback);
  76. }
  77. }
  78. void OnDestroy()
  79. {
  80. quitting = true;
  81. }
  82. void LateUpdate()
  83. {
  84. //if we're missing stuff, find it
  85. if (!tmpText) FindTMP();
  86. if (mySettings == null) return;
  87. //Edit Mesh on TextMeshPro component
  88. if (tmpText && !quitting)
  89. {
  90. if (ShouldTesselate())
  91. tesselationRequired = true;
  92. if (Dirty || tesselationRequired || (curvingRequired && !Application.isPlaying))
  93. {
  94. if (mySettings == null)
  95. {
  96. enabled = false;
  97. return;
  98. }
  99. //Get the flat vertices from TMP object.
  100. //store a copy of flat UIVertices for later so we dont have to retrieve the Mesh every framee.
  101. tmpText.renderMode = TMPro.TextRenderFlags.Render;
  102. tmpText.ForceMeshUpdate(true);
  103. CreateUIVertexList(tmpText.mesh);
  104. //Tesselate and Curve the flat UIVertices stored in Vertex Helper
  105. crvdVE.ModifyTMPMesh(ref m_UIVerts);
  106. //fill curved vertices back to TMP mesh
  107. FillMeshWithUIVertexList(tmpText.mesh, m_UIVerts);
  108. //cleanup
  109. tmpText.renderMode = TMPro.TextRenderFlags.DontRender;
  110. //save current data
  111. savedLocalScale = mySettings.transform.localScale;
  112. savedGlobalScale = mySettings.transform.lossyScale;
  113. savedSize = (transform as RectTransform).rect.size;
  114. savedUp = mySettings.transform.worldToLocalMatrix.MultiplyVector(transform.up);
  115. savedPos = mySettings.transform.worldToLocalMatrix.MultiplyPoint3x4(transform.position);
  116. //reset flags
  117. tesselationRequired = false;
  118. curvingRequired = false;
  119. Dirty = false;
  120. //prompt submeshes to update
  121. FindSubmeshes();
  122. foreach (CurvedUITMPSubmesh mesh in subMeshes)
  123. mesh.UpdateSubmesh(true, false);
  124. }
  125. //Upload mesh to TMP Object's renderer
  126. if(tmpText.text.Length > 0)
  127. tmpText.canvasRenderer.SetMesh(tmpText.mesh);
  128. else
  129. tmpText.canvasRenderer.Clear();
  130. }
  131. }
  132. #endregion
  133. #region UIVERTEX MANAGEMENT
  134. void CreateUIVertexList(Mesh mesh)
  135. {
  136. //trim if too long list
  137. if (mesh.vertexCount < m_UIVerts.Count)
  138. m_UIVerts.RemoveRange(mesh.vertexCount, m_UIVerts.Count - mesh.vertexCount);
  139. //extract mesh data
  140. vertices = mesh.vertices;
  141. //colors32 = mesh.colors32;
  142. //uv = mesh.uv;
  143. //uv2 = mesh.uv2;
  144. //uv3 = mesh.uv3;
  145. //uv4 = mesh.uv4;
  146. //normals = mesh.normals;
  147. //tangents = mesh.tangents;
  148. for (int i = 0; i < mesh.vertexCount; i++)
  149. {
  150. //add if list too short
  151. if (m_UIVerts.Count <= i)
  152. {
  153. m_tempVertex = new UIVertex();
  154. GetUIVertexFromMesh(ref m_tempVertex, i);
  155. m_UIVerts.Add(m_tempVertex);
  156. }
  157. else //modify
  158. {
  159. m_tempVertex = m_UIVerts[i];
  160. GetUIVertexFromMesh(ref m_tempVertex, i);
  161. m_UIVerts[i] = m_tempVertex;
  162. }
  163. }
  164. //indices = mesh.GetIndices(0);
  165. }
  166. void GetUIVertexFromMesh(ref UIVertex vert, int i)
  167. {
  168. vert.position = vertices[i];
  169. //vert.color = colors32[i];
  170. //vert.uv0 = uv[i];
  171. //vert.uv1 = uv2.Length > i ? uv2[i] : Vector2.zero;
  172. //vert.uv2 = uv3.Length > i ? uv3[i] : Vector2.zero;
  173. //vert.uv3 = uv4.Length > i ? uv4[i] : Vector2.zero;
  174. //vert.normal = normals[i];
  175. //vert.tangent = tangents[i];
  176. }
  177. void FillMeshWithUIVertexList(Mesh mesh, List<UIVertex> list)
  178. {
  179. if (list.Count >= 65536)
  180. {
  181. Debug.LogError("CURVEDUI: Unity UI Mesh can not have more than 65536 vertices. Remove some UI elements or lower quality.");
  182. return;
  183. }
  184. for (int i = 0; i < list.Count; i++)
  185. {
  186. vertices[i] = list[i].position;
  187. //colors32[i] = list[i].color;
  188. //uv[i] = list[i].uv0;
  189. //if (uv2.Length < i) uv2[i] = list[i].uv1;
  190. ////if (uv3.Length < i) uv3[i] = list[i].uv2;
  191. ////if (uv4.Length < i) uv4[i] = list[i].uv3;
  192. //normals[i] = list[i].normal;
  193. //tangents[i] = list[i].tangent;
  194. }
  195. //Fill mesh with data
  196. mesh.vertices = vertices;
  197. //mesh.colors32 = colors32;
  198. //mesh.uv = uv;
  199. //mesh.uv2 = uv2;
  200. ////mesh.uv3 = uv3;
  201. ////mesh.uv4 = uv4;
  202. //mesh.normals = normals;
  203. //mesh.tangents = tangents;
  204. //mesh.SetTriangles(indices, 0);
  205. mesh.RecalculateBounds();
  206. }
  207. #endregion
  208. #region PRIVATE
  209. void FindTMP()
  210. {
  211. if (this.GetComponent<TextMeshProUGUI>() != null)
  212. {
  213. tmpText = this.gameObject.GetComponent<TextMeshProUGUI>();
  214. crvdVE = this.gameObject.GetComponent<CurvedUIVertexEffect>();
  215. mySettings = GetComponentInParent<CurvedUISettings>();
  216. transform.hasChanged = false;
  217. FindSubmeshes();
  218. }
  219. }
  220. void FindSubmeshes()
  221. {
  222. foreach (TMP_SubMeshUI sub in GetComponentsInChildren<TMP_SubMeshUI>())
  223. {
  224. m_tempSubMsh = sub.gameObject.AddComponentIfMissing<CurvedUITMPSubmesh>();
  225. if (!subMeshes.Contains(m_tempSubMsh))
  226. subMeshes.Add(m_tempSubMsh);
  227. }
  228. }
  229. bool ShouldTesselate()
  230. {
  231. if (savedSize != (transform as RectTransform).rect.size)
  232. {
  233. //Debug.Log("size changed");
  234. return true;
  235. }
  236. else if (savedLocalScale != mySettings.transform.localScale)
  237. {
  238. //Debug.Log("local scale changed");
  239. return true;
  240. }
  241. else if (savedGlobalScale != mySettings.transform.lossyScale)
  242. {
  243. //Debug.Log("global scale changed");
  244. return true;
  245. }
  246. else if (!savedUp.AlmostEqual(mySettings.transform.worldToLocalMatrix.MultiplyVector(transform.up)))
  247. {
  248. // Debug.Log("up changed");
  249. return true;
  250. }
  251. Vector3 testedPos = mySettings.transform.worldToLocalMatrix.MultiplyPoint3x4(transform.position);
  252. if (!savedPos.AlmostEqual(testedPos))
  253. {
  254. //we dont have to curve vertices if we only moved the object vertically in a cylinder.
  255. if (mySettings.Shape != CurvedUISettings.CurvedUIShape.CYLINDER || Mathf.Pow(testedPos.x - savedPos.x, 2) > 0.00001 || Mathf.Pow(testedPos.z - savedPos.z, 2) > 0.00001)
  256. {
  257. //Debug.Log("pos changed");
  258. return true;
  259. }
  260. }
  261. return false;
  262. }
  263. #endregion
  264. #region EVENTS AND CALLBACKS
  265. void TMPTextChangedCallback(object obj)
  266. {
  267. if (obj != (object)tmpText) return;
  268. tesselationRequired = true;
  269. //Debug.Log("tmp prop changed on "+this.gameObject.name, this.gameObject);
  270. }
  271. void TesselationRequiredCallback()
  272. {
  273. tesselationRequired = true;
  274. curvingRequired = true;
  275. }
  276. #endregion
  277. #endif
  278. }
  279. }