udpate 0.6.3.3
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线缓存渲染器基类
|
||||
/// 管理 CurveCacheEntry 列表 + 扁平控制点/宽度/箭头缓存 + 脏标记增量重建
|
||||
/// LateUpdate 中只重建脏标记范围内的曲线
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
public abstract class CurveCacheRendererBase : MaskableGraphic
|
||||
{
|
||||
#region 默认材质
|
||||
|
||||
private static Material s_DefaultMaterial;
|
||||
private const string DefaultShaderPath = "XericLibrary/UIGraph/XericUICurve";
|
||||
|
||||
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<CurveCacheEntry> m_Curves = new List<CurveCacheEntry>();
|
||||
|
||||
/// <summary>所有曲线的控制点扁平存储</summary>
|
||||
protected List<Vector2> m_ControlPoints = new List<Vector2>();
|
||||
|
||||
/// <summary>所有曲线的宽度扁平存储(与控制点一一对应)</summary>
|
||||
protected List<float> m_Widths = new List<float>();
|
||||
|
||||
/// <summary>所有曲线的箭头扁平存储</summary>
|
||||
protected List<ArrowHeadData> m_Arrows = new List<ArrowHeadData>();
|
||||
|
||||
/// <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>
|
||||
/// <param name="entry">曲线缓存条目(控制点计数和起始索引需预先设置)</param>
|
||||
/// <param name="controlPoints">控制点数组</param>
|
||||
/// <param name="widths">宽度数组(长度需与控制点一致)</param>
|
||||
/// <param name="arrows">箭头数组(可选)</param>
|
||||
/// <returns>曲线索引</returns>
|
||||
public int AddCurve(CurveCacheEntry entry, Vector2[] controlPoints, float[] widths, ArrowHeadData[] arrows = null)
|
||||
{
|
||||
int index = m_Curves.Count;
|
||||
|
||||
// 填充起始索引
|
||||
entry.controlPointStartIndex = m_ControlPoints.Count;
|
||||
entry.widthStartIndex = m_Widths.Count;
|
||||
entry.arrowStartIndex = (arrows != null && arrows.Length > 0) ? m_Arrows.Count : -1;
|
||||
entry.arrowCount = arrows?.Length ?? 0;
|
||||
entry.controlPointCount = controlPoints.Length;
|
||||
|
||||
m_Curves.Add(entry);
|
||||
m_ControlPoints.AddRange(controlPoints);
|
||||
m_Widths.AddRange(widths);
|
||||
if (arrows != null)
|
||||
m_Arrows.AddRange(arrows);
|
||||
|
||||
MarkDirty(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定曲线
|
||||
/// </summary>
|
||||
public void RemoveCurve(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Curves.Count) return;
|
||||
m_Curves.RemoveAt(index);
|
||||
RebuildAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记指定曲线为脏
|
||||
/// </summary>
|
||||
public void SetCurveDirty(int index)
|
||||
{
|
||||
MarkDirty(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有曲线
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
m_Curves.Clear();
|
||||
m_ControlPoints.Clear();
|
||||
m_Widths.Clear();
|
||||
m_Arrows.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_Curves.Count - 1;
|
||||
if (m_DirtyEndIndex < 0)
|
||||
m_DirtyStartIndex = -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LateUpdate
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
if (m_DirtyStartIndex < 0) return;
|
||||
|
||||
int start = m_DirtyStartIndex;
|
||||
|
||||
// 统计脏范围前的顶点/索引数(这些无需重建)
|
||||
int preVertexCount = 0;
|
||||
int preIndexCount = 0;
|
||||
for (int i = 0; i < start; i++)
|
||||
{
|
||||
preVertexCount += m_Curves[i].vertexCount;
|
||||
preIndexCount += m_Curves[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_Curves.Count; i++)
|
||||
{
|
||||
var entry = m_Curves[i];
|
||||
int vertBefore = tempVerts.Count;
|
||||
int idxBefore = tempIndices.Count;
|
||||
CurveMeshGenerator.GenerateCurve(
|
||||
entry, m_ControlPoints, m_Widths, m_Arrows,
|
||||
tempVerts, tempIndices);
|
||||
var updated = entry;
|
||||
updated.startVertexIndex = vAccum;
|
||||
updated.vertexCount = tempVerts.Count - vertBefore;
|
||||
updated.startIndexIndex = iAccum;
|
||||
updated.triangleCount = (tempIndices.Count - idxBefore) / 3;
|
||||
m_Curves[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: 905c29aca6f80f64f8da8cf8579868ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,333 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线网格生成器
|
||||
/// 将贝塞尔曲线细分为线段带状三角片 + 生成箭头装饰
|
||||
///
|
||||
/// 核心算法:
|
||||
/// 1. 在 t∈[0,1] 上均匀采样 N+1 个点
|
||||
/// 2. 每个采样点计算位置、切线、宽度、颜色
|
||||
/// 3. 切线转 90° 得切法线,沿切法线位移 halfWidth 得左右边顶点
|
||||
/// 4. 顺序连接左右边顶点形成 ribbon 带状:
|
||||
/// 左[i]──右[i]──左[i+1]──右[i+1]──...
|
||||
/// 每个四边形拆两个三角形
|
||||
/// </summary>
|
||||
public static class CurveMeshGenerator
|
||||
{
|
||||
/// <summary>圆圈箭头多边形边数</summary>
|
||||
private const int CircleArrowSides = 8;
|
||||
|
||||
/// <summary>计算切线时的 epsilon 采样偏移</summary>
|
||||
private const float TangentEpsilon = 0.001f;
|
||||
|
||||
/// <summary>
|
||||
/// 生成单条曲线的网格数据
|
||||
/// </summary>
|
||||
public static void GenerateCurve(
|
||||
in CurveCacheEntry entry,
|
||||
List<Vector2> controlPointsPool,
|
||||
List<float> widthsPool,
|
||||
List<ArrowHeadData> arrowsPool,
|
||||
List<UIVertex> vertexList,
|
||||
List<int> indexList)
|
||||
{
|
||||
int segCount = entry.tessellationSegments;
|
||||
if (segCount < 1) segCount = 4;
|
||||
int sampleCount = segCount + 1; // N+1 个采样点
|
||||
|
||||
int cpStart = entry.controlPointStartIndex;
|
||||
int cpCount = entry.controlPointCount;
|
||||
int wStart = entry.widthStartIndex;
|
||||
|
||||
var ctrlPts = new Vector2[cpCount];
|
||||
var wPts = new float[cpCount];
|
||||
for (int i = 0; i < cpCount; i++)
|
||||
{
|
||||
ctrlPts[i] = controlPointsPool[cpStart + i];
|
||||
wPts[i] = widthsPool[wStart + i];
|
||||
}
|
||||
|
||||
int baseVertex = vertexList.Count;
|
||||
UIVertex vert = new UIVertex
|
||||
{
|
||||
normal = Vector3.back,
|
||||
tangent = new Vector4(1f, 0f, 0f, -1f),
|
||||
uv1 = new Vector2(1f, 0f),
|
||||
};
|
||||
|
||||
// 1. 采样 + 生成左右边顶点
|
||||
for (int s = 0; s < sampleCount; s++)
|
||||
{
|
||||
float t = (float)s / segCount;
|
||||
|
||||
Vector2 pos = MacroCurve.BezierCurve(ctrlPts, t); // 曲线上点
|
||||
float width = MacroCurve.BezierCurve(wPts, t); // 当前宽度
|
||||
Vector2 tangent = ComputeTangent(ctrlPts, t); // 切线方向
|
||||
Vector2 normal = new Vector2(-tangent.y, tangent.x); // 切法线(左转90°)
|
||||
|
||||
MacroCurve.BezierCurve1(entry.startColor, entry.endColor, t, out Color col);
|
||||
Color32 color = col;
|
||||
|
||||
float halfW = width * 0.5f;
|
||||
float uv = (float)s / segCount;
|
||||
|
||||
// 左边顶点
|
||||
vert.position = pos - normal * halfW;
|
||||
vert.color = color;
|
||||
vert.uv0 = new Vector2(0f, uv);
|
||||
vertexList.Add(vert);
|
||||
|
||||
// 右边顶点
|
||||
vert.position = pos + normal * halfW;
|
||||
vert.color = color;
|
||||
vert.uv0 = new Vector2(1f, uv);
|
||||
vertexList.Add(vert);
|
||||
}
|
||||
|
||||
// 2. 连接带状三角形
|
||||
// 左[i]=v[i*2], 右[i]=v[i*2+1]
|
||||
// 四边形 i→i+1: (左i, 右i, 左i+1) + (右i, 右i+1, 左i+1)
|
||||
for (int s = 0; s < segCount; s++)
|
||||
{
|
||||
int bv = baseVertex + s * 2;
|
||||
|
||||
indexList.Add(bv + 0); // 左i
|
||||
indexList.Add(bv + 1); // 右i
|
||||
indexList.Add(bv + 2); // 左i+1
|
||||
|
||||
indexList.Add(bv + 1); // 右i
|
||||
indexList.Add(bv + 3); // 右i+1
|
||||
indexList.Add(bv + 2); // 左i+1
|
||||
}
|
||||
|
||||
// 3. 生成箭头
|
||||
if (entry.arrowCount > 0 && entry.arrowStartIndex >= 0)
|
||||
{
|
||||
for (int a = 0; a < entry.arrowCount; a++)
|
||||
{
|
||||
var arrow = arrowsPool[entry.arrowStartIndex + a];
|
||||
GenerateArrow(arrow, ctrlPts, vertexList, indexList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算曲线上 t 处的切线方向(差分法)
|
||||
/// </summary>
|
||||
private static Vector2 ComputeTangent(Vector2[] ctrlPts, float t)
|
||||
{
|
||||
float t0 = Mathf.Max(0f, t - TangentEpsilon);
|
||||
float t1 = Mathf.Min(1f, t + TangentEpsilon);
|
||||
Vector2 tangent = MacroCurve.BezierCurve(ctrlPts, t1) - MacroCurve.BezierCurve(ctrlPts, t0);
|
||||
if (tangent.sqrMagnitude < float.Epsilon)
|
||||
tangent = Vector2.up;
|
||||
else
|
||||
tangent.Normalize();
|
||||
return tangent;
|
||||
}
|
||||
|
||||
#region 箭头
|
||||
|
||||
private static void GenerateArrow(
|
||||
ArrowHeadData arrow,
|
||||
Vector2[] controlPoints,
|
||||
List<UIVertex> vertexList,
|
||||
List<int> indexList)
|
||||
{
|
||||
float t = Mathf.Clamp01(arrow.progress);
|
||||
Vector2 pos = MacroCurve.BezierCurve(controlPoints, t);
|
||||
Vector2 tangent = ComputeTangent(controlPoints, t);
|
||||
|
||||
if (arrow.reversed)
|
||||
tangent = -tangent;
|
||||
|
||||
// 深度补偿:半长偏移(箭头中心在本地x=0,尖端在+width*0.5,尾部在-width*0.5)
|
||||
pos += tangent * arrow.width * 0.5f * arrow.depthCompensation;
|
||||
|
||||
int baseVert = vertexList.Count;
|
||||
GenerateArrowShape(arrow.shape, pos, tangent, arrow.width, arrow.height, arrow.color,
|
||||
vertexList, indexList, baseVert, t);
|
||||
}
|
||||
|
||||
private static void GenerateArrowShape(
|
||||
ArrowShape shape,
|
||||
Vector2 position,
|
||||
Vector2 tangent,
|
||||
float width,
|
||||
float height,
|
||||
Color32 color,
|
||||
List<UIVertex> vertexList,
|
||||
List<int> indexList,
|
||||
int baseVertex,
|
||||
float progressT)
|
||||
{
|
||||
float cos = tangent.x;
|
||||
float sin = tangent.y;
|
||||
|
||||
Vector2 TransformPoint(float ux, float uy)
|
||||
{
|
||||
float x = ux * width;
|
||||
float y = uy * height;
|
||||
float rx = x * cos - y * sin;
|
||||
float ry = x * sin + y * cos;
|
||||
return position + new Vector2(rx, ry);
|
||||
}
|
||||
|
||||
UIVertex vert = new UIVertex
|
||||
{
|
||||
color = color,
|
||||
normal = Vector3.back,
|
||||
tangent = new Vector4(1f, 0f, 0f, -1f),
|
||||
uv0 = new Vector2(0.5f, progressT),
|
||||
uv1 = new Vector2(0f, 1f)
|
||||
};
|
||||
|
||||
switch (shape)
|
||||
{
|
||||
case ArrowShape.Triangle:
|
||||
{
|
||||
vert.position = TransformPoint(0.5f, 0f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f, -0.3f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f, 0.3f);
|
||||
vertexList.Add(vert);
|
||||
|
||||
indexList.Add(baseVertex + 0);
|
||||
indexList.Add(baseVertex + 1);
|
||||
indexList.Add(baseVertex + 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case ArrowShape.HollowTriangle:
|
||||
{
|
||||
float tailOff = -0.1f;
|
||||
|
||||
// 外三角
|
||||
vert.position = TransformPoint(0.5f, 0f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f, -0.3f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f, 0.3f);
|
||||
vertexList.Add(vert);
|
||||
|
||||
// 内三角(缩小 + 向尾部偏移)
|
||||
vert.position = TransformPoint(0.5f * 0.5f + tailOff, 0f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f * 0.5f + tailOff, -0.3f * 0.5f * 0.6f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-0.5f * 0.5f + tailOff, 0.3f * 0.5f * 0.6f);
|
||||
vertexList.Add(vert);
|
||||
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 5);
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 2);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 4);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 3);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 3); indexList.Add(baseVertex + 5);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case ArrowShape.Arrow:
|
||||
{
|
||||
float headBaseU = 0.5f - 0.35f; // 头部基座 x=0.15
|
||||
float shaftEndU = -0.8f; // 杆尾
|
||||
|
||||
vert.position = TransformPoint(0.5f, 0f);
|
||||
vertexList.Add(vert); // 0: 尖端
|
||||
vert.position = TransformPoint(headBaseU, -0.25f);
|
||||
vertexList.Add(vert); // 1: 左翼
|
||||
vert.position = TransformPoint(headBaseU, 0.25f);
|
||||
vertexList.Add(vert); // 2: 右翼
|
||||
vert.position = TransformPoint(headBaseU, -0.06f);
|
||||
vertexList.Add(vert); // 3: 杆起点下
|
||||
vert.position = TransformPoint(headBaseU, 0.06f);
|
||||
vertexList.Add(vert); // 4: 杆起点上
|
||||
vert.position = TransformPoint(shaftEndU, -0.06f);
|
||||
vertexList.Add(vert); // 5: 杆尾下
|
||||
vert.position = TransformPoint(shaftEndU, 0.06f);
|
||||
vertexList.Add(vert); // 6: 杆尾上
|
||||
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
|
||||
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 6);
|
||||
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 1);
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 5);
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 3);
|
||||
break;
|
||||
}
|
||||
|
||||
case ArrowShape.Circle:
|
||||
{
|
||||
float r = 0.4f;
|
||||
vert.position = TransformPoint(0f, 0f);
|
||||
vertexList.Add(vert);
|
||||
for (int i = 0; i < CircleArrowSides; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / CircleArrowSides;
|
||||
vert.position = TransformPoint(Mathf.Cos(angle) * r, Mathf.Sin(angle) * r);
|
||||
vertexList.Add(vert);
|
||||
}
|
||||
for (int i = 0; i < CircleArrowSides; i++)
|
||||
{
|
||||
int next = (i + 1) % CircleArrowSides;
|
||||
indexList.Add(baseVertex + 0);
|
||||
indexList.Add(baseVertex + 1 + i);
|
||||
indexList.Add(baseVertex + 1 + next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ArrowShape.Square:
|
||||
{
|
||||
float hs = 0.35f;
|
||||
vert.position = TransformPoint(-hs, -hs);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(hs, -hs);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(hs, hs);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(-hs, hs);
|
||||
vertexList.Add(vert);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 3);
|
||||
break;
|
||||
}
|
||||
|
||||
case ArrowShape.ReverseArrow:
|
||||
{
|
||||
float rHeadBaseU = 0.5f - 0.35f;
|
||||
float rShaftEndU = -0.8f;
|
||||
|
||||
vert.position = TransformPoint(0.5f, 0f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rHeadBaseU, -0.25f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rHeadBaseU, 0.25f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rHeadBaseU, -0.06f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rHeadBaseU, 0.06f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rShaftEndU, -0.06f);
|
||||
vertexList.Add(vert);
|
||||
vert.position = TransformPoint(rShaftEndU, 0.06f);
|
||||
vertexList.Add(vert);
|
||||
|
||||
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
|
||||
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 6);
|
||||
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 1);
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 5);
|
||||
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b0e0346140ad4847a65df78853dd3dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 箭头形状枚举
|
||||
/// </summary>
|
||||
public enum ArrowShape : byte
|
||||
{
|
||||
/// <summary>实心三角箭头</summary>
|
||||
Triangle,
|
||||
/// <summary>空心三角箭头</summary>
|
||||
HollowTriangle,
|
||||
/// <summary>箭矢箭头(翼+杆)</summary>
|
||||
Arrow,
|
||||
/// <summary>圆形头</summary>
|
||||
Circle,
|
||||
/// <summary>方形头</summary>
|
||||
Square,
|
||||
/// <summary>反向箭头(箭尾)</summary>
|
||||
ReverseArrow,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箭头装饰数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct ArrowHeadData
|
||||
{
|
||||
/// <summary>箭头形状</summary>
|
||||
public ArrowShape shape;
|
||||
/// <summary>是否反向</summary>
|
||||
public bool reversed;
|
||||
/// <summary>箭头出现在曲线上的位置 (0~1)</summary>
|
||||
[Range(0f, 1f)]
|
||||
public float progress;
|
||||
/// <summary>箭头宽度(沿切线方向)</summary>
|
||||
public float width;
|
||||
/// <summary>箭头高度(垂直切线方向)</summary>
|
||||
public float height;
|
||||
/// <summary>深度补偿:-1~1,箭头沿切线偏移 width*0.5*depth 距离。
|
||||
/// -1:尾部对准曲线点(头部向前伸)
|
||||
/// 0:中心对准曲线点
|
||||
/// +1:头部对准曲线点(尾部向后拉)</summary>
|
||||
[Range(-1f, 1f)]
|
||||
public float depthCompensation;
|
||||
/// <summary>箭头颜色</summary>
|
||||
public Color32 color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 曲线缓存条目
|
||||
/// 控制点、宽度、箭头数据统一存放在外部扁平列表中,
|
||||
/// 本结构体只记录起始索引和计数。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct CurveCacheEntry
|
||||
{
|
||||
/// <summary>在网格顶点缓存中的起始索引(由 Base 类回填)</summary>
|
||||
public int startVertexIndex;
|
||||
/// <summary>该曲线占用的顶点数(由 Base 类回填)</summary>
|
||||
public int vertexCount;
|
||||
/// <summary>在网格索引缓存中的起始索引(由 Base 类回填)</summary>
|
||||
public int startIndexIndex;
|
||||
/// <summary>三角形数量(由 Base 类回填)</summary>
|
||||
public int triangleCount;
|
||||
|
||||
/// <summary>控制点数量(≥4,默认三阶贝塞尔)</summary>
|
||||
public int controlPointCount;
|
||||
/// <summary>在统一控制点缓存中的起始索引</summary>
|
||||
public int controlPointStartIndex;
|
||||
/// <summary>在统一宽度缓存中的起始索引</summary>
|
||||
public int widthStartIndex;
|
||||
/// <summary>起始颜色</summary>
|
||||
public Color32 startColor;
|
||||
/// <summary>结束颜色</summary>
|
||||
public Color32 endColor;
|
||||
/// <summary>在统一箭头缓存中的起始索引(-1 表示无箭头)</summary>
|
||||
public int arrowStartIndex;
|
||||
/// <summary>箭头数量</summary>
|
||||
public int arrowCount;
|
||||
/// <summary>曲线细分段数</summary>
|
||||
public int tessellationSegments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6ce7575b6b9e6a45bdba82ae35018e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线绘制器组件
|
||||
/// 支持多阶贝塞尔曲线,可配置控制点、宽度渐变、颜色和箭头装饰
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI/UICurveRenderer", 16)]
|
||||
public class UICurveRenderer : CurveCacheRendererBase
|
||||
{
|
||||
/// <summary>控制点,(x,y)=位置, z=宽度</summary>
|
||||
public Vector3[] controlPoints = new Vector3[]
|
||||
{
|
||||
new Vector3(-150f, 0f, 10f),
|
||||
new Vector3(-50f, 100f, 10f),
|
||||
new Vector3(50f, -100f, 10f),
|
||||
new Vector3(150f, 0f, 10f),
|
||||
};
|
||||
|
||||
public Color32 startColor = Color.white;
|
||||
public Color32 endColor = Color.white;
|
||||
|
||||
[Range(4, 128)]
|
||||
public int tessellationSegments = 32;
|
||||
|
||||
public List<ArrowHeadData> arrows = new List<ArrowHeadData>();
|
||||
|
||||
public bool autoRebuild = true;
|
||||
|
||||
private CurveCacheEntry? m_CurrentEntry;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (autoRebuild) RebuildCurve();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (controlPoints == null || controlPoints.Length < 2)
|
||||
controlPoints = new Vector3[] { new Vector3(0f, 0f, 10f), new Vector3(100f, 0f, 10f) };
|
||||
if (tessellationSegments < 4) tessellationSegments = 4;
|
||||
if (autoRebuild && isActiveAndEnabled)
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this != null && isActiveAndEnabled)
|
||||
RebuildCurve();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 重建曲线
|
||||
/// </summary>
|
||||
public void RebuildCurve()
|
||||
{
|
||||
ClearAll();
|
||||
|
||||
if (controlPoints == null || controlPoints.Length < 2) return;
|
||||
|
||||
// 从 Vector3[] 中提取 Vector2[] 位置和 float[] 宽度
|
||||
int count = controlPoints.Length;
|
||||
var ctrlPts2 = new Vector2[count];
|
||||
var widths = new float[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ctrlPts2[i] = (Vector2)controlPoints[i];
|
||||
widths[i] = controlPoints[i].z;
|
||||
}
|
||||
|
||||
var entry = new CurveCacheEntry
|
||||
{
|
||||
startColor = startColor,
|
||||
endColor = endColor,
|
||||
tessellationSegments = tessellationSegments,
|
||||
};
|
||||
|
||||
AddCurve(entry, ctrlPts2, widths, arrows?.ToArray());
|
||||
m_CurrentEntry = m_Curves.Count > 0 ? m_Curves[0] : (CurveCacheEntry?)null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置控制点(z=宽度)
|
||||
/// </summary>
|
||||
public void SetControlPoints(Vector3[] points)
|
||||
{
|
||||
controlPoints = points;
|
||||
RebuildCurve();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加箭头
|
||||
/// </summary>
|
||||
public void AddArrow(ArrowHeadData arrow)
|
||||
{
|
||||
if (arrows == null) arrows = new List<ArrowHeadData>();
|
||||
arrows.Add(arrow);
|
||||
RebuildCurve();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除箭头
|
||||
/// </summary>
|
||||
public void RemoveArrow(int index)
|
||||
{
|
||||
if (arrows == null || index < 0 || index >= arrows.Count) return;
|
||||
arrows.RemoveAt(index);
|
||||
RebuildCurve();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3189cafe7a5628048b2243986703e3e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user