using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Pool; using UnityEngine.Serialization; using UnityEngine.UI; using XericLibrary.Runtime.Debuger; using XericLibrary.Runtime.MacroLibrary; #if ODIN_INSPECTOR using Sirenix.OdinInspector; #endif namespace XericLibrary.Runtime.UIGraph { /// /// UI多边形图形图案渲染组件(三角形,菱形,五边形...三十二边形) /// 支持倒角 /// [AddComponentMenu("Xeric Library/UI/UIPatternRenderer", 15)] public class UIPatternRenderer : XericRendererComponent { #region 字段属性 public override string ShaderName => "XericLibrary/UILine/UIPattern"; [Header("多边形设置"), Range(3, 32)] public int sides = 6; [SerializeField, Tooltip("多边形的大小 (相对于矩形区域的比例 0-1)")] #if ODIN_INSPECTOR [PropertyRange("ScaleMin", "GetMaxScale")] #else [Range(0f, 1f)] #endif private float scale = 1f; [Tooltip("倒角尺寸")][Range(0f, 1f)] public float roundingSize = 0; [Tooltip("倒角细分")][Range(2f, 32f)] public int roundingTime = 3; [Tooltip("倒角形状")][Range(0f, 1f)] public float roundingPower = 1; public AngleSelectMode angleMode; [ShowIf("angleMode", optionalValue: AngleSelectMode.Angle)] [Tooltip("起始角度")] public float startAngle = 90f; [ShowIf("angleMode", optionalValue: AngleSelectMode.Multiples)] [Tooltip("起始角度(倍数对齐)")] public float startAngleTime = 1; [SerializeField, Tooltip("边框厚度")] [Range(0f, 1f)] private float borderThickness = .1f; [SerializeField, Tooltip("边框颜色")] private Color borderColor = Color.white; [SerializeField, Tooltip("边框颜色渐变")] private Color borderColor2 = Color.white; [SerializeField, Tooltip("填充颜色")] private Color fillColor = Color.gray; /// /// 获取最大缩放(一个参考值) /// public float GetMaxScale => 1 / Mathf.Cos(GetSideRadius); private float ScaleMin => 0.01f; /// /// 获取多边形的旋转弧度 /// public float GetSideRadius => Mathf.PI / sides; private float sideAngle => angleMode switch { AngleSelectMode.Angle => startAngle, AngleSelectMode.Multiples => Mathf.Rad2Deg * 2 * GetSideRadius * (startAngleTime - (sides % 2 == 0 ? 0 : 0.25f)), _ => 1f }; private static readonly int BorderColor1 = Shader.PropertyToID("_BorderColor"); private static readonly int BorderColor2 = Shader.PropertyToID("_BorderColor2"); private static readonly int FillColor1 = Shader.PropertyToID("_FillColor"); private static readonly int Thinkness = Shader.PropertyToID("_Thinkness"); /// /// 边框厚度 /// public float BorderThickness { get => borderThickness; set { borderThickness = value; m_Material.SetFloat(Thinkness, borderThickness); } } /// /// 边框颜色 /// public Color BorderColor { get => borderColor; set { borderColor = value; m_Material.SetColor(BorderColor1, borderColor); } } /// /// 边框颜色渐变 /// public Color BorderColorLinear { get => borderColor2; set { borderColor2 = value; m_Material.SetColor(BorderColor2, borderColor2); } } /// /// 填充颜色 /// public Color FillColor { get => fillColor; set { fillColor = value; m_Material.SetColor(FillColor1, fillColor); } } /// /// 设置缩放 /// /// public void SetScale(float scale) => this.scale = scale; // 缓存顶点列表以避免每帧分配内存 private readonly List verticesCache = new List(); #endregion #region 类内类型 public enum AngleSelectMode { Angle, Multiples, } #endregion protected override void OnPopulatePrefabricateVertex(List vertexList, List indexList) { // 获取绘图区域的中心点和尺寸 Rect rect = GetPixelAdjustedRect(); Vector2 center = rect.center; // 取宽高较小值的一半作为基准半径,确保图形不超出边界 float minSize = Mathf.Min(rect.width, rect.height); float radius = (minSize / 2f) * scale; // 计算多边形的顶点 verticesCache.Clear(); // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (sides < 3) { sides = 3; Debug.LogError("图元必须由至少3个顶点构成!"); } // 半角 (弧度) var halfAngleRad = Mathf.PI / sides; // 限制最大倒角半径,防止图形自相交或消失 // 最大理论半径是内切圆半径,但为了安全,限制在边长的一半对应的切距内 // 边长 L = 2 * R * sin(pi/n) // 最大切距 d_max = L / 2 = R * sin(pi/n) // 对应最大圆角半径 r_max = d_max * tan(pi/n) = R * sin^2/cos ? // 简单限制:r < radius * 0.4 (经验值) 或者精确计算 var maxRounding = radius * 0.4f; var currentRounding = Mathf.Clamp(roundingSize, 0f, maxRounding); // 此渲染组件的颜色和中心uv Color32 color32 = this.color; Vector2 uvCenter = new Vector4(0.5f, 0.5f, 0, 0); // 没有倒角,使用常规流程 if (currentRounding <= 0f) { verticesCache.AddRange(OriginPatternVectorPoints(sides, radius, sideAngle, center)); // 添加中心点 vertexList.AddVert(center, uvCenter, color32); float angleStep = 360f / sides; // 添加外围点 for (int i = 0; i < verticesCache.Count; i++) { // 简单的映射UV,将世界坐标映射到 0-1 范围,或者根据需求自定义 // 这里简单地将多边形映射到整个纹理空间 XericRendererUtils.GetUVAtRect(verticesCache[i], rect, out var u, out var v); GetNormalizedAngleByPos(angleStep, i, out var normalizedAngle); var uv = new Vector4(u, v, 1, normalizedAngle); vertexList.AddVert(verticesCache[i], uv, color32); } // 三角形顺序:中心点, 当前外点, 下一个外点 for (int i = 1; i < sides; i++) indexList.AddTriangle(0, i, i + 1); // 连接最后一个点和第一个点以闭合形状 indexList.AddTriangle(0, sides, 1); } // 启用倒角 else { verticesCache.AddRange(OriginPatternVectorPoints(sides, radius, sideAngle, center)); // 添加中心点 vertexList.AddVert(center, uvCenter, color32); var maxVpCount = sides * roundingTime + 1; var l = 2 * radius * Mathf.Sin(Mathf.PI / sides); for (var i = 0; i < sides; i++) { Vector2 pCurr = verticesCache[i]; Vector2 pPrev = verticesCache[(i - 1 + sides) % sides]; Vector2 pNext = verticesCache[(i + 1) % sides]; Vector2 dirIn = (pPrev - pCurr).normalized; Vector2 dirOut = (pNext - pCurr).normalized; // 计算圆弧圆心 // 角平分线方向 (指向内部) // dirIn 的反向是 -dirIn. dirOut 是 dirOut. // 平分线 = normalize(-dirIn + dirOut) Vector2 bisector = (-dirIn + dirOut).normalized; // 圆心距离 pCurr 的距离: h = r / sin(alpha) float distToCenter = currentRounding / Mathf.Sin(halfAngleRad); Vector2 arcCenter = pCurr + bisector * distToCenter; var halfL = l * 0.5f; var power = halfL * roundingPower * roundingSize; // 添加圆弧中心点 // GetUVByPos(arcCenter, rect, out var ac_u, out var ac_v); // vh.AddVert(arcCenter, this.color, new Vector4(ac_u, ac_v, )); // // 中心区域 // if (i > 0) // vh.AddTriangle(0, 2 + (i - 1) * roundingTime, 2 + i * roundingTime); var p0 = pCurr + dirIn * halfL * roundingSize; var p1 = pCurr + dirIn * power; var p2 = pCurr + dirOut * power; var p3 = pCurr + dirOut * halfL * roundingSize; for (var j = 0; j < roundingTime; j++) { var jTime = (float)j / (roundingTime - 1); MacroCurve.BezierCurve3(p0, p1, p2, p3, jTime, out var pos); pos.GetUVAtRect(rect, out var u, out var v); var uv = new Vector4(u, v, 1, 1); vertexList.AddVert(pos, uv, this.color); var index = roundingTime * i + j + 1; var next = index + 1; if (next >= maxVpCount) next = (next % maxVpCount) + 1; indexList.AddTriangle(0, index, next); } } } } /// /// 图元基础外围图形顶点 /// /// 边数(或者顶点数) /// 半径 /// 图形起始角度 /// 中心偏移 /// private static IEnumerable OriginPatternVectorPoints(int sideCount, float radius, float startAngle, Vector2 center) { float angleStep = 360f / sideCount; for (int i = 0; i < sideCount; i++) { float angleDeg = startAngle + i * angleStep; float angle = angleDeg * Mathf.Deg2Rad; float x = center.x + Mathf.Cos(angle) * radius; float y = center.y + Mathf.Sin(angle) * radius; yield return new Vector2(x, y); // uv的y是角度,不过看起来不太需要这一步 // float normalizedDist = 1.0f; // float normalizedAngle = (angleDeg + 90f) / 360f; // if (normalizedAngle < 0) normalizedAngle += 1f; // if (normalizedAngle > 1) normalizedAngle -= 1f; // // uvCache.Add(new Vector2(normalizedDist, normalizedAngle)); } } private void GetNormalizedAngleByPos(float angleStep, int count, out float normalizedAngle) { float angleDeg = startAngle + count * angleStep; normalizedAngle = (angleDeg + 90f) / 360f; if (normalizedAngle < 0) normalizedAngle += 1f; if (normalizedAngle > 1) normalizedAngle -= 1f; } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); if (sides < 3) sides = 3; if (scale <= float.Epsilon) scale = float.Epsilon; SetVerticesDirty(); BorderThickness = borderThickness; BorderColor = borderColor; BorderColorLinear = borderColor2; FillColor = fillColor; } #endif } }