using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace RainbowArt.CleanFlatUI { public class GradientModifier : BaseMeshEffect { public enum Style { Horizontal, Vertical, Radial, Diamond } public enum Blend { Override, Add, Multiply } [SerializeField] Style gradientStyle = Style.Horizontal; [SerializeField] Blend blend = Blend.Override; [SerializeField] bool moreVertices = true; [SerializeField] [Range(-1, 1)] float offset = 0f; [SerializeField] [Range(0.1f, 10)] float scale = 1f; [SerializeField] UnityEngine.Gradient gradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } }; List vertexList = new List(); List gradientKeysPos = new List(); List originIndices = new List(3); List starts = new List(3); List ends = new List(2); float[] cachedVertexPositions = new float[3]; public Style GradientStyle { get => gradientStyle; set { if(gradientStyle == value) { return; } gradientStyle = value; graphic.SetVerticesDirty(); } } public Blend BlendMode { get => blend; set { if(blend == value) { return; } blend = value; graphic.SetVerticesDirty(); } } public bool MoreVertices { get => moreVertices; set { if (moreVertices == value) { return; } moreVertices = value; graphic.SetVerticesDirty(); } } public float Offset { get => offset; set { if (offset == value) { return; } offset = Mathf.Clamp(value, -1f, 1f); graphic.SetVerticesDirty(); } } public float Scale { get => scale; set { if (scale == value) { return; } scale = Mathf.Clamp(value, 0.1f, 10f); graphic.SetVerticesDirty(); } } public UnityEngine.Gradient Gradient { get => gradient; set { gradient = value; graphic.SetVerticesDirty(); } } Color BlendColor(Color colorA, Color colorB) { switch (BlendMode) { case Blend.Add: { return colorA + colorB; } case Blend.Multiply: { return colorA * colorB; } default: return colorB; } } public override void ModifyMesh(VertexHelper helper) { if (!IsActive() || helper.currentVertCount == 0) { return; } switch (GradientStyle) { case Style.Horizontal: { ModifyMeshForHorizontal(helper); break; } case Style.Vertical: { ModifyMeshForVertical(helper); break; } case Style.Diamond: { ModifyMeshForDiamond(helper); break; } case Style.Radial: { ModifyMeshForRadial(helper); break; } } } void ModifyMeshForHorizontal(VertexHelper helper) { vertexList.Clear(); helper.GetUIVertexStream(vertexList); Rect bounds = GetVertsBounds(vertexList); float min = bounds.xMin; float w = bounds.width; float width = w == 0f ? 0f : 1f / w / Scale; float zoomOffset = (1 - (1 / Scale)) * 0.5f; float offset = (Offset * (1 - zoomOffset)) - zoomOffset; if (MoreVertices) { SplitTrianglesAtGradientKeys(vertexList, bounds, zoomOffset, helper); } UIVertex vertex = new UIVertex(); for (int i = 0; i < helper.currentVertCount; i++) { helper.PopulateUIVertex(ref vertex, i); vertex.color = BlendColor(vertex.color, Gradient.Evaluate((vertex.position.x - min) * width - offset)); helper.SetUIVertex(vertex, i); } } void ModifyMeshForVertical(VertexHelper helper) { vertexList.Clear(); helper.GetUIVertexStream(vertexList); Rect bounds = GetVertsBounds(vertexList); float min = bounds.yMin; float h = bounds.height; float height = h == 0f ? 0f : 1f / h / Scale; float zoomOffset = (1 - (1 / Scale)) * 0.5f; float offset = (Offset * (1 - zoomOffset)) - zoomOffset; if (MoreVertices) { SplitTrianglesAtGradientKeys(vertexList, bounds, zoomOffset, helper); } UIVertex vertex = new UIVertex(); for (int i = 0; i < helper.currentVertCount; i++) { helper.PopulateUIVertex(ref vertex, i); vertex.color = BlendColor(vertex.color, Gradient.Evaluate((vertex.position.y - min) * height - offset)); helper.SetUIVertex(vertex, i); } } void ModifyMeshForDiamond(VertexHelper helper) { vertexList.Clear(); helper.GetUIVertexStream(vertexList); int nCount = vertexList.Count; Rect bounds = GetVertsBounds(vertexList); float height = bounds.height == 0f ? 0f : 1f / bounds.height / Scale; float radius = bounds.center.y / 2f; Vector3 center = (Vector3.right + Vector3.up) * radius + Vector3.forward * vertexList[0].position.z; if (MoreVertices) { helper.Clear(); for (int i = 0; i < nCount; i++) { helper.AddVert(vertexList[i]); } UIVertex centralVertex = new UIVertex(); centralVertex.position = center; centralVertex.normal = vertexList[0].normal; centralVertex.uv0 = new Vector2(0.5f, 0.5f); centralVertex.color = Color.white; helper.AddVert(centralVertex); for (int i = 1; i < nCount; i++) { helper.AddTriangle(i - 1, i, nCount); } helper.AddTriangle(0, nCount - 1, nCount); } UIVertex vertex = new UIVertex(); for (int i = 0; i < helper.currentVertCount; i++) { helper.PopulateUIVertex(ref vertex, i); vertex.color = BlendColor(vertex.color, Gradient.Evaluate( Vector3.Distance(vertex.position, center) * height - Offset)); helper.SetUIVertex(vertex, i); } } void ModifyMeshForRadial(VertexHelper helper) { vertexList.Clear(); helper.GetUIVertexStream(vertexList); Rect bounds = GetVertsBounds(vertexList); float width = bounds.width == 0f ? 0f : 1f / bounds.width / Scale; float height = bounds.height == 0f ? 0f : 1f / bounds.height / Scale; if (MoreVertices) { helper.Clear(); float radiusX = bounds.width / 2f; float radiusY = bounds.height / 2f; UIVertex centralVertex = new UIVertex(); centralVertex.position = Vector3.right * bounds.center.x + Vector3.up * bounds.center.y + Vector3.forward * vertexList[0].position.z; centralVertex.normal = vertexList[0].normal; centralVertex.uv0 = new Vector2(0.5f, 0.5f); centralVertex.color = Color.white; int steps = 64; for (int i = 0; i < steps; i++) { UIVertex curVertex = new UIVertex(); float angle = (float)i * 360f / (float)steps; float cosX = Mathf.Cos(Mathf.Deg2Rad * angle); float cosY = Mathf.Sin(Mathf.Deg2Rad * angle); curVertex.position = Vector3.right * cosX * radiusX + Vector3.up * cosY * radiusY + Vector3.forward * vertexList[0].position.z; curVertex.normal = vertexList[0].normal; curVertex.uv0 = new Vector2((cosX + 1) * 0.5f, (cosY + 1) * 0.5f); curVertex.color = Color.white; helper.AddVert(curVertex); } helper.AddVert(centralVertex); for (int i = 1; i < steps; i++) { helper.AddTriangle(i - 1, i, steps); } helper.AddTriangle(0, steps - 1, steps); } UIVertex vertex = new UIVertex(); for (int i = 0; i < helper.currentVertCount; i++) { helper.PopulateUIVertex(ref vertex, i); vertex.color = BlendColor(vertex.color, Gradient.Evaluate( Mathf.Sqrt( Mathf.Pow(Mathf.Abs(vertex.position.x - bounds.center.x) * width, 2f) + Mathf.Pow(Mathf.Abs(vertex.position.y - bounds.center.y) * height, 2f)) * 2f - Offset)); helper.SetUIVertex(vertex, i); } } Rect GetVertsBounds(List vertices) { float left = vertices[0].position.x; float right = left; float bottom = vertices[0].position.y; float top = bottom; for (int i = vertices.Count - 1; i >= 1; --i) { float x = vertices[i].position.x; float y = vertices[i].position.y; if (x > right) { right = x; } else if (x < left) { left = x; } if (y > top) { top = y; } else if (y < bottom) { bottom = y; } } return new Rect(left, bottom, right - left, top - bottom); } void SplitOneTriangle(List vertexList, VertexHelper helper,int triangleIndex) { int i = triangleIndex * 3; float[] positions = GetVertexPositions(vertexList, i); originIndices.Clear(); starts.Clear(); ends.Clear(); for (int s = 0; s < gradientKeysPos.Count; s++) { int initialCount = helper.currentVertCount; bool hadEnds = ends.Count > 0; bool earlyStart = false; for (int p = 0; p < 3; p++) { if (!originIndices.Contains(p) && positions[p] < gradientKeysPos[s]) { int p1 = (p + 1) % 3; var start = vertexList[p + i]; if (positions[p1] > gradientKeysPos[s]) { originIndices.Insert(0, p); starts.Insert(0, start); earlyStart = true; } else { originIndices.Add(p); starts.Add(start); } } } if (originIndices.Count == 0) { continue; } if (originIndices.Count == 3) { break; } foreach (var start in starts) { helper.AddVert(start); } ends.Clear(); foreach (int index in originIndices) { int oppositeIndex = (index + 1) % 3; if (positions[oppositeIndex] < gradientKeysPos[s]) { oppositeIndex = (oppositeIndex + 1) % 3; } ends.Add(CreateSplitVertex(vertexList[index + i], vertexList[oppositeIndex + i], gradientKeysPos[s])); } if (ends.Count == 1) { int oppositeIndex = (originIndices[0] + 2) % 3; ends.Add(CreateSplitVertex(vertexList[originIndices[0] + i], vertexList[oppositeIndex + i], gradientKeysPos[s])); } foreach (var end in ends) { helper.AddVert(end); } if (hadEnds) { helper.AddTriangle(initialCount - 2, initialCount, initialCount + 1); helper.AddTriangle(initialCount - 2, initialCount + 1, initialCount - 1); if (starts.Count > 0) { if (earlyStart) { helper.AddTriangle(initialCount - 2, initialCount + 3, initialCount); } else { helper.AddTriangle(initialCount + 1, initialCount + 3, initialCount - 1); } } } else { int vertexCount = helper.currentVertCount; helper.AddTriangle(initialCount, vertexCount - 2, vertexCount - 1); if (starts.Count > 1) { helper.AddTriangle(initialCount, vertexCount - 1, initialCount + 1); } } starts.Clear(); } if (ends.Count > 0) { if (starts.Count == 0) { for (int p = 0; p < 3; p++) { if (!originIndices.Contains(p) && positions[p] > gradientKeysPos[gradientKeysPos.Count - 1]) { int p1 = (p + 1) % 3; UIVertex end = vertexList[p + i]; if (positions[p1] > gradientKeysPos[gradientKeysPos.Count - 1]) { starts.Insert(0, end); } else { starts.Add(end); } } } } foreach (var start in starts) { helper.AddVert(start); } int vertexCount = helper.currentVertCount; if (starts.Count > 1) { helper.AddTriangle(vertexCount - 4, vertexCount - 2, vertexCount - 1); helper.AddTriangle(vertexCount - 4, vertexCount - 1, vertexCount - 3); } else if (starts.Count > 0) { helper.AddTriangle(vertexCount - 3, vertexCount - 1, vertexCount - 2); } } else { helper.AddVert(vertexList[i]); helper.AddVert(vertexList[i + 1]); helper.AddVert(vertexList[i + 2]); int vertexCount = helper.currentVertCount; helper.AddTriangle(vertexCount - 3, vertexCount - 2, vertexCount - 1); } } void SplitTrianglesAtGradientKeys(List vertexList, Rect bounds, float zoomOffset, VertexHelper helper) { FindGradientKeysPos(zoomOffset, bounds); if (gradientKeysPos.Count == 0) { return; } helper.Clear(); int count = vertexList.Count/3; for (int i = 0; i < count; ++i) { SplitOneTriangle(vertexList, helper, i); } } float[] GetVertexPositions(List vertexList, int index) { if (GradientStyle == Style.Horizontal) { cachedVertexPositions[0] = vertexList[index].position.x; cachedVertexPositions[1] = vertexList[index + 1].position.x; cachedVertexPositions[2] = vertexList[index + 2].position.x; } else { cachedVertexPositions[0] = vertexList[index].position.y; cachedVertexPositions[1] = vertexList[index + 1].position.y; cachedVertexPositions[2] = vertexList[index + 2].position.y; } return cachedVertexPositions; } void FindGradientKeysPos(float zoomOffset, Rect bounds) { gradientKeysPos.Clear(); var offset = Offset * (1 - zoomOffset); var startBoundary = zoomOffset - offset; var endBoundary = (1 - zoomOffset) - offset; foreach (var color in Gradient.colorKeys) { if (color.time >= endBoundary) { break; } if (color.time > startBoundary) { gradientKeysPos.Add((color.time - startBoundary) * Scale); } } foreach (var alpha in Gradient.alphaKeys) { if (alpha.time >= endBoundary) { break; } if (alpha.time > startBoundary) { gradientKeysPos.Add((alpha.time - startBoundary) * Scale); } } float min = bounds.xMin; float size = bounds.width; if (GradientStyle == Style.Vertical) { min = bounds.yMin; size = bounds.height; } gradientKeysPos.Sort(); for (int i = 0; i < gradientKeysPos.Count; i++) { gradientKeysPos[i] = (gradientKeysPos[i] * size) + min; if (i > 0 && Math.Abs(gradientKeysPos[i] - gradientKeysPos[i - 1]) < 2) { gradientKeysPos.RemoveAt(i); --i; } } } UIVertex CreateSplitVertex(UIVertex vertex1, UIVertex vertex2, float stop) { if (GradientStyle == Style.Horizontal) { float sx = vertex1.position.x - stop; float dx = vertex1.position.x - vertex2.position.x; float dy = vertex1.position.y - vertex2.position.y; float uvx = vertex1.uv0.x - vertex2.uv0.x; float uvy = vertex1.uv0.y - vertex2.uv0.y; float ratio = sx / dx; float splitY = vertex1.position.y - (dy * ratio); UIVertex splitVertex = new UIVertex(); splitVertex.position = new Vector3(stop, splitY, vertex1.position.z); splitVertex.normal = vertex1.normal; splitVertex.uv0 = new Vector2(vertex1.uv0.x - (uvx * ratio), vertex1.uv0.y - (uvy * ratio)); splitVertex.color = Color.white; return splitVertex; } else { float sy = vertex1.position.y - stop; float dy = vertex1.position.y - vertex2.position.y; float dx = vertex1.position.x - vertex2.position.x; float uvx = vertex1.uv0.x - vertex2.uv0.x; float uvy = vertex1.uv0.y - vertex2.uv0.y; float ratio = sy / dy; float splitX = vertex1.position.x - (dx * ratio); UIVertex splitVertex = new UIVertex(); splitVertex.position = new Vector3(splitX, stop, vertex1.position.z); splitVertex.normal = vertex1.normal; splitVertex.uv0 = new Vector2(vertex1.uv0.x - (uvx * ratio), vertex1.uv0.y - (uvy * ratio)); splitVertex.color = Color.white; return splitVertex; } } } }