Archived
316 lines
12 KiB
C#
316 lines
12 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// UI多边形图形图案渲染组件(三角形,菱形,五边形...三十二边形)
|
|
/// 支持倒角
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// 获取最大缩放(一个参考值)
|
|
/// </summary>
|
|
public float GetMaxScale => 1 / Mathf.Cos(GetSideRadius);
|
|
private float ScaleMin => 0.01f;
|
|
/// <summary>
|
|
/// 获取多边形的旋转弧度
|
|
/// </summary>
|
|
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");
|
|
|
|
/// <summary>
|
|
/// 边框厚度
|
|
/// </summary>
|
|
public float BorderThickness
|
|
{
|
|
get => borderThickness;
|
|
set
|
|
{
|
|
borderThickness = value;
|
|
m_Material.SetFloat(Thinkness, borderThickness);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 边框颜色
|
|
/// </summary>
|
|
public Color BorderColor
|
|
{
|
|
get => borderColor;
|
|
set
|
|
{
|
|
borderColor = value;
|
|
m_Material.SetColor(BorderColor1, borderColor);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 边框颜色渐变
|
|
/// </summary>
|
|
public Color BorderColorLinear
|
|
{
|
|
get => borderColor2;
|
|
set
|
|
{
|
|
borderColor2 = value;
|
|
m_Material.SetColor(BorderColor2, borderColor2);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 填充颜色
|
|
/// </summary>
|
|
public Color FillColor
|
|
{
|
|
get => fillColor;
|
|
set
|
|
{
|
|
fillColor = value;
|
|
m_Material.SetColor(FillColor1, fillColor);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置缩放
|
|
/// </summary>
|
|
/// <param name="scale"></param>
|
|
public void SetScale(float scale) => this.scale = scale;
|
|
|
|
// 缓存顶点列表以避免每帧分配内存
|
|
private readonly List<Vector3> verticesCache = new List<Vector3>();
|
|
|
|
#endregion
|
|
|
|
#region 类内类型
|
|
|
|
public enum AngleSelectMode
|
|
{
|
|
Angle,
|
|
Multiples,
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected override void OnPopulatePrefabricateVertex(List<UIVertex> vertexList, List<int> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 图元基础外围图形顶点
|
|
/// </summary>
|
|
/// <param name="sideCount">边数(或者顶点数)</param>
|
|
/// <param name="radius">半径</param>
|
|
/// <param name="startAngle">图形起始角度</param>
|
|
/// <param name="center">中心偏移</param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<Vector3> 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
|
|
}
|
|
} |