using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace CurvedUI { public class CurvedUIVertexEffect : BaseMeshEffect { [Tooltip("Check to skip tesselation pass on this object. CurvedUI will not create additional vertices to make this object have a smoother curve. Checking this can solve some issues if you create your own procedural mesh for this object. Default false.")] public bool DoNotTesselate; private Canvas myCanvas; private CurvedUISettings mySettings; private Graphic myGraphic; private Text myText; private bool m_tesselationRequired = true; private bool curvingRequired = true; private bool TransformMisaligned; private Matrix4x4 CanvasToWorld; private Matrix4x4 CanvasToLocal; private Matrix4x4 MyToWorld; private Matrix4x4 MyToLocal; private List m_tesselatedVerts = new List(); private List m_curvedVerts = new List(); private List m_vertsInQuads = new List(); private UIVertex m_ret; private UIVertex[] m_quad = new UIVertex[4]; private float[] m_weights = new float[4]; [HideInInspector] [SerializeField] private Vector3 savedPos; [HideInInspector] [SerializeField] private Vector3 savedUp; [HideInInspector] [SerializeField] private Vector2 savedRectSize; [HideInInspector] [SerializeField] private Color savedColor; [HideInInspector] [SerializeField] private Vector2 savedTextUV0; [HideInInspector] [SerializeField] private float savedFill; private Vector2 _uv0; private Vector2 _uv1; private Vector3 _pos; private bool tesselationRequired { get { return m_tesselationRequired; } set { m_tesselationRequired = value; } } public bool TesselationRequired { get { return tesselationRequired; } set { tesselationRequired = value; } } public bool CurvingRequired { get { return curvingRequired; } set { curvingRequired = value; } } protected override void Awake() { base.Awake(); myGraphic = GetComponent(); myText = GetComponent(); } protected override void OnEnable() { FindParentSettings(); if ((bool)myGraphic) { myGraphic.RegisterDirtyMaterialCallback(TesselationRequiredCallback); myGraphic.SetVerticesDirty(); } if ((bool)myText) { myText.RegisterDirtyVerticesCallback(TesselationRequiredCallback); Font.textureRebuilt += FontTextureRebuiltCallback; } } protected override void Start() { base.Start(); if ((bool)myCanvas && (bool)GetComponent()) { Vector3 vector = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(base.transform.position); RectTransform rectTransform = myCanvas.transform as RectTransform; if (vector.x.Abs() > rectTransform.rect.width / 2f || vector.y.Abs() > rectTransform.rect.height / 2f) { Debug.LogWarning("CurvedUI: " + GetComponent().GetType().Name + " \"" + base.gameObject.name + "\" is outside of the canvas. It will not be interactable. Move it inside the canvas rectangle (white border in scene view) for it to work.", base.gameObject); } if (vector.z.Abs() > 0.1f) { Debug.LogWarning("CurvedUI: The Z position of \"" + base.gameObject.name + "\" " + GetComponent().GetType().Name + ", or one of its parents is not 0 in relation to the canvas. The interactions may not be accurate.", base.gameObject); } } } protected override void OnDisable() { if ((bool)myGraphic) { myGraphic.UnregisterDirtyMaterialCallback(TesselationRequiredCallback); } if ((bool)myText) { myText.UnregisterDirtyVerticesCallback(TesselationRequiredCallback); Font.textureRebuilt -= FontTextureRebuiltCallback; } } private void TesselationRequiredCallback() { tesselationRequired = true; } private void FontTextureRebuiltCallback(Font fontie) { if (myText.font == fontie) { tesselationRequired = true; } } private void LateUpdate() { if (!tesselationRequired) { if ((base.transform as RectTransform).rect.size != savedRectSize) { tesselationRequired = true; } else if (myGraphic != null) { if (myGraphic.color != savedColor) { tesselationRequired = true; savedColor = myGraphic.color; } else if (myGraphic is Image && (myGraphic as Image).fillAmount != savedFill) { tesselationRequired = true; savedFill = (myGraphic as Image).fillAmount; } } } if (!tesselationRequired && !curvingRequired) { Vector3 a = mySettings.transform.worldToLocalMatrix.MultiplyPoint3x4(base.transform.position); if (!a.AlmostEqual(savedPos) && (mySettings.Shape != CurvedUISettings.CurvedUIShape.CYLINDER || (double)Mathf.Pow(a.x - savedPos.x, 2f) > 1E-05 || (double)Mathf.Pow(a.z - savedPos.z, 2f) > 1E-05)) { savedPos = a; curvingRequired = true; } Vector3 normalized = mySettings.transform.worldToLocalMatrix.MultiplyVector(base.transform.up).normalized; if (!savedUp.AlmostEqual(normalized, 0.0001)) { bool flag = normalized.AlmostEqual(Vector3.up.normalized); bool flag2 = savedUp.AlmostEqual(Vector3.up.normalized); if ((!flag && flag2) || (flag && !flag2)) { tesselationRequired = true; } savedUp = normalized; curvingRequired = true; } } if ((bool)myGraphic && (tesselationRequired || curvingRequired)) { myGraphic.SetVerticesDirty(); } } public override void ModifyMesh(VertexHelper vh) { if (!ShouldModify()) { return; } CheckTextFontMaterial(); if (tesselationRequired || !Application.isPlaying) { if (m_tesselatedVerts == null) { m_tesselatedVerts = new List(); } else { m_tesselatedVerts.Clear(); } vh.GetUIVertexStream(m_tesselatedVerts); TesselateGeometry(m_tesselatedVerts); savedRectSize = (base.transform as RectTransform).rect.size; tesselationRequired = false; curvingRequired = true; } if (curvingRequired) { CanvasToWorld = myCanvas.transform.localToWorldMatrix; CanvasToLocal = myCanvas.transform.worldToLocalMatrix; MyToWorld = base.transform.localToWorldMatrix; MyToLocal = base.transform.worldToLocalMatrix; if (m_curvedVerts == null) { m_curvedVerts = new List(); } if (m_curvedVerts.Count == m_tesselatedVerts.Count) { for (int i = 0; i < m_curvedVerts.Count; i++) { m_curvedVerts[i] = CurveVertex(m_tesselatedVerts[i], mySettings.Angle, mySettings.GetCyllinderRadiusInCanvasSpace(), (myCanvas.transform as RectTransform).rect.size); } } else { m_curvedVerts.Clear(); for (int j = 0; j < m_tesselatedVerts.Count; j++) { m_curvedVerts.Add(CurveVertex(m_tesselatedVerts[j], mySettings.Angle, mySettings.GetCyllinderRadiusInCanvasSpace(), (myCanvas.transform as RectTransform).rect.size)); } } curvingRequired = false; } vh.Clear(); if (m_curvedVerts.Count % 4 == 0) { for (int k = 0; k < m_curvedVerts.Count; k += 4) { for (int l = 0; l < 4; l++) { m_quad[l] = m_curvedVerts[k + l]; } vh.AddUIVertexQuad(m_quad); } } else { vh.AddUIVertexTriangleStream(m_curvedVerts); } } public void ModifyTMPMesh(ref List vertexList) { if (!ShouldModify()) { return; } CheckTextFontMaterial(); tesselationRequired = false; curvingRequired = true; if (curvingRequired) { CanvasToWorld = myCanvas.transform.localToWorldMatrix; CanvasToLocal = myCanvas.transform.worldToLocalMatrix; MyToWorld = base.transform.localToWorldMatrix; MyToLocal = base.transform.worldToLocalMatrix; for (int i = 0; i < vertexList.Count; i++) { vertexList[i] = CurveVertex(vertexList[i], mySettings.Angle, mySettings.GetCyllinderRadiusInCanvasSpace(), (myCanvas.transform as RectTransform).rect.size); } curvingRequired = false; } } private bool ShouldModify() { if (!IsActive()) { return false; } if (mySettings == null) { FindParentSettings(); } if (mySettings == null || !mySettings.enabled || mySettings.Angle == 1) { return false; } return true; } private void CheckTextFontMaterial() { if ((bool)myText && myText.cachedTextGenerator.verts.Count > 0 && myText.cachedTextGenerator.verts[0].uv0 != savedTextUV0) { savedTextUV0 = myText.cachedTextGenerator.verts[0].uv0; tesselationRequired = true; } } public CurvedUISettings FindParentSettings(bool forceNew = false) { if (mySettings == null || forceNew) { mySettings = GetComponentInParent(); if (mySettings == null) { return null; } myCanvas = mySettings.GetComponent(); } return mySettings; } private UIVertex CurveVertex(UIVertex input, float cylinder_angle, float radius, Vector2 canvasSize) { Vector3 position = input.position; position = CanvasToLocal.MultiplyPoint3x4(MyToWorld.MultiplyPoint3x4(position)); if (mySettings.Shape == CurvedUISettings.CurvedUIShape.CYLINDER && mySettings.Angle != 0) { float f = position.x / canvasSize.x * cylinder_angle * ((float)Math.PI / 180f); radius += position.z; position.x = Mathf.Sin(f) * radius; position.z += Mathf.Cos(f) * radius - radius; } else if (mySettings.Shape == CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL && mySettings.Angle != 0) { float f2 = position.y / canvasSize.y * cylinder_angle * ((float)Math.PI / 180f); radius += position.z; position.y = Mathf.Sin(f2) * radius; position.z += Mathf.Cos(f2) * radius - radius; } else if (mySettings.Shape == CurvedUISettings.CurvedUIShape.RING) { float num = 0f; float num2 = position.y.Remap(canvasSize.y * 0.5f * (float)(mySettings.RingFlipVertical ? 1 : (-1)), (0f - canvasSize.y) * 0.5f * (float)(mySettings.RingFlipVertical ? 1 : (-1)), (float)mySettings.RingExternalDiameter * (1f - mySettings.RingFill) * 0.5f, (float)mySettings.RingExternalDiameter * 0.5f); float f3 = (position.x / canvasSize.x).Remap(-0.5f, 0.5f, (float)Math.PI / 2f, cylinder_angle * ((float)Math.PI / 180f) + (float)Math.PI / 2f) - num; position.x = num2 * Mathf.Cos(f3); position.y = num2 * Mathf.Sin(f3); } else if (mySettings.Shape == CurvedUISettings.CurvedUIShape.SPHERE && mySettings.Angle != 0) { float num3 = mySettings.VerticalAngle; float num4 = 0f - position.z; if (mySettings.PreserveAspect) { num3 = cylinder_angle * (canvasSize.y / canvasSize.x); } else { radius = canvasSize.x / 2f; if (num3 == 0f) { return input; } } float num5 = (position.x / canvasSize.x).Remap(-0.5f, 0.5f, (180f - cylinder_angle) / 2f - 90f, 180f - (180f - cylinder_angle) / 2f - 90f); num5 *= (float)Math.PI / 180f; float num6 = (position.y / canvasSize.y).Remap(-0.5f, 0.5f, (180f - num3) / 2f, 180f - (180f - num3) / 2f); num6 *= (float)Math.PI / 180f; position.z = Mathf.Sin(num6) * Mathf.Cos(num5) * (radius + num4); position.y = (0f - (radius + num4)) * Mathf.Cos(num6); position.x = Mathf.Sin(num6) * Mathf.Sin(num5) * (radius + num4); if (mySettings.PreserveAspect) { position.z -= radius; } } input.position = MyToLocal.MultiplyPoint3x4(CanvasToWorld.MultiplyPoint3x4(position)); return input; } private void TesselateGeometry(List verts) { Vector2 tesslationSize = mySettings.GetTesslationSize(); TransformMisaligned = !savedUp.AlmostEqual(Vector3.up.normalized); TrisToQuads(verts); if (myText == null && !DoNotTesselate) { int count = verts.Count; for (int i = 0; i < count; i += 4) { ModifyQuad(verts, i, tesslationSize); } verts.RemoveRange(0, count); } } private void ModifyQuad(List verts, int vertexIndex, Vector2 requiredSize) { for (int i = 0; i < 4; i++) { m_quad[i] = verts[vertexIndex + i]; } Vector3 vector = m_quad[2].position - m_quad[1].position; Vector3 vector2 = m_quad[1].position - m_quad[0].position; if (myGraphic != null && myGraphic is Image && (myGraphic as Image).type == Image.Type.Filled) { vector = ((!(vector.x > (m_quad[3].position - m_quad[0].position).x)) ? (m_quad[3].position - m_quad[0].position) : vector); vector2 = ((!(vector2.y > (m_quad[2].position - m_quad[3].position).y)) ? (m_quad[2].position - m_quad[3].position) : vector2); } int num = 1; int num2 = 1; if (TransformMisaligned || mySettings.Shape == CurvedUISettings.CurvedUIShape.SPHERE || mySettings.Shape == CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL) { num2 = Mathf.CeilToInt(vector2.magnitude * (1f / Mathf.Max(0.0001f, requiredSize.y))); } if (TransformMisaligned || mySettings.Shape != CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL) { num = Mathf.CeilToInt(vector.magnitude * (1f / Mathf.Max(0.0001f, requiredSize.x))); } bool flag = false; bool flag2 = false; float y = 0f; for (int j = 0; j < num2 || !flag; j++) { flag = true; float num3 = ((float)j + 1f) / (float)num2; float x = 0f; for (int k = 0; k < num || !flag2; k++) { flag2 = true; float num4 = ((float)k + 1f) / (float)num; verts.Add(TesselateQuad(x, y)); verts.Add(TesselateQuad(x, num3)); verts.Add(TesselateQuad(num4, num3)); verts.Add(TesselateQuad(num4, y)); x = num4; } y = num3; } } private void TrisToQuads(List verts) { int count = verts.Count; m_vertsInQuads.Clear(); for (int i = 0; i < count; i += 6) { m_vertsInQuads.Add(verts[i]); m_vertsInQuads.Add(verts[i + 1]); m_vertsInQuads.Add(verts[i + 2]); m_vertsInQuads.Add(verts[i + 4]); } verts.AddRange(m_vertsInQuads); verts.RemoveRange(0, count); } private UIVertex TesselateQuad(float x, float y) { m_weights[0] = (1f - x) * (1f - y); m_weights[1] = (1f - x) * y; m_weights[2] = x * y; m_weights[3] = x * (1f - y); _uv0 = (_uv1 = Vector2.zero); _pos = Vector3.zero; for (int i = 0; i < 4; i++) { _uv0 += m_quad[i].uv0 * m_weights[i]; _uv1 += m_quad[i].uv1 * m_weights[i]; _pos += m_quad[i].position * m_weights[i]; } m_ret.position = _pos; m_ret.color = m_quad[0].color; m_ret.uv0 = _uv0; m_ret.uv1 = _uv1; m_ret.normal = m_quad[0].normal; m_ret.tangent = m_quad[0].tangent; return m_ret; } public void SetDirty() { TesselationRequired = true; } } }