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; } } }