Looks good, merging

Merged in david_gileadi/unity-ui-extensions/gradient-2-improvements (pull request #15)

Support complex linear gradients, gradient zoom, and a switch of whether to modify vertices
pull/413/head
David Gileadi 2017-08-13 20:10:38 +00:00 committed by Simon Jackson
commit 2abb2130fc
1 changed files with 465 additions and 193 deletions

View File

@ -4,258 +4,530 @@
/// -Uses Unity's Gradient class to define the color /// -Uses Unity's Gradient class to define the color
/// -Offset is now limited to -1,1 /// -Offset is now limited to -1,1
/// -Multiple color blend modes /// -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.
/// </summary> /// </summary>
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace UnityEngine.UI.Extensions namespace UnityEngine.UI.Extensions
{ {
[AddComponentMenu("UI/Effects/Extensions/Gradient2")] [AddComponentMenu("UI/Effects/Extensions/Gradient2")]
public class Gradient2 : BaseMeshEffect { public class Gradient2 : BaseMeshEffect
[SerializeField] {
Type _gradientType; [SerializeField]
Type _gradientType;
[SerializeField] [SerializeField]
Blend _blendMode = Blend.Multiply; Blend _blendMode = Blend.Multiply;
[SerializeField] [SerializeField]
[Range(-1, 1)] [Tooltip("Add vertices to display complex gradients. Turn off if your shape is already very complex, like text.")]
float _offset = 0f; bool _modifyVertices = true;
[SerializeField] [SerializeField]
UnityEngine.Gradient _effectGradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } }; [Range(-1, 1)]
float _offset = 0f;
#region Properties [SerializeField]
public Blend BlendMode { [Range(0.1f, 10)]
get { return _blendMode; } float _zoom = 1f;
set
{
_blendMode = value;
graphic.SetVerticesDirty();
}
}
public UnityEngine.Gradient EffectGradient { [SerializeField]
get { return _effectGradient; } UnityEngine.Gradient _effectGradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } };
set
{
_effectGradient = value;
graphic.SetVerticesDirty();
}
}
public Type GradientType { #region Properties
get { return _gradientType; } public Blend BlendMode
set {
{ get { return _blendMode; }
_gradientType = value; set
graphic.SetVerticesDirty(); {
} _blendMode = value;
} graphic.SetVerticesDirty();
}
}
public float Offset { public UnityEngine.Gradient EffectGradient
get { return _offset; } {
set get { return _effectGradient; }
{ set
_offset = value; {
graphic.SetVerticesDirty(); _effectGradient = value;
} graphic.SetVerticesDirty();
} }
#endregion }
public override void ModifyMesh(VertexHelper helper) { public Type GradientType
if(!IsActive() || helper.currentVertCount == 0) {
return; get { return _gradientType; }
set
{
_gradientType = value;
graphic.SetVerticesDirty();
}
}
List<UIVertex> _vertexList = new List<UIVertex>(); 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; public float Zoom
switch(GradientType) { {
case Type.Horizontal: { get { return _zoom; }
float left = _vertexList[0].position.x; set
float right = _vertexList[0].position.x; {
float x = 0f; _zoom = value;
graphic.SetVerticesDirty();
}
}
#endregion
for(int i = nCount - 1; i >= 1; --i) { public override void ModifyMesh(VertexHelper helper)
x = _vertexList[i].position.x; {
if (!IsActive() || helper.currentVertCount == 0)
return;
if(x > right) right = x; List<UIVertex> _vertexList = new List<UIVertex>();
else if(x < left) left = x;
}
float width = 1f / (right - left); helper.GetUIVertexStream(_vertexList);
UIVertex vertex = new UIVertex();
for(int i = 0; i < helper.currentVertCount; i++) { int nCount = _vertexList.Count;
helper.PopulateUIVertex(ref vertex, i); switch (GradientType)
{
case Type.Horizontal:
case Type.Vertical:
{
Rect bounds = GetBounds(_vertexList);
float min = bounds.xMin;
float w = bounds.width;
Func<UIVertex, float> 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); float width = 1f / w / Zoom;
} float zoomOffset = ((w * Zoom) - w) * width * 0.5f;
} float offset = Offset - zoomOffset;
break;
case Type.Vertical: { if (ModifyVertices)
float bottom = _vertexList[0].position.y; {
float top = _vertexList[0].position.y; SplitTrianglesAtGradientStops(_vertexList, bounds, zoomOffset, helper);
float y = 0f; }
for(int i = nCount - 1; i >= 1; --i) { UIVertex vertex = new UIVertex();
y = _vertexList[i].position.y; 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; case Type.Diamond:
else if(y < bottom) bottom = y; {
} Rect bounds = GetBounds(_vertexList);
float height = 1f / (top - bottom); float height = 1f / bounds.height / Zoom;
UIVertex vertex = new UIVertex(); 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++) { if (ModifyVertices)
helper.PopulateUIVertex(ref vertex, i); {
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); for (int i = 1; i < nCount; i++) helper.AddTriangle(i - 1, i, nCount);
} helper.AddTriangle(0, nCount - 1, nCount);
} }
break;
case Type.Diamond: { UIVertex vertex = new UIVertex();
float bottom = _vertexList[0].position.y;
float top = _vertexList[0].position.y;
float y = 0f;
for(int i = nCount - 1; i >= 1; --i) { for (int i = 0; i < helper.currentVertCount; i++)
y = _vertexList[i].position.y; {
helper.PopulateUIVertex(ref vertex, i);
if(y > top) top = y; vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate(
else if(y < bottom) bottom = y; Vector3.Distance(vertex.position, center) * height - Offset));
}
float height = 1f / (top - bottom); helper.SetUIVertex(vertex, i);
}
}
break;
helper.Clear(); case Type.Radial:
for (int i = 0; i < nCount; i++) helper.AddVert(_vertexList[i]); {
Rect bounds = GetBounds(_vertexList);
float center = (bottom + top) / 2f; float width = 1f / bounds.width / Zoom;
UIVertex centralVertex = new UIVertex(); float height = 1f / bounds.height / Zoom;
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);
for (int i = 1; i < nCount; i++) helper.AddTriangle(i-1,i,nCount); if (ModifyVertices)
helper.AddTriangle(0,nCount-1,nCount); {
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++) { int steps = 64;
helper.PopulateUIVertex(ref vertex, i); 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( curVertex.position = Vector3.right * cosX * radiusX + Vector3.up * cosY * radiusY + Vector3.forward * _vertexList[0].position.z;
Vector3.Distance(vertex.position, centralVertex.position) * height - Offset)); curVertex.normal = _vertexList[0].normal;
curVertex.uv0 = new Vector2((cosX + 1) * 0.5f, (cosY + 1) * 0.5f);
helper.SetUIVertex(vertex, i); curVertex.color = Color.white;
} helper.AddVert(curVertex);
} }
break;
case Type.Radial: { helper.AddVert(centralVertex);
float left = _vertexList[0].position.x; for (int i = 1; i < steps; i++) helper.AddTriangle(i - 1, i, steps);
float right = _vertexList[0].position.x; helper.AddTriangle(0, steps - 1, steps);
float bottom = _vertexList[0].position.y; }
float top = _vertexList[0].position.y;
float x = 0f; UIVertex vertex = new UIVertex();
float y = 0f;
for(int i = nCount - 1; i >= 1; --i) { for (int i = 0; i < helper.currentVertCount; i++)
x = _vertexList[i].position.x; {
helper.PopulateUIVertex(ref vertex, i);
if(x > right) right = x; vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate(
else if(x < left) left = x; 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; Rect GetBounds(List<UIVertex> vertices)
else if(y < bottom) bottom = y; {
} float left = vertices[0].position.x;
float right = left;
float bottom = vertices[0].position.y;
float top = bottom;
float width = 1f / (right - left); for (int i = vertices.Count - 1; i >= 1; --i)
float height = 1f / (top - bottom); {
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; if (y > top) top = y;
float centerY = (bottom + top) / 2f; else if (y < bottom) bottom = y;
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;
int steps = 64; return new Rect(left, bottom, right - left, top - bottom);
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;
curVertex.position = Vector3.right * curX + Vector3.up * curY + Vector3.forward * _vertexList[0].position.z; void SplitTrianglesAtGradientStops(List<UIVertex> _vertexList, Rect bounds, float zoomOffset, VertexHelper helper)
curVertex.normal = _vertexList[0].normal; {
curVertex.color = Color.white; List<float> stops = FindStops(zoomOffset, bounds);
helper.AddVert(curVertex); 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<int> originIndices = new List<int>(3);
List<UIVertex> starts = new List<UIVertex>(3);
List<UIVertex> ends = new List<UIVertex>(2);
for (int i = 1; i < steps; i++) helper.AddTriangle(i-1,i,steps); for (int s = 0; s < stops.Count; s++)
helper.AddTriangle(0,steps-1,steps); {
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++) { // bail if all before or after the stop
helper.PopulateUIVertex(ref vertex, i); if (originIndices.Count == 0)
continue;
if (originIndices.Count == 3)
break;
vertex.color = BlendColor(vertex.color, EffectGradient.Evaluate( // report any start vertices
Mathf.Sqrt( foreach (var start in starts)
Mathf.Pow(Mathf.Abs(vertex.position.x - centerX) * width, 2f) + helper.AddVert(start);
Mathf.Pow(Mathf.Abs(vertex.position.y - centerY) * height, 2f)) * 2f - Offset));
helper.SetUIVertex(vertex, i); // make two ends, splitting at the stop
} ends.Clear();
} foreach (int index in originIndices)
break; {
} 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) { // report end vertices
switch(BlendMode) { foreach (var end in ends)
default: return colorB; helper.AddVert(end);
case Blend.Add: return colorA + colorB;
case Blend.Multiply: return colorA * colorB;
}
}
public enum Type { // make triangles
Horizontal, if (hadEnds)
Vertical, {
Radial, helper.AddTriangle(initialCount - 2, initialCount, initialCount + 1);
Diamond 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 { starts.Clear();
Override, }
Add,
Multiply // 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<UIVertex> _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<float> FindStops(float zoomOffset, Rect bounds)
{
List<float> stops = new List<float>();
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
}
}
} }