522 lines
14 KiB
C#
522 lines
14 KiB
C#
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<UIVertex> m_tesselatedVerts = new List<UIVertex>();
|
|
|
|
private List<UIVertex> m_curvedVerts = new List<UIVertex>();
|
|
|
|
private List<UIVertex> m_vertsInQuads = new List<UIVertex>();
|
|
|
|
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<Graphic>();
|
|
myText = GetComponent<Text>();
|
|
}
|
|
|
|
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<Selectable>())
|
|
{
|
|
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<Selectable>().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<Selectable>().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<UIVertex>();
|
|
}
|
|
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<UIVertex>();
|
|
}
|
|
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<UIVertex> 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<CurvedUISettings>();
|
|
if (mySettings == null)
|
|
{
|
|
return null;
|
|
}
|
|
myCanvas = mySettings.GetComponent<Canvas>();
|
|
}
|
|
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<UIVertex> 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<UIVertex> 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<UIVertex> 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;
|
|
}
|
|
}
|
|
}
|