udpate 0.6.3.3
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 图元缓存渲染器基类
|
||||
/// 管理 PrimitiveCacheEntry 列表 + 脏标记范围更新
|
||||
/// LateUpdate 中只重建脏标记范围内的图元
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
public abstract class PrimitiveCacheRendererBase : MaskableGraphic
|
||||
{
|
||||
#region 默认材质
|
||||
|
||||
private static Material s_DefaultMaterial;
|
||||
private const string DefaultShaderPath = "XericLibrary/UIGraph/UIPattern";
|
||||
|
||||
/// <summary>
|
||||
/// 获取图元渲染器的默认材质(基于 UIPattern.shader)
|
||||
/// </summary>
|
||||
public override Material defaultMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_DefaultMaterial == null)
|
||||
{
|
||||
var shader = Shader.Find(DefaultShaderPath);
|
||||
if (shader != null)
|
||||
s_DefaultMaterial = new Material(shader);
|
||||
else
|
||||
s_DefaultMaterial = base.defaultMaterial;
|
||||
}
|
||||
return s_DefaultMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据缓存
|
||||
|
||||
/// <summary>
|
||||
/// 图元列表
|
||||
/// </summary>
|
||||
protected List<PrimitiveCacheEntry> m_Primitives = new List<PrimitiveCacheEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// 模型顶点缓存
|
||||
/// </summary>
|
||||
protected List<UIVertex> m_MeshVertexCache = new List<UIVertex>();
|
||||
|
||||
/// <summary>
|
||||
/// 三角形索引缓存
|
||||
/// </summary>
|
||||
protected List<int> m_MeshIndexCache = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// 脏标记范围起点(图元索引),-1 表示无脏标记
|
||||
/// </summary>
|
||||
protected int m_DirtyStartIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 脏标记范围终点(图元索引)
|
||||
/// </summary>
|
||||
protected int m_DirtyEndIndex = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region API
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个图元
|
||||
/// </summary>
|
||||
/// <returns>图元索引</returns>
|
||||
public int AddPrimitive(PrimitiveCacheEntry entry)
|
||||
{
|
||||
int index = m_Primitives.Count;
|
||||
m_Primitives.Add(entry);
|
||||
MarkDirty(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定图元
|
||||
/// </summary>
|
||||
public void RemovePrimitive(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Primitives.Count) return;
|
||||
m_Primitives.RemoveAt(index);
|
||||
RebuildAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记指定图元为脏
|
||||
/// </summary>
|
||||
public void SetPrimitiveDirty(int index)
|
||||
{
|
||||
MarkDirty(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有图元
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
m_Primitives.Clear();
|
||||
m_MeshVertexCache.Clear();
|
||||
m_MeshIndexCache.Clear();
|
||||
m_DirtyStartIndex = -1;
|
||||
m_DirtyEndIndex = -1;
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 脏标记
|
||||
|
||||
protected void MarkDirty(int index)
|
||||
{
|
||||
if (m_DirtyStartIndex < 0 || index < m_DirtyStartIndex)
|
||||
m_DirtyStartIndex = index;
|
||||
if (m_DirtyEndIndex < 0 || index > m_DirtyEndIndex)
|
||||
m_DirtyEndIndex = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记所有图元为脏(下次 LateUpdate 全量重绘)
|
||||
/// 适用于:更新顶点坐标后,不改变元素数量的重绘
|
||||
/// </summary>
|
||||
public void RebuildAll()
|
||||
{
|
||||
m_DirtyStartIndex = 0;
|
||||
m_DirtyEndIndex = m_Primitives.Count - 1;
|
||||
if (m_DirtyEndIndex < 0)
|
||||
m_DirtyStartIndex = -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LateUpdate
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
if (m_DirtyStartIndex < 0) return;
|
||||
|
||||
// 全量重建策略:从脏范围起点开始,重绘该条目及后续所有条目
|
||||
// 保证所有条目 startVertexIndex/startIndexIndex 始终正确
|
||||
int start = m_DirtyStartIndex;
|
||||
|
||||
// 统计脏范围前的顶点/索引数(这些条目无需重建)
|
||||
int preVertexCount = 0;
|
||||
int preIndexCount = 0;
|
||||
for (int i = 0; i < start; i++)
|
||||
{
|
||||
preVertexCount += m_Primitives[i].vertexCount;
|
||||
preIndexCount += m_Primitives[i].triangleCount * 3;
|
||||
}
|
||||
|
||||
// 从 start 开始重建所有条目
|
||||
var tempVerts = new List<UIVertex>();
|
||||
var tempIndices = new List<int>();
|
||||
int vAccum = preVertexCount;
|
||||
int iAccum = preIndexCount;
|
||||
for (int i = start; i < m_Primitives.Count; i++)
|
||||
{
|
||||
var entry = m_Primitives[i];
|
||||
int vertBefore = tempVerts.Count;
|
||||
int idxBefore = tempIndices.Count;
|
||||
PrimitiveMeshGenerator.GeneratePrimitive(entry, tempVerts, tempIndices);
|
||||
var updated = entry;
|
||||
updated.startVertexIndex = vAccum;
|
||||
updated.vertexCount = tempVerts.Count - vertBefore;
|
||||
updated.startIndexIndex = iAccum;
|
||||
updated.triangleCount = (tempIndices.Count - idxBefore) / 3;
|
||||
m_Primitives[i] = updated;
|
||||
vAccum += updated.vertexCount;
|
||||
iAccum += updated.triangleCount * 3;
|
||||
}
|
||||
|
||||
// 合并到主缓存
|
||||
int totalVertices = vAccum;
|
||||
int totalIndices = iAccum;
|
||||
|
||||
while (m_MeshVertexCache.Count < totalVertices)
|
||||
m_MeshVertexCache.Add(new UIVertex());
|
||||
while (m_MeshIndexCache.Count < totalIndices)
|
||||
m_MeshIndexCache.Add(0);
|
||||
|
||||
for (int i = 0; i < tempVerts.Count; i++)
|
||||
m_MeshVertexCache[preVertexCount + i] = tempVerts[i];
|
||||
|
||||
for (int i = 0; i < tempIndices.Count; i++)
|
||||
m_MeshIndexCache[preIndexCount + i] = tempIndices[i] + preVertexCount;
|
||||
|
||||
SetVerticesDirty();
|
||||
m_DirtyStartIndex = -1;
|
||||
m_DirtyEndIndex = -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OnPopulateMesh
|
||||
|
||||
protected override void OnPopulateMesh(VertexHelper vh)
|
||||
{
|
||||
vh.Clear();
|
||||
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0) return;
|
||||
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
RebuildAll();
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28047425f50534b46a5ad5b7a924bd30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,390 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 图元网格生成器
|
||||
/// 为多边形/矩形生成 triangle-fan 网格,支持倒角
|
||||
/// </summary>
|
||||
public static class PrimitiveMeshGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成单个图元的网格顶点和索引
|
||||
/// </summary>
|
||||
public static void GeneratePrimitive(
|
||||
in PrimitiveCacheEntry entry,
|
||||
List<UIVertex> vertexList,
|
||||
List<int> indexList)
|
||||
{
|
||||
// 将归一化 chamferSize [0,1] 映射为实际像素倒角尺寸
|
||||
float minDim = Mathf.Min(entry.size.x, entry.size.y);
|
||||
float scaledChamfer = entry.@params.chamferSize * minDim * 0.5f;
|
||||
|
||||
PrimitiveParamHelper helper = new PrimitiveParamHelper
|
||||
{
|
||||
center = entry.center,
|
||||
size = entry.size,
|
||||
sizeMode = entry.sizeMode,
|
||||
type = entry.type,
|
||||
sideCount = (int)entry.@params.sideCount,
|
||||
chamferSize = scaledChamfer,
|
||||
chamferSegments = entry.@params.chamferSegments,
|
||||
faceCount = 0
|
||||
};
|
||||
|
||||
// 1. 计算外围顶点
|
||||
var corners = ComputeCorners(helper);
|
||||
if (corners.Count < 3) return;
|
||||
|
||||
// 应用角度旋转
|
||||
if (Mathf.Abs(entry.angle) > 0.001f)
|
||||
{
|
||||
float rad = entry.angle * Mathf.Deg2Rad;
|
||||
float cos = Mathf.Cos(rad);
|
||||
float sin = Mathf.Sin(rad);
|
||||
for (int i = 0; i < corners.Count; i++)
|
||||
{
|
||||
Vector2 rel = corners[i] - entry.center;
|
||||
corners[i] = entry.center + new Vector2(
|
||||
rel.x * cos - rel.y * sin,
|
||||
rel.x * sin + rel.y * cos);
|
||||
}
|
||||
}
|
||||
|
||||
int baseVertex = vertexList.Count;
|
||||
int typeIndex = (int)entry.type;
|
||||
|
||||
// 颜色打包到法线/副法线
|
||||
// normal: (bg.r/255, bg.g/255, bg.b/255)
|
||||
// tangent: (border.r/255, border.g/255, border.b/255, border.a/255)
|
||||
Color32 bg = entry.@params.bgColor;
|
||||
Vector3 normalCol = new Vector3(bg.r / 255f, bg.g / 255f, bg.b / 255f);
|
||||
Color32 bc = entry.@params.borderColor;
|
||||
Vector4 tangentCol = new Vector4(bc.r / 255f, bc.g / 255f, bc.b / 255f, bc.a / 255f);
|
||||
|
||||
// 2. 添加中心点
|
||||
UIVertex centerVert = new UIVertex
|
||||
{
|
||||
position = (Vector3)entry.center,
|
||||
color = entry.@params.centerColor,
|
||||
normal = normalCol,
|
||||
tangent = tangentCol,
|
||||
uv0 = new Vector2(0.5f, 0.5f),
|
||||
uv1 = new Vector2(0f, 0f),
|
||||
};
|
||||
vertexList.Add(centerVert);
|
||||
|
||||
// 3. 添加外围顶点 + 创建三角形扇
|
||||
for (int i = 0; i < corners.Count; i++)
|
||||
{
|
||||
Vector2 pos = corners[i];
|
||||
float dist = Vector2.Distance(pos, entry.center);
|
||||
float maxDist = Mathf.Max(entry.size.x, entry.size.y) * 0.5f;
|
||||
float normalizedDist = maxDist > 0.001f ? Mathf.Clamp01(dist / maxDist) : 0f;
|
||||
|
||||
UIVertex vert = new UIVertex
|
||||
{
|
||||
position = (Vector3)pos,
|
||||
color = entry.@params.centerColor,
|
||||
normal = normalCol,
|
||||
tangent = tangentCol,
|
||||
uv0 = new Vector2(
|
||||
(pos.x - entry.center.x) / entry.size.x + 0.5f,
|
||||
(pos.y - entry.center.y) / entry.size.y + 0.5f),
|
||||
uv1 = new Vector2(normalizedDist, 0f),
|
||||
};
|
||||
vertexList.Add(vert);
|
||||
|
||||
// 三角形扇: (中心, i, i+1)
|
||||
if (i > 0)
|
||||
{
|
||||
indexList.Add(baseVertex); // 中心
|
||||
indexList.Add(baseVertex + i); // 当前角
|
||||
indexList.Add(baseVertex + i + 1); // 下一个角
|
||||
}
|
||||
}
|
||||
|
||||
// 闭合:最后一个角 → 第一个角
|
||||
if (corners.Count > 2)
|
||||
{
|
||||
indexList.Add(baseVertex);
|
||||
indexList.Add(baseVertex + corners.Count);
|
||||
indexList.Add(baseVertex + 1);
|
||||
}
|
||||
|
||||
// 4. 生成物理边框 — 复制外围顶点并向外偏移一圈,条带拼接
|
||||
float borderThickness = entry.@params.borderThickness;
|
||||
if (borderThickness > 0.001f && corners.Count >= 3)
|
||||
{
|
||||
int n = corners.Count;
|
||||
int borderBase = vertexList.Count;
|
||||
|
||||
// 计算每个顶点的向外偏移方向(从中心指向顶点的方向)
|
||||
var outDirs = new Vector2[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
Vector2 dir = corners[i] - entry.center;
|
||||
outDirs[i] = dir.sqrMagnitude > 0.001f ? dir.normalized : Vector2.up;
|
||||
}
|
||||
|
||||
// 添加内圈(原始位置)+ 外圈(偏移位置),isBorder=1
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
Vector2 pos = corners[i];
|
||||
Vector2 outPos = pos + outDirs[i] * borderThickness;
|
||||
|
||||
// 内圈顶点 (borderProgress=0)
|
||||
UIVertex inner = new UIVertex
|
||||
{
|
||||
position = (Vector3)pos,
|
||||
color = entry.@params.borderColor,
|
||||
normal = normalCol,
|
||||
tangent = tangentCol,
|
||||
uv0 = new Vector2(
|
||||
(pos.x - entry.center.x) / entry.size.x + 0.5f,
|
||||
(pos.y - entry.center.y) / entry.size.y + 0.5f),
|
||||
uv1 = new Vector2(0f, 1f),
|
||||
};
|
||||
vertexList.Add(inner);
|
||||
|
||||
// 外圈顶点 (borderProgress=1)
|
||||
UIVertex outer = new UIVertex
|
||||
{
|
||||
position = (Vector3)outPos,
|
||||
color = entry.@params.borderColor,
|
||||
normal = normalCol,
|
||||
tangent = tangentCol,
|
||||
uv0 = new Vector2(
|
||||
(outPos.x - entry.center.x) / entry.size.x + 0.5f,
|
||||
(outPos.y - entry.center.y) / entry.size.y + 0.5f),
|
||||
uv1 = new Vector2(1f, 1f),
|
||||
};
|
||||
vertexList.Add(outer);
|
||||
}
|
||||
|
||||
// 连接条带三角形
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int next = (i + 1) % n;
|
||||
int iI = borderBase + i * 2; // 内圈 i
|
||||
int oI = borderBase + i * 2 + 1; // 外圈 i
|
||||
int iN = borderBase + next * 2; // 内圈 next
|
||||
int oN = borderBase + next * 2 + 1; // 外圈 next
|
||||
|
||||
indexList.Add(iI);
|
||||
indexList.Add(oI);
|
||||
indexList.Add(iN);
|
||||
|
||||
indexList.Add(oI);
|
||||
indexList.Add(oN);
|
||||
indexList.Add(iN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算图元的外围顶点列表
|
||||
/// </summary>
|
||||
private static List<Vector2> ComputeCorners(in PrimitiveParamHelper helper)
|
||||
{
|
||||
if (helper.type == PrimitiveType.Polygon)
|
||||
return ComputePolygonCorners(helper);
|
||||
else
|
||||
return ComputeRectangleCorners(helper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算正多边形的外围顶点
|
||||
/// </summary>
|
||||
private static List<Vector2> ComputePolygonCorners(in PrimitiveParamHelper helper)
|
||||
{
|
||||
int sides = Mathf.Max(3, helper.sideCount);
|
||||
float angleStep = 360f / sides;
|
||||
float startAngle = 90f;
|
||||
|
||||
// 计算半径
|
||||
float minSize = Mathf.Min(helper.size.x, helper.size.y);
|
||||
float radius = minSize * 0.5f;
|
||||
|
||||
if (helper.sizeMode == SizeMode.CircumscribedCircle)
|
||||
{
|
||||
// 外切圆:半径延伸到各顶点
|
||||
// 外切圆半径 = minSize/2
|
||||
// 实际半径需要让多边形的边与外切圆相切
|
||||
// 对于外切圆,多边形的顶点到中心的距离 = r / cos(π/N)
|
||||
float apothemAngle = Mathf.PI / sides;
|
||||
radius = radius / Mathf.Cos(apothemAngle);
|
||||
}
|
||||
// InscribedCircle 保持 radius = minSize/2
|
||||
|
||||
// 计算基本多边形顶点
|
||||
var corners = new List<Vector2>(sides);
|
||||
for (int i = 0; i < sides; i++)
|
||||
{
|
||||
float angleDeg = startAngle + i * angleStep;
|
||||
float angle = angleDeg * Mathf.Deg2Rad;
|
||||
corners.Add(new Vector2(
|
||||
helper.center.x + Mathf.Cos(angle) * radius,
|
||||
helper.center.y + Mathf.Sin(angle) * radius));
|
||||
}
|
||||
|
||||
// 应用 XY 缩放
|
||||
for (int i = 0; i < corners.Count; i++)
|
||||
{
|
||||
Vector2 dir = corners[i] - helper.center;
|
||||
corners[i] = helper.center + new Vector2(
|
||||
dir.x * helper.size.x / minSize,
|
||||
dir.y * helper.size.y / minSize);
|
||||
}
|
||||
|
||||
// 处理倒角
|
||||
if (helper.chamferSize > 0.001f)
|
||||
corners = ApplyChamfer(corners, helper.chamferSize, helper.chamferSegments);
|
||||
|
||||
return corners;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算矩形的四个角
|
||||
/// </summary>
|
||||
private static List<Vector2> ComputeRectangleCorners(in PrimitiveParamHelper helper)
|
||||
{
|
||||
float halfX, halfY;
|
||||
|
||||
switch (helper.sizeMode)
|
||||
{
|
||||
case SizeMode.InscribedCircle:
|
||||
{
|
||||
float minSize = Mathf.Min(helper.size.x, helper.size.y);
|
||||
halfX = minSize * 0.5f;
|
||||
halfY = minSize * 0.5f;
|
||||
break;
|
||||
}
|
||||
case SizeMode.CircumscribedCircle:
|
||||
{
|
||||
float minSize = Mathf.Min(helper.size.x, helper.size.y);
|
||||
float radius = minSize * 0.5f;
|
||||
halfX = radius;
|
||||
halfY = radius;
|
||||
break;
|
||||
}
|
||||
case SizeMode.InscribedEllipse:
|
||||
{
|
||||
halfX = helper.size.x * 0.5f;
|
||||
halfY = helper.size.y * 0.5f;
|
||||
break;
|
||||
}
|
||||
case SizeMode.CircumscribedRatio:
|
||||
{
|
||||
float minSize = Mathf.Min(helper.size.x, helper.size.y);
|
||||
float radius = minSize * 0.5f;
|
||||
halfX = radius * (helper.size.x / minSize);
|
||||
halfY = radius * (helper.size.y / minSize);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
halfX = helper.size.x * 0.5f;
|
||||
halfY = helper.size.y * 0.5f;
|
||||
break;
|
||||
}
|
||||
|
||||
var corners = new List<Vector2>(4)
|
||||
{
|
||||
helper.center + new Vector2(-halfX, -halfY),
|
||||
helper.center + new Vector2( halfX, -halfY),
|
||||
helper.center + new Vector2( halfX, halfY),
|
||||
helper.center + new Vector2(-halfX, halfY),
|
||||
};
|
||||
|
||||
if (helper.chamferSize > 0.001f)
|
||||
corners = ApplyChamfer(corners, helper.chamferSize, helper.chamferSegments);
|
||||
|
||||
return corners;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对多边形角应用倒角(Bezier 曲线圆角)
|
||||
/// </summary>
|
||||
private static List<Vector2> ApplyChamfer(
|
||||
List<Vector2> corners, float chamferSize, int segments)
|
||||
{
|
||||
int n = corners.Count;
|
||||
// 计算每条边的长度,取最短边的一半作为倒角上限
|
||||
float minEdgeHalf = float.MaxValue;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int next = (i + 1) % n;
|
||||
float edgeLen = Vector2.Distance(corners[i], corners[next]);
|
||||
minEdgeHalf = Mathf.Min(minEdgeHalf, edgeLen * 0.5f);
|
||||
}
|
||||
float clampedChamfer = Mathf.Min(chamferSize, minEdgeHalf);
|
||||
if (clampedChamfer < 0.001f) return corners;
|
||||
|
||||
var result = new List<Vector2>();
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int prev = (i - 1 + n) % n;
|
||||
int curr = i;
|
||||
int next = (i + 1) % n;
|
||||
|
||||
Vector2 pCurr = corners[curr];
|
||||
Vector2 pPrev = corners[prev];
|
||||
Vector2 pNext = corners[next];
|
||||
|
||||
float edgeLenIn = Vector2.Distance(pPrev, pCurr);
|
||||
float edgeLenOut = Vector2.Distance(pCurr, pNext);
|
||||
float clampIn = Mathf.Min(clampedChamfer, edgeLenIn * 0.5f);
|
||||
float clampOut = Mathf.Min(clampedChamfer, edgeLenOut * 0.5f);
|
||||
|
||||
// p0 在入边 (prev→curr) 上,距角 clampIn
|
||||
// p3 在出边 (curr→next) 上,距角 clampOut
|
||||
Vector2 dirFromCurrToPrev = (pPrev - pCurr).normalized;
|
||||
Vector2 dirFromCurrToNext = (pNext - pCurr).normalized;
|
||||
|
||||
Vector2 p0 = pCurr + dirFromCurrToPrev * clampIn;
|
||||
Vector2 p3 = pCurr + dirFromCurrToNext * clampOut;
|
||||
|
||||
// 三次 Bezier 控制点:向角的方向内收,使曲线从 p0 平滑过渡到 p3
|
||||
// 控制点位于 p0→pCorner 和 p3→pCorner 之间
|
||||
float c = 0.5522847498f; // 四分之一圆的 Bezier 近似常数
|
||||
float bezierR = Mathf.Min(clampIn, clampOut);
|
||||
Vector2 p1 = p0 + (pCurr - p0).normalized * bezierR * c;
|
||||
Vector2 p2 = p3 + (pCurr - p3).normalized * bezierR * c;
|
||||
|
||||
// 添加 p0(每条边的起点)
|
||||
result.Add(p0);
|
||||
|
||||
// 插值 Bezier 曲线上的中间点
|
||||
for (int j = 1; j < segments; j++)
|
||||
{
|
||||
float t = (float)j / segments;
|
||||
MacroCurve.BezierCurve3(p0, p1, p2, p3, t, out Vector2 pos);
|
||||
result.Add(pos);
|
||||
}
|
||||
|
||||
// 添加 p3(每条边的终点,也是下条边的起点)
|
||||
result.Add(p3);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部辅助结构(避免在 ComputeCorners 中传递过多参数)
|
||||
/// </summary>
|
||||
private struct PrimitiveParamHelper
|
||||
{
|
||||
public Vector2 center;
|
||||
public Vector2 size;
|
||||
public SizeMode sizeMode;
|
||||
public PrimitiveType type;
|
||||
public int sideCount;
|
||||
public float chamferSize;
|
||||
public int chamferSegments;
|
||||
public int faceCount; // 输出:实际生成的三角形数
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ae63ff723ba749448c841d5afe21ac2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 图元类型
|
||||
/// </summary>
|
||||
public enum PrimitiveType : byte
|
||||
{
|
||||
/// <summary>正多边形(内切圆/外切圆)</summary>
|
||||
Polygon,
|
||||
/// <summary>矩形(内切椭圆/外切圆比例)</summary>
|
||||
Rectangle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尺寸模式
|
||||
/// </summary>
|
||||
public enum SizeMode : byte
|
||||
{
|
||||
/// <summary>内切圆 — 半径延伸到各边中点</summary>
|
||||
InscribedCircle,
|
||||
/// <summary>外切圆 — 半径延伸到各顶点</summary>
|
||||
CircumscribedCircle,
|
||||
/// <summary>内切椭圆 — XY各自独立的内切半径</summary>
|
||||
InscribedEllipse,
|
||||
/// <summary>外切圆比例 — 外切圆半径 + XY比例</summary>
|
||||
CircumscribedRatio,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图元参数
|
||||
/// 顶点通道分配:
|
||||
/// uv0: 面片内归一化坐标 (0~1)
|
||||
/// uv1: (centerDist, isBorder)
|
||||
/// normal: (bg.r, bg.g, bg.b) — 0~1
|
||||
/// tangent: (border.r, border.g, border.b, border.a) — 0~1
|
||||
/// </summary>
|
||||
public struct PrimitiveParams
|
||||
{
|
||||
public float axisScaleX;
|
||||
public float axisScaleY;
|
||||
public float sideCount;
|
||||
public float chamferSize;
|
||||
/// <summary>倒角细分段数(仅网格生成使用)</summary>
|
||||
public int chamferSegments;
|
||||
public Color32 bgColor;
|
||||
public Color32 centerColor;
|
||||
public Color32 borderColor;
|
||||
public float borderThickness;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图元缓存条目
|
||||
/// </summary>
|
||||
public struct PrimitiveCacheEntry
|
||||
{
|
||||
/// <summary>在网格顶点缓存中的起始索引</summary>
|
||||
public int startVertexIndex;
|
||||
/// <summary>该图元占用的顶点数</summary>
|
||||
public int vertexCount;
|
||||
/// <summary>在网格索引缓存中的起始索引</summary>
|
||||
public int startIndexIndex;
|
||||
/// <summary>三角形数量</summary>
|
||||
public int triangleCount;
|
||||
|
||||
public PrimitiveType type;
|
||||
public SizeMode sizeMode;
|
||||
|
||||
/// <summary>图元中心(局部坐标)</summary>
|
||||
public Vector2 center;
|
||||
/// <summary>图元尺寸</summary>
|
||||
public Vector2 size;
|
||||
/// <summary>旋转角度(度数,0 = 正上方)</summary>
|
||||
public float angle;
|
||||
|
||||
/// <summary>图元参数</summary>
|
||||
public PrimitiveParams @params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b233b049f5be67e47a89d1f9bee867b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 图元绘制器组件
|
||||
/// 支持多边形/矩形的内切圆/外切圆/椭圆等尺寸模式
|
||||
/// 参数写入 UV2-UV7 通道
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI/UIPrimitiveRenderer", 15)]
|
||||
public class UIPrimitiveRenderer : PrimitiveCacheRendererBase
|
||||
{
|
||||
public PrimitiveType primitiveType = PrimitiveType.Polygon;
|
||||
|
||||
public SizeMode sizeMode = SizeMode.InscribedCircle;
|
||||
public Vector2 size = new Vector2(100f, 100f);
|
||||
public Vector2 centerOffset = Vector2.zero;
|
||||
[Range(-180f, 180f)]
|
||||
public float angle = 0f;
|
||||
|
||||
[Range(3, 64)]
|
||||
public int sideCount = 6;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float chamferSize = 0f;
|
||||
|
||||
[Range(1, 16)]
|
||||
public int chamferSegments = 4;
|
||||
|
||||
public Color bgColor = Color.gray;
|
||||
public Color centerColor = Color.white;
|
||||
public Color borderColor = Color.black;
|
||||
|
||||
[Range(0f, 50f)]
|
||||
public float borderThickness = 2f;
|
||||
|
||||
public bool autoRebuild = true;
|
||||
|
||||
private PrimitiveCacheEntry? m_CurrentEntry;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (autoRebuild) RebuildPrimitive();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (sideCount < 3) sideCount = 3;
|
||||
if (autoRebuild && isActiveAndEnabled)
|
||||
{
|
||||
// 使用 EditorApplication.delayCall 避免 OnValidate 中的 SetVerticesDirty 警告
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this != null && isActiveAndEnabled)
|
||||
RebuildPrimitive();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 重建图元
|
||||
/// </summary>
|
||||
public void RebuildPrimitive()
|
||||
{
|
||||
// 清除旧的图元
|
||||
ClearAll();
|
||||
|
||||
var entry = new PrimitiveCacheEntry
|
||||
{
|
||||
type = primitiveType,
|
||||
sizeMode = sizeMode,
|
||||
center = centerOffset,
|
||||
size = size,
|
||||
angle = angle,
|
||||
@params = new PrimitiveParams
|
||||
{
|
||||
axisScaleX = 1f,
|
||||
axisScaleY = 1f,
|
||||
sideCount = sideCount,
|
||||
chamferSize = chamferSize,
|
||||
chamferSegments = chamferSegments,
|
||||
bgColor = bgColor,
|
||||
centerColor = centerColor,
|
||||
borderColor = borderColor,
|
||||
borderThickness = borderThickness,
|
||||
}
|
||||
};
|
||||
|
||||
AddPrimitive(entry);
|
||||
m_CurrentEntry = entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新图元参数(不触发完全重建,只标记脏)
|
||||
/// </summary>
|
||||
public void UpdateParams(System.Action<PrimitiveCacheEntry> updater)
|
||||
{
|
||||
if (m_Primitives.Count == 0) return;
|
||||
var entry = m_Primitives[0];
|
||||
updater?.Invoke(entry);
|
||||
m_Primitives[0] = entry;
|
||||
SetPrimitiveDirty(0);
|
||||
}
|
||||
|
||||
#region 便捷方法
|
||||
|
||||
/// <summary>
|
||||
/// 快速设置为正多边形
|
||||
/// </summary>
|
||||
public void SetPolygon(int sides, float radius, Color? fill = null, Color? border = null)
|
||||
{
|
||||
primitiveType = PrimitiveType.Polygon;
|
||||
sizeMode = SizeMode.InscribedCircle;
|
||||
size = new Vector2(radius * 2f, radius * 2f);
|
||||
sideCount = Mathf.Max(3, sides);
|
||||
if (fill.HasValue) bgColor = fill.Value;
|
||||
if (border.HasValue) borderColor = border.Value;
|
||||
RebuildPrimitive();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速设置为矩形
|
||||
/// </summary>
|
||||
public void SetRectangle(float width, float height, Color? fill = null, Color? border = null)
|
||||
{
|
||||
primitiveType = PrimitiveType.Rectangle;
|
||||
sizeMode = SizeMode.InscribedEllipse;
|
||||
size = new Vector2(width, height);
|
||||
if (fill.HasValue) bgColor = fill.Value;
|
||||
if (border.HasValue) borderColor = border.Value;
|
||||
RebuildPrimitive();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7f57f5609d9f3a46aa9d50dc1a47904
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user