From 48acafbb85626e4c7f7202c43700d4c6a57e87e6 Mon Sep 17 00:00:00 2001 From: David Gileadi Date: Sat, 12 Aug 2017 15:41:24 -0700 Subject: [PATCH] Support complex linear gradients, gradient zoom, and a switch of whether to modify vertices --- Scripts/Effects/Gradient2.cs | 658 +++++++++++++++++++++++++---------- 1 file changed, 465 insertions(+), 193 deletions(-) diff --git a/Scripts/Effects/Gradient2.cs b/Scripts/Effects/Gradient2.cs index 7bb3d06..fbca272 100644 --- a/Scripts/Effects/Gradient2.cs +++ b/Scripts/Effects/Gradient2.cs @@ -4,258 +4,530 @@ /// -Uses Unity's Gradient class to define the color /// -Offset is now limited to -1,1 /// -Multiple color blend modes -/// -/// Remember that the colors are applied per-vertex so if you have multiple points on your gradient where the color changes and there aren't enough vertices, you won't see all of the colors. +/// +/// Remember that for radial and diamond gradients, colors are applied per-vertex so if you have multiple points on your gradient where the color changes and there aren't enough vertices, you won't see all of the colors. /// +using System; using System.Collections.Generic; namespace UnityEngine.UI.Extensions { [AddComponentMenu("UI/Effects/Extensions/Gradient2")] - public class Gradient2 : BaseMeshEffect { - [SerializeField] - Type _gradientType; + public class Gradient2 : BaseMeshEffect + { + [SerializeField] + Type _gradientType; - [SerializeField] - Blend _blendMode = Blend.Multiply; + [SerializeField] + Blend _blendMode = Blend.Multiply; - [SerializeField] - [Range(-1, 1)] - float _offset = 0f; + [SerializeField] + [Tooltip("Add vertices to display complex gradients. Turn off if your shape is already very complex, like text.")] + bool _modifyVertices = true; - [SerializeField] - UnityEngine.Gradient _effectGradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } }; + [SerializeField] + [Range(-1, 1)] + float _offset = 0f; - #region Properties - public Blend BlendMode { - get { return _blendMode; } - set - { - _blendMode = value; - graphic.SetVerticesDirty(); - } - } + [SerializeField] + [Range(0.1f, 10)] + float _zoom = 1f; - public UnityEngine.Gradient EffectGradient { - get { return _effectGradient; } - set - { - _effectGradient = value; - graphic.SetVerticesDirty(); - } - } + [SerializeField] + UnityEngine.Gradient _effectGradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } }; - public Type GradientType { - get { return _gradientType; } - set - { - _gradientType = value; - graphic.SetVerticesDirty(); - } - } + #region Properties + public Blend BlendMode + { + get { return _blendMode; } + set + { + _blendMode = value; + graphic.SetVerticesDirty(); + } + } - public float Offset { - get { return _offset; } - set - { - _offset = value; - graphic.SetVerticesDirty(); - } - } - #endregion + public UnityEngine.Gradient EffectGradient + { + get { return _effectGradient; } + set + { + _effectGradient = value; + graphic.SetVerticesDirty(); + } + } - public override void ModifyMesh(VertexHelper helper) { - if(!IsActive() || helper.currentVertCount == 0) - return; + public Type GradientType + { + get { return _gradientType; } + set + { + _gradientType = value; + graphic.SetVerticesDirty(); + } + } - List _vertexList = new List(); + public bool ModifyVertices + { + get { return _modifyVertices; } + set + { + _modifyVertices = value; + graphic.SetVerticesDirty(); + } + } - helper.GetUIVertexStream(_vertexList); + public float Offset + { + get { return _offset; } + set + { + _offset = value; + graphic.SetVerticesDirty(); + } + } - int nCount = _vertexList.Count; - switch(GradientType) { - case Type.Horizontal: { - float left = _vertexList[0].position.x; - float right = _vertexList[0].position.x; - float x = 0f; + public float Zoom + { + get { return _zoom; } + set + { + _zoom = value; + graphic.SetVerticesDirty(); + } + } + #endregion - for(int i = nCount - 1; i >= 1; --i) { - x = _vertexList[i].position.x; + public override void ModifyMesh(VertexHelper helper) + { + if (!IsActive() || helper.currentVertCount == 0) + return; - if(x > right) right = x; - else if(x < left) left = x; - } + List _vertexList = new List(); - float width = 1f / (right - left); - UIVertex vertex = new UIVertex(); + helper.GetUIVertexStream(_vertexList); - for(int i = 0; i < helper.currentVertCount; i++) { - helper.PopulateUIVertex(ref vertex, i); + int nCount = _vertexList.Count; + switch (GradientType) + { + case Type.Horizontal: + case Type.Vertical: + { + Rect bounds = GetBounds(_vertexList); + float min = bounds.xMin; + float w = bounds.width; + Func GetPosition = v => v.position.x; - vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate((vertex.position.x - left) * width - Offset)); + if (GradientType == Type.Vertical) + { + min = bounds.yMin; + w = bounds.height; + GetPosition = v => v.position.y; + } - helper.SetUIVertex(vertex, i); - } - } - break; + float width = 1f / w / Zoom; + float zoomOffset = ((w * Zoom) - w) * width * 0.5f; + float offset = Offset - zoomOffset; - case Type.Vertical: { - float bottom = _vertexList[0].position.y; - float top = _vertexList[0].position.y; - float y = 0f; + if (ModifyVertices) + { + SplitTrianglesAtGradientStops(_vertexList, bounds, zoomOffset, helper); + } - for(int i = nCount - 1; i >= 1; --i) { - y = _vertexList[i].position.y; + UIVertex vertex = new UIVertex(); + for (int i = 0; i < helper.currentVertCount; i++) + { + helper.PopulateUIVertex(ref vertex, i); + vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate((GetPosition(vertex) - min) * width - offset)); + helper.SetUIVertex(vertex, i); + } + } + break; - if(y > top) top = y; - else if(y < bottom) bottom = y; - } + case Type.Diamond: + { + Rect bounds = GetBounds(_vertexList); - float height = 1f / (top - bottom); - UIVertex vertex = new UIVertex(); + float height = 1f / bounds.height / Zoom; + float radius = bounds.center.y / 2f; + Vector3 center = (Vector3.right + Vector3.up) * radius + Vector3.forward * _vertexList[0].position.z; - for(int i = 0; i < helper.currentVertCount; i++) { - helper.PopulateUIVertex(ref vertex, i); + if (ModifyVertices) + { + helper.Clear(); + for (int i = 0; i < nCount; i++) helper.AddVert(_vertexList[i]); - vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate((vertex.position.y - bottom) * height - Offset)); + 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); - helper.SetUIVertex(vertex, i); - } - } - break; + for (int i = 1; i < nCount; i++) helper.AddTriangle(i - 1, i, nCount); + helper.AddTriangle(0, nCount - 1, nCount); + } - case Type.Diamond: { - - float bottom = _vertexList[0].position.y; - float top = _vertexList[0].position.y; - float y = 0f; + UIVertex vertex = new UIVertex(); - for(int i = nCount - 1; i >= 1; --i) { - y = _vertexList[i].position.y; + for (int i = 0; i < helper.currentVertCount; i++) + { + helper.PopulateUIVertex(ref vertex, i); - if(y > top) top = y; - else if(y < bottom) bottom = y; - } + vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate( + Vector3.Distance(vertex.position, center) * height - Offset)); - float height = 1f / (top - bottom); + helper.SetUIVertex(vertex, i); + } + } + break; - helper.Clear(); - for (int i = 0; i < nCount; i++) helper.AddVert(_vertexList[i]); + case Type.Radial: + { + Rect bounds = GetBounds(_vertexList); - float center = (bottom + top) / 2f; - UIVertex centralVertex = new UIVertex(); - centralVertex.position = (Vector3.right + Vector3.up) * center + Vector3.forward * _vertexList[0].position.z; - centralVertex.normal = _vertexList[0].normal; - centralVertex.color = Color.white; - helper.AddVert(centralVertex); + float width = 1f / bounds.width / Zoom; + float height = 1f / bounds.height / Zoom; - for (int i = 1; i < nCount; i++) helper.AddTriangle(i-1,i,nCount); - helper.AddTriangle(0,nCount-1,nCount); + if (ModifyVertices) + { + helper.Clear(); - UIVertex vertex = new UIVertex(); + 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; - for(int i = 0; i < helper.currentVertCount; i++) { - helper.PopulateUIVertex(ref vertex, i); + 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); - vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate( - Vector3.Distance(vertex.position, centralVertex.position) * height - Offset)); - - helper.SetUIVertex(vertex, i); - } - } - break; + 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); + } - case Type.Radial: { + helper.AddVert(centralVertex); - float left = _vertexList[0].position.x; - float right = _vertexList[0].position.x; - float bottom = _vertexList[0].position.y; - float top = _vertexList[0].position.y; + for (int i = 1; i < steps; i++) helper.AddTriangle(i - 1, i, steps); + helper.AddTriangle(0, steps - 1, steps); + } - float x = 0f; - float y = 0f; + UIVertex vertex = new UIVertex(); - for(int i = nCount - 1; i >= 1; --i) { - x = _vertexList[i].position.x; + for (int i = 0; i < helper.currentVertCount; i++) + { + helper.PopulateUIVertex(ref vertex, i); - if(x > right) right = x; - else if(x < left) left = x; + vertex.color = BlendColor(vertex.color, EffectGradient.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)); - y = _vertexList[i].position.y; + helper.SetUIVertex(vertex, i); + } + } + break; + } + } - if(y > top) top = y; - else if(y < bottom) bottom = y; - } + Rect GetBounds(List vertices) + { + float left = vertices[0].position.x; + float right = left; + float bottom = vertices[0].position.y; + float top = bottom; - float width = 1f / (right - left); - float height = 1f / (top - bottom); + for (int i = vertices.Count - 1; i >= 1; --i) + { + float x = vertices[i].position.x; + float y = vertices[i].position.y; - helper.Clear(); + if (x > right) right = x; + else if (x < left) left = x; - float centerX = (right + left) / 2f; - float centerY = (bottom + top) / 2f; - float radiusX = (right - left) / 2f; - float radiusY = (top - bottom) / 2f; - UIVertex centralVertex = new UIVertex(); - centralVertex.position = Vector3.right * centerX + Vector3.up * centerY + Vector3.forward * _vertexList[0].position.z; - centralVertex.normal = _vertexList[0].normal; - centralVertex.color = Color.white; + if (y > top) top = y; + else if (y < bottom) bottom = y; + } - int steps = 64; - for (int i = 0; i < steps; i++) - { - UIVertex curVertex = new UIVertex(); - float angle = (float)i * 360f / (float)steps; - float curX = Mathf.Cos(Mathf.Deg2Rad * angle) * radiusX; - float curY = Mathf.Sin(Mathf.Deg2Rad * angle) * radiusY; + return new Rect(left, bottom, right - left, top - bottom); + } - curVertex.position = Vector3.right * curX + Vector3.up * curY + Vector3.forward * _vertexList[0].position.z; - curVertex.normal = _vertexList[0].normal; - curVertex.color = Color.white; - helper.AddVert(curVertex); - } + void SplitTrianglesAtGradientStops(List _vertexList, Rect bounds, float zoomOffset, VertexHelper helper) + { + List stops = FindStops(zoomOffset, bounds); + if (stops.Count > 0) + { + helper.Clear(); - helper.AddVert(centralVertex); + int nCount = _vertexList.Count; + for (int i = 0; i < nCount; i += 3) + { + float[] positions = GetPositions(_vertexList, i); + List originIndices = new List(3); + List starts = new List(3); + List ends = new List(2); - for (int i = 1; i < steps; i++) helper.AddTriangle(i-1,i,steps); - helper.AddTriangle(0,steps-1,steps); + for (int s = 0; s < stops.Count; s++) + { + int initialCount = helper.currentVertCount; + bool hadEnds = ends.Count > 0; + bool earlyStart = false; - UIVertex vertex = new UIVertex(); + // find any start vertices for this stop + for (int p = 0; p < 3; p++) + { + if (!originIndices.Contains(p) && positions[p] < stops[s]) + { + // make sure the first index crosses the stop + int p1 = (p + 1) % 3; + var start = _vertexList[p + i]; + if (positions[p1] > stops[s]) + { + originIndices.Insert(0, p); + starts.Insert(0, start); + earlyStart = true; + } + else + { + originIndices.Add(p); + starts.Add(start); + } + } + } - for(int i = 0; i < helper.currentVertCount; i++) { - helper.PopulateUIVertex(ref vertex, i); + // bail if all before or after the stop + if (originIndices.Count == 0) + continue; + if (originIndices.Count == 3) + break; - vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate( - Mathf.Sqrt( - Mathf.Pow(Mathf.Abs(vertex.position.x - centerX) * width, 2f) + - Mathf.Pow(Mathf.Abs(vertex.position.y - centerY) * height, 2f)) * 2f - Offset)); + // report any start vertices + foreach (var start in starts) + helper.AddVert(start); - helper.SetUIVertex(vertex, i); - } - } - break; - } - } + // make two ends, splitting at the stop + ends.Clear(); + foreach (int index in originIndices) + { + int oppositeIndex = (index + 1) % 3; + if (positions[oppositeIndex] < stops[s]) + oppositeIndex = (oppositeIndex + 1) % 3; + ends.Add(CreateSplitVertex(_vertexList[index + i], _vertexList[oppositeIndex + i], stops[s])); + } + if (ends.Count == 1) + { + int oppositeIndex = (originIndices[0] + 2) % 3; + ends.Add(CreateSplitVertex(_vertexList[originIndices[0] + i], _vertexList[oppositeIndex + i], stops[s])); + } - Color BlendColor(Color colorA, Color colorB) { - switch(BlendMode) { - default: return colorB; - case Blend.Add: return colorA + colorB; - case Blend.Multiply: return colorA * colorB; - } - } + // report end vertices + foreach (var end in ends) + helper.AddVert(end); - public enum Type { - Horizontal, - Vertical, - Radial, - Diamond - } + // make triangles + 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); + } - public enum Blend { - Override, - Add, - Multiply - } - } + starts.Clear(); + } + + // clean up after looping through gradient stops + if (ends.Count > 0) + { + // find any final vertices after the gradient stops + if (starts.Count == 0) + { + for (int p = 0; p < 3; p++) + { + if (!originIndices.Contains(p) && positions[p] > stops[stops.Count - 1]) + { + int p1 = (p + 1) % 3; + UIVertex end = _vertexList[p + i]; + if (positions[p1] > stops[stops.Count - 1]) + starts.Insert(0, end); + else + starts.Add(end); + } + } + } + + // report final vertices + foreach (var start in starts) + helper.AddVert(start); + + // make final triangle(s) + 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 + { + // if the triangle wasn't split, add it as-is + 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); + } + } + } + } + + float[] GetPositions(List _vertexList, int index) + { + float[] positions = new float[3]; + if (GradientType == Type.Horizontal) + { + positions[0] = _vertexList[index].position.x; + positions[1] = _vertexList[index + 1].position.x; + positions[2] = _vertexList[index + 2].position.x; + } + else + { + positions[0] = _vertexList[index].position.y; + positions[1] = _vertexList[index + 1].position.y; + positions[2] = _vertexList[index + 2].position.y; + } + return positions; + } + + List FindStops(float zoomOffset, Rect bounds) + { + List stops = new List(); + var scaledOffset = Offset * Zoom; + foreach (var color in EffectGradient.colorKeys) + { + if (color.time >= (1 - (zoomOffset + scaledOffset))) + break; + if (color.time > (zoomOffset - scaledOffset)) + stops.Add(((color.time - 0.5f) * Zoom) + 0.5f + scaledOffset); + } + foreach (var alpha in EffectGradient.alphaKeys) + { + if (alpha.time >= (1 - (zoomOffset + scaledOffset))) + break; + if (alpha.time > (zoomOffset - scaledOffset)) + stops.Add(((alpha.time - 0.5f) * Zoom) + 0.5f + scaledOffset); + } + + float min = bounds.xMin; + float size = bounds.width; + if (GradientType == Type.Vertical) + { + min = bounds.yMin; + size = bounds.height; + } + + stops.Sort(); + for (int i = 0; i < stops.Count; i++) + { + stops[i] = (stops[i] * size) + min; + + if (i > 0 && Math.Abs(stops[i] - stops[i - 1]) < 2) + { + stops.RemoveAt(i); + --i; + } + } + + return stops; + } + + UIVertex CreateSplitVertex(UIVertex vertex1, UIVertex vertex2, float stop) + { + if (GradientType == Type.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; + } + } + + Color BlendColor(Color colorA, Color colorB) + { + switch (BlendMode) + { + default: return colorB; + case Blend.Add: return colorA + colorB; + case Blend.Multiply: return colorA * colorB; + } + } + + public enum Type + { + Horizontal, + Vertical, + Radial, + Diamond + } + + public enum Blend + { + Override, + Add, + Multiply + } + } } \ No newline at end of file