using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
namespace TyphoonUI
{
///
/// 三色渐变
///
public class UIGradientThreeColor : BaseMeshEffect
{
//方形
private static Rect Square = new Rect(-1, -1, 2, 2);
[Range(0, 360)] public float angle = 0;
public Color TopColor = new Color(1, 0, 0);
public Color MiddleColor = new Color(0, 1, 0);
public Color BottomColor = new Color(0, 0, 1);
private List _intersection2DPoints = new List();
private List _remap2DTo3D = new List();
private List _uiVertices = new List();
private List _vertexData = new List();
private List _triangle = new List();
private Rect _rect;
private struct VertexData
{
public UIVertex Vertex;
public int Index;
public Remap2DTo3DPoint RemapData;
}
///
/// 采样2D到3D点
///
private struct Remap2DTo3DPoint
{
public int Index; //点索引
public Vector2 Point2D; //2D点
public int RemapStartIndex; //采样点开始索引
public int RemapEndIndex; //采样点结束索引
public float RemapProcess; //采样进度
public float ProjectionOffset; //投影偏移
}
//线
protected struct Line
{
public Vector2 Start;
public Vector2 End;
public Line(Vector2 start, Vector2 end)
{
Start = start;
End = end;
}
public float yMin => Mathf.Min(Start.y, End.y);
public float yMax => Mathf.Max(Start.y, End.y);
public float xMin => Mathf.Min(Start.x, End.x);
public float xMax => Mathf.Max(Start.x, End.x);
}
public override void ModifyMesh(VertexHelper vh)
{
if (vh.currentVertCount <= 0)
{
return;
}
_rect = graphic.rectTransform.rect;
//清空交点数组
_remap2DTo3D.Clear();
_intersection2DPoints.Clear();
//汇总所有顶点
var dir = Vector2.zero + AngleToDirection(angle);
var p1 = Vector2.zero + dir * 2;
var p2 = Vector2.zero - dir * 2;
//计算中线交点
var point1 = Square.IntersectionWithRayFromCenter(p1);
var point2 = Square.IntersectionWithRayFromCenter(p2);
if (Mathf.Approximately(angle, 45))
{
point1 = new Vector2(1, 1);
point2 = new Vector2(-1, -1);
}
else if (Mathf.Approximately(angle, 225))
{
point1 = new Vector2(-1, -1);
point2 = new Vector2(1, 1);
}
else if (Mathf.Approximately(angle, 135))
{
point1 = new Vector2(-1, 1);
point2 = new Vector2(1, -1);
}
else if (Mathf.Approximately(angle, 315))
{
point1 = new Vector2(1, -1);
point2 = new Vector2(-1, 1);
}
_intersection2DPoints.Add(point1);
_intersection2DPoints.Add(point2);
//计算颜色垂直线
Vector2 perpendicular = Vector2.Perpendicular(point1 - point2).normalized;
//计算平行四边形剩余交点
var hasCutLine = TryCalculateCutLines(point1, point2, out var cutLines);
if (hasCutLine)
{
foreach (var cutLine in cutLines)
{
//剔除边界点
if (!Mathf.Approximately(Mathf.Abs(cutLine.Start.x), Mathf.Abs(cutLine.Start.y)))
{
_intersection2DPoints.Add(cutLine.Start);
}
if (!Mathf.Approximately(Mathf.Abs(cutLine.End.x), Mathf.Abs(cutLine.End.y)))
{
_intersection2DPoints.Add(cutLine.End);
}
}
}
else
{
//加入两组空点
_intersection2DPoints.Add(point1);
_intersection2DPoints.Add(point2);
}
//左下点
_remap2DTo3D.Add(new Remap2DTo3DPoint()
{
Index = 0, Point2D = new Vector2(-1, -1), RemapStartIndex = 0, RemapEndIndex = 1, RemapProcess = 0f,
ProjectionOffset = CalculateProjectOffset(perpendicular, new Vector2(-1, -1))
});
//左上
_remap2DTo3D.Add(new Remap2DTo3DPoint()
{
Index = 1, Point2D = new Vector2(-1, 1), RemapStartIndex = 1, RemapEndIndex = 2, RemapProcess = 0f,
ProjectionOffset = CalculateProjectOffset(perpendicular, new Vector2(-1, 1))
});
//右上
_remap2DTo3D.Add(new Remap2DTo3DPoint()
{
Index = 2, Point2D = new Vector2(1, 1), RemapStartIndex = 2, RemapEndIndex = 3, RemapProcess = 0f,
ProjectionOffset = CalculateProjectOffset(perpendicular, new Vector2(1, 1))
});
//右下
_remap2DTo3D.Add(new Remap2DTo3DPoint()
{
Index = 3, Point2D = new Vector2(1, -1), RemapStartIndex = 3, RemapEndIndex = 0, RemapProcess = 0f,
ProjectionOffset = CalculateProjectOffset(perpendicular, new Vector2(1, -1))
});
//交点
var index = 4;
foreach (var element in _intersection2DPoints)
{
CalculateEdgeRemapProcess(element, Square, out var start, out var end, out var process);
_remap2DTo3D.Add(new Remap2DTo3DPoint()
{
Index = index, Point2D = element, RemapStartIndex = start, RemapEndIndex = end,
RemapProcess = process,
ProjectionOffset = CalculateProjectOffset(perpendicular, element)
});
index += 1;
}
//颜色中线投影偏移
var minOffset = float.MaxValue;
var maxOffset = float.MinValue;
foreach (var point in _remap2DTo3D)
{
minOffset = Mathf.Min(point.ProjectionOffset, minOffset);
maxOffset = Mathf.Max(point.ProjectionOffset, maxOffset);
}
var ver0 = new UIVertex()
{
position = new Vector2(_rect.xMin, _rect.yMin),
color = graphic.color,
uv0 = new Vector2(0, 0),
};
var ver1 = new UIVertex()
{
position = new Vector2(_rect.xMin, _rect.yMax),
color = graphic.color,
uv0 = new Vector2(0, 1),
};
var ver2 = new UIVertex()
{
position = new Vector2(_rect.xMax, _rect.yMax),
color = graphic.color,
uv0 = new Vector2(1, 1),
};
var ver3 = new UIVertex()
{
position = new Vector2(_rect.xMax, _rect.yMin),
color = graphic.color,
uv0 = new Vector2(1, 0),
};
vh.GetUIVertexStream(_uiVertices);
//边缘点
var vertices = new[] { ver0, ver1, ver2, ver3 };
//计算顶点数组
_vertexData.Clear();
UIVertex ver4 = default, ver5 = default;
for (int i = 0; i < _remap2DTo3D.Count; i++)
{
var element = _remap2DTo3D[i];
var vertex = RemapToUIVertex(element, vertices, minOffset, maxOffset);
switch (i)
{
case 4:
ver4 = vertex;
break;
case 5:
ver5 = vertex;
break;
}
_vertexData.Add(new VertexData()
{
Vertex = vertex,
Index = i,
RemapData = element,
});
}
//按角度排序
var pointQueue = _vertexData.OrderBy(e => Vector2.SignedAngle(Vector2.right, e.RemapData.Point2D))
.Select(e => e.Index).ToList();
//计算中心点
var center = new UIVertex()
{
position = Vector3.Lerp(ver4.position, ver5.position, 0.5f),
uv0 = Vector4.Lerp(ver4.uv0, ver5.uv0, 0.5f),
color = Color.Lerp(ver4.color, ver5.color, 0.5f)
};
_uiVertices.Clear();
foreach (var data in _vertexData)
{
_uiVertices.Add(data.Vertex);
}
_uiVertices.Add(center);
_triangle.Clear();
for (var i = 0; i < pointQueue.Count; i++)
{
var index1 = pointQueue.Count;
var index2 = pointQueue[i];
var index3 = pointQueue[(i + 1) % pointQueue.Count];
_triangle.Add(index1);
_triangle.Add(index2);
_triangle.Add(index3);
}
vh.Clear();
vh.AddUIVertexStream(_uiVertices, _triangle);
}
//计算边线采样进度
private void CalculateEdgeRemapProcess(Vector2 point, Rect square, out int startIndex, out int endIndex,
out float process)
{
if (Math.Abs(point.x - square.xMin) < 0.0001f)
{
process = Mathf.InverseLerp(square.yMin, square.yMax, point.y);
startIndex = 0;
endIndex = 1;
return;
}
if (Math.Abs(point.y - square.yMax) < 0.0001f)
{
process = Mathf.InverseLerp(square.xMin, square.xMax, point.x);
startIndex = 1;
endIndex = 2;
return;
}
if (Math.Abs(point.x - square.xMax) < 0.0001f)
{
process = Mathf.InverseLerp(square.yMax, square.yMin, point.y);
startIndex = 2;
endIndex = 3;
return;
}
process = Mathf.InverseLerp(square.xMax, square.xMin, point.x);
startIndex = 3;
endIndex = 0;
}
//采样到UI顶点
private UIVertex RemapToUIVertex(Remap2DTo3DPoint remap2DTo3DPoint, UIVertex[] vertices, float minOffset,
float maxOffset)
{
var v1 = vertices[remap2DTo3DPoint.RemapStartIndex];
var v2 = vertices[remap2DTo3DPoint.RemapEndIndex];
var process = remap2DTo3DPoint.RemapProcess;
var colorProcess = remap2DTo3DPoint.ProjectionOffset >= 0
? Mathf.InverseLerp(0, maxOffset, remap2DTo3DPoint.ProjectionOffset)
: Mathf.InverseLerp(0, minOffset,
remap2DTo3DPoint.ProjectionOffset);
var colorAdd = remap2DTo3DPoint.ProjectionOffset >= 0
? Color.Lerp(MiddleColor, TopColor, colorProcess)
: Color.Lerp(MiddleColor, BottomColor, colorProcess);
return new UIVertex()
{
position = Vector3.Lerp(v1.position, v2.position, process),
uv0 = Vector4.Lerp(v1.uv0, v2.uv0, process),
color = Color.Lerp(v1.color, v2.color, process) * colorAdd,
};
}
// //计算切割点
private bool TryCalculateCutLines(Vector2 start, Vector2 end, out List lines)
{
lines = new List();
//特殊角度,不处理
if (angle % 45 == 0)
{
return false;
}
var line = new Line(start, end);
Line line1;
Line line2;
Line line3;
Line line4;
//竖直方向
if (angle > 45 && angle < 135 || angle > 225 && angle < 315)
{
var move1 = Vector2.right * (1 - start.x);
var move2 = Vector2.right * (-1 - start.x);
var move3 = Vector2.right * (1 - end.x);
var move4 = Vector2.right * (-1 - end.x);
line1 = MoveLineTo(line, move1);
line2 = MoveLineTo(line, move2);
line3 = MoveLineTo(line, move3);
line4 = MoveLineTo(line, move4);
}
else
{
//横向,上下找点
var move1 = Vector2.up * (1 - start.y);
var move2 = Vector2.up * (-1 - start.y);
var move3 = Vector2.up * (1 - end.y);
var move4 = Vector2.up * (-1 - end.y);
line1 = MoveLineTo(line, move1);
line2 = MoveLineTo(line, move2);
line3 = MoveLineTo(line, move3);
line4 = MoveLineTo(line, move4);
}
if (IsInSquareRange(Square, line1))
{
lines.Add(line1);
}
if (IsInSquareRange(Square, line2))
{
lines.Add(line2);
}
if (IsInSquareRange(Square, line3))
{
lines.Add(line3);
}
if (IsInSquareRange(Square, line4))
{
lines.Add(line4);
}
return true;
}
private Line MoveLineTo(Line line, Vector2 offset)
{
return new Line()
{
Start = line.Start + offset,
End = line.End + offset,
};
}
private bool IsInSquareRange(Rect square, Line line)
{
return line.yMax <= square.yMax && line.yMin >= square.yMin && line.xMax <= square.xMax &&
line.xMin >= square.xMin;
}
//计算投影偏移
private float CalculateProjectOffset(Vector2 dir, Vector2 check)
{
var projectionLength = Vector2.Dot(check, dir);
// //计算投影交点
var dot = Vector2.Dot(dir.normalized, dir);
if (dot < 0)
{
projectionLength = -projectionLength; // 如果投影方向与向量 a 的方向相反,则将投影长度取负值
}
return projectionLength;
}
//顶点排序
private Vector2[] SortPoints(List points)
{
return points.OrderBy(e => Vector2.SignedAngle(Vector2.right, e)).ToArray();
}
private Vector2 AngleToDirection(float angle)
{
float rad = Mathf.Deg2Rad * angle; // 将角度转换为弧度
Vector2 direction = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)).normalized;
return direction;
}
}
}