535 lines
17 KiB
C#
535 lines
17 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
#if ENABLE_BURST
|
||
using Unity.Jobs;
|
||
using Unity.Collections;
|
||
#endif
|
||
|
||
namespace XericLibrary.Runtime.UIGraph
|
||
{
|
||
/// <summary>
|
||
/// 基于缓存结构的线段渲染器基类
|
||
/// 数据由 NativeArray 管理,支持脏标记范围更新和 Jobs 并行生成
|
||
/// </summary>
|
||
[RequireComponent(typeof(CanvasRenderer))]
|
||
public abstract class CacheLineRendererBase : MaskableGraphic
|
||
{
|
||
#region 数据缓存
|
||
|
||
/// <summary>
|
||
/// 所有线段端点的扁平缓存
|
||
/// </summary>
|
||
protected List<LineVertexData> m_VertexData = new List<LineVertexData>();
|
||
|
||
/// <summary>
|
||
/// 线段范围数组
|
||
/// </summary>
|
||
protected List<LineSegmentRange> m_SegmentRanges = new List<LineSegmentRange>();
|
||
|
||
/// <summary>
|
||
/// 待添加的端点(Begin/End API 模式用)
|
||
/// </summary>
|
||
protected List<LineVertexData> m_PendingVertices = new List<LineVertexData>();
|
||
|
||
/// <summary>
|
||
/// 当前 Begin/End 模式的 uvMode
|
||
/// </summary>
|
||
protected UVMode m_PendingUVMode = UVMode.ByDistance;
|
||
|
||
/// <summary>
|
||
/// 是否正在 Begin/End 会话中
|
||
/// </summary>
|
||
protected bool m_IsBuilding;
|
||
|
||
/// <summary>
|
||
/// 模型顶点缓存 (segmentCount × 5)
|
||
/// </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_DirtyStartSegment = -1;
|
||
|
||
/// <summary>
|
||
/// 脏标记范围终点(线段范围索引)
|
||
/// </summary>
|
||
protected int m_DirtyEndSegment = -1;
|
||
|
||
/// <summary>
|
||
/// 是否使用 Jobs 模式
|
||
/// </summary>
|
||
protected static bool UseJobs =>
|
||
#if ENABLE_BURST
|
||
SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.Null &&
|
||
Application.platform != RuntimePlatform.WebGLPlayer;
|
||
#else
|
||
false;
|
||
#endif
|
||
|
||
#endregion
|
||
|
||
#region 线段添加
|
||
|
||
/// <summary>
|
||
/// 批量添加一根完整线段
|
||
/// </summary>
|
||
/// <param name="vertices">线段端点列表(至少2个)</param>
|
||
/// <param name="uvMode">UV1 均分模式</param>
|
||
/// <returns>线段范围索引</returns>
|
||
public int AddLine(LineVertexData[] vertices, UVMode uvMode = UVMode.ByDistance)
|
||
{
|
||
if (vertices == null || vertices.Length < 2)
|
||
return -1;
|
||
|
||
int startIndex = m_VertexData.Count;
|
||
m_VertexData.AddRange(vertices);
|
||
|
||
var range = new LineSegmentRange
|
||
{
|
||
startVertexIndex = startIndex,
|
||
vertexCount = vertices.Length,
|
||
uvMode = uvMode
|
||
};
|
||
|
||
int rangeIndex = m_SegmentRanges.Count;
|
||
m_SegmentRanges.Add(range);
|
||
|
||
MarkDirty(rangeIndex);
|
||
return rangeIndex;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始画线(记录模式)
|
||
/// </summary>
|
||
public void BeginLine(UVMode uvMode = UVMode.ByDistance)
|
||
{
|
||
m_IsBuilding = true;
|
||
m_PendingUVMode = uvMode;
|
||
m_PendingVertices.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加画线节点到当前记录
|
||
/// </summary>
|
||
public void AddLineNode(Vector2 position, Color32 color, float thickness, Vector4 userData = default)
|
||
{
|
||
if (!m_IsBuilding) return;
|
||
m_PendingVertices.Add(new LineVertexData
|
||
{
|
||
position = position,
|
||
color = color,
|
||
thickness = thickness,
|
||
userData = userData
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加画线节点
|
||
/// </summary>
|
||
public void AddLineNode(LineVertexData node)
|
||
{
|
||
if (!m_IsBuilding) return;
|
||
m_PendingVertices.Add(node);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结束画线,将挂起列表写入缓存
|
||
/// </summary>
|
||
public void EndLine()
|
||
{
|
||
if (!m_IsBuilding) return;
|
||
m_IsBuilding = false;
|
||
|
||
if (m_PendingVertices.Count < 2)
|
||
{
|
||
m_PendingVertices.Clear();
|
||
return;
|
||
}
|
||
|
||
int startIndex = m_VertexData.Count;
|
||
m_VertexData.AddRange(m_PendingVertices);
|
||
|
||
var range = new LineSegmentRange
|
||
{
|
||
startVertexIndex = startIndex,
|
||
vertexCount = m_PendingVertices.Count,
|
||
uvMode = m_PendingUVMode
|
||
};
|
||
|
||
int rangeIndex = m_SegmentRanges.Count;
|
||
m_SegmentRanges.Add(range);
|
||
m_PendingVertices.Clear();
|
||
|
||
MarkDirty(rangeIndex);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 线段管理
|
||
|
||
/// <summary>
|
||
/// 移除指定线段
|
||
/// </summary>
|
||
public void RemoveLine(int segmentRangeIndex)
|
||
{
|
||
if (segmentRangeIndex < 0 || segmentRangeIndex >= m_SegmentRanges.Count)
|
||
return;
|
||
|
||
m_SegmentRanges.RemoveAt(segmentRangeIndex);
|
||
// 移除后后续所有线段索引发生变化,需完全重建
|
||
RebuildAll();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有线段
|
||
/// </summary>
|
||
public void ClearAll()
|
||
{
|
||
m_VertexData.Clear();
|
||
m_SegmentRanges.Clear();
|
||
m_MeshVertexCache.Clear();
|
||
m_MeshIndexCache.Clear();
|
||
m_DirtyStartSegment = -1;
|
||
m_DirtyEndSegment = -1;
|
||
SetVerticesDirty();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 标记指定线段为脏(需要重建)
|
||
/// </summary>
|
||
public void SetSegmentDirty(int segmentRangeIndex)
|
||
{
|
||
MarkDirty(segmentRangeIndex);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 脏标记缓存
|
||
|
||
/// <summary>
|
||
/// 标记脏范围(合并到现有范围)
|
||
/// </summary>
|
||
protected void MarkDirty(int segmentIndex)
|
||
{
|
||
if (m_DirtyStartSegment < 0 || segmentIndex < m_DirtyStartSegment)
|
||
m_DirtyStartSegment = segmentIndex;
|
||
if (m_DirtyEndSegment < 0 || segmentIndex > m_DirtyEndSegment)
|
||
m_DirtyEndSegment = segmentIndex;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 标记所有线段为脏(下次 LateUpdate 全量重绘)
|
||
/// 适用于:更新顶点数据后,不改变元素数量的重绘
|
||
/// </summary>
|
||
public void RebuildAll()
|
||
{
|
||
m_DirtyStartSegment = 0;
|
||
m_DirtyEndSegment = m_SegmentRanges.Count - 1;
|
||
if (m_DirtyEndSegment < 0)
|
||
m_DirtyStartSegment = -1;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 顶点生成
|
||
|
||
protected virtual void LateUpdate()
|
||
{
|
||
if (m_DirtyStartSegment < 0)
|
||
return;
|
||
|
||
// 1) 统计脏范围内所有子段数(一个 range 有 vertexCount-1 个子段)
|
||
int totalDirtySegments = 0;
|
||
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
|
||
totalDirtySegments += m_SegmentRanges[ri].vertexCount - 1;
|
||
|
||
if (totalDirtySegments == 0)
|
||
{
|
||
m_DirtyStartSegment = -1;
|
||
m_DirtyEndSegment = -1;
|
||
return;
|
||
}
|
||
|
||
// 2) 确保 mesh 顶点缓存足够大
|
||
int totalSegments = GetTotalSegmentCount();
|
||
int neededVertices = totalSegments * LineMeshGenerator.VerticesPerSegment;
|
||
if (m_MeshVertexCache.Count < neededVertices)
|
||
{
|
||
int growBy = neededVertices - m_MeshVertexCache.Count;
|
||
for (int i = 0; i < growBy; i++)
|
||
m_MeshVertexCache.Add(new UIVertex());
|
||
}
|
||
|
||
m_MeshIndexCache.Clear();
|
||
|
||
// 3) 按子段构建 SegmentJobInput(同时计算 ByDistance 的 totalDist 缓存)
|
||
int globalSegBase = GetGlobalSegmentOffset(m_DirtyStartSegment);
|
||
var inputs = new List<SegmentJobInput>(totalDirtySegments);
|
||
int inSegIdx = 0;
|
||
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
|
||
{
|
||
var range = m_SegmentRanges[ri];
|
||
float cachedTotalDist = (range.uvMode == UVMode.ByDistance)
|
||
? CalculateRangeTotalDistance(range)
|
||
: -1f;
|
||
int subSegCount = range.vertexCount - 1;
|
||
for (int si = 0; si < subSegCount; si++)
|
||
{
|
||
inputs.Add(BuildSegmentInput(range, si,
|
||
globalSegBase + inSegIdx, totalSegments, cachedTotalDist));
|
||
inSegIdx++;
|
||
}
|
||
}
|
||
|
||
// 4) 生成顶点(串行或 Jobs)
|
||
if (UseJobs)
|
||
{
|
||
#if ENABLE_BURST
|
||
GenerateMeshJobs(inputs, globalSegBase);
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
GenerateMeshSerial(inputs, globalSegBase);
|
||
}
|
||
|
||
// 5) 生成三角形索引
|
||
GenerateIndicesForRange(m_DirtyStartSegment, m_DirtyEndSegment,
|
||
globalSegBase);
|
||
|
||
// 清除脏标记
|
||
SetVerticesDirty();
|
||
m_DirtyStartSegment = -1;
|
||
m_DirtyEndSegment = -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 串行生成顶点
|
||
/// </summary>
|
||
private void GenerateMeshSerial(List<SegmentJobInput> inputs, int globalSegBase)
|
||
{
|
||
var tempVerts = new UIVertex[LineMeshGenerator.VerticesPerSegment];
|
||
for (int i = 0; i < inputs.Count; i++)
|
||
{
|
||
int meshVertStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
|
||
LineMeshGenerator.GenerateSingleSegment(inputs[i], tempVerts, 0);
|
||
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
|
||
{
|
||
int cacheIdx = meshVertStart + v;
|
||
if (cacheIdx < m_MeshVertexCache.Count)
|
||
m_MeshVertexCache[cacheIdx] = tempVerts[v];
|
||
}
|
||
}
|
||
}
|
||
|
||
#if ENABLE_BURST
|
||
/// <summary>
|
||
/// Jobs 并行生成顶点
|
||
/// </summary>
|
||
private void GenerateMeshJobs(List<SegmentJobInput> inputs, int globalSegBase)
|
||
{
|
||
int count = inputs.Count;
|
||
int outputSize = count * LineMeshGenerator.VerticesPerSegment;
|
||
|
||
var nativeInputs = new NativeArray<SegmentJobInput>(count, Allocator.TempJob);
|
||
var nativeOutputs = new NativeArray<UIVertex>(outputSize, Allocator.TempJob);
|
||
|
||
for (int i = 0; i < count; i++)
|
||
nativeInputs[i] = inputs[i];
|
||
|
||
var handle = LineMeshGenerator.GenerateSegmentsJobs(nativeInputs, nativeOutputs);
|
||
handle.Complete();
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
int srcStart = i * LineMeshGenerator.VerticesPerSegment;
|
||
int dstStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
|
||
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
|
||
{
|
||
if (dstStart + v < m_MeshVertexCache.Count)
|
||
m_MeshVertexCache[dstStart + v] = nativeOutputs[srcStart + v];
|
||
}
|
||
}
|
||
|
||
nativeInputs.Dispose();
|
||
nativeOutputs.Dispose();
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 生成指定 range 范围中所有子段的三角形索引
|
||
/// 每条线的首个子段不生成转角连接三角形
|
||
/// </summary>
|
||
private void GenerateIndicesForRange(int startRange, int endRange,
|
||
int globalSegBase)
|
||
{
|
||
int segIdx = globalSegBase;
|
||
for (int ri = startRange; ri <= endRange; ri++)
|
||
{
|
||
int subSegCount = m_SegmentRanges[ri].vertexCount - 1;
|
||
for (int si = 0; si < subSegCount; si++)
|
||
{
|
||
// 子段 si == 0 是该条线的首段 → 无前驱同线段 → hasPrevInLine = false
|
||
bool hasPrevInLine = (si > 0);
|
||
LineMeshGenerator.GenerateSegmentIndices(segIdx, hasPrevInLine, m_MeshIndexCache);
|
||
segIdx++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算某条线段包含的段数(端点数量 - 1)
|
||
/// </summary>
|
||
protected static int GetSegmentCountForRange(in LineSegmentRange range) => range.vertexCount - 1;
|
||
|
||
/// <summary>
|
||
/// 获取总段数(所有线段累加)
|
||
/// </summary>
|
||
protected int GetTotalSegmentCount()
|
||
{
|
||
int count = 0;
|
||
for (int i = 0; i < m_SegmentRanges.Count; i++)
|
||
count += GetSegmentCountForRange(m_SegmentRanges[i]);
|
||
return count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定线段范围的全局段偏移
|
||
/// </summary>
|
||
protected int GetGlobalSegmentOffset(int rangeIndex)
|
||
{
|
||
int offset = 0;
|
||
for (int i = 0; i < rangeIndex; i++)
|
||
offset += GetSegmentCountForRange(m_SegmentRanges[i]);
|
||
return offset;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算指定 range 的总距离(用于 ByDistance UV 模式)
|
||
/// </summary>
|
||
private float CalculateRangeTotalDistance(in LineSegmentRange range)
|
||
{
|
||
float total = 0f;
|
||
int segCount = range.vertexCount - 1;
|
||
for (int i = 0; i < segCount; i++)
|
||
{
|
||
int idx = range.startVertexIndex + i;
|
||
total += Vector2.Distance(m_VertexData[idx].position,
|
||
m_VertexData[idx + 1].position);
|
||
}
|
||
return total;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建指定范围内第 inSegmentIndex 段的输入参数
|
||
/// </summary>
|
||
protected SegmentJobInput BuildSegmentInput(
|
||
in LineSegmentRange range, int inSegmentIndex,
|
||
int globalSegmentIndex, int totalSegments,
|
||
float cachedTotalDist = -1f)
|
||
{
|
||
int startIdx = range.startVertexIndex + inSegmentIndex;
|
||
var start = m_VertexData[startIdx];
|
||
var end = m_VertexData[startIdx + 1];
|
||
|
||
var input = new SegmentJobInput
|
||
{
|
||
start = start,
|
||
end = end,
|
||
segmentIndex = globalSegmentIndex,
|
||
totalSegmentCount = totalSegments,
|
||
uvMode = range.uvMode,
|
||
hasPrev = inSegmentIndex > 0 ? (byte)1 : (byte)0,
|
||
hasNext = (inSegmentIndex < range.vertexCount - 2) ? (byte)1 : (byte)0
|
||
};
|
||
|
||
if (input.hasPrev == 1)
|
||
input.prevEnd = m_VertexData[startIdx - 1];
|
||
if (input.hasNext == 1)
|
||
input.nextStart = m_VertexData[startIdx + 2];
|
||
|
||
// 计算 startLineUV / endLineUV
|
||
int segCount = GetSegmentCountForRange(range);
|
||
if (range.uvMode == UVMode.BySegment)
|
||
{
|
||
float t0 = (float)inSegmentIndex / segCount;
|
||
float t1 = (float)(inSegmentIndex + 1) / segCount;
|
||
input.startLineUV = t0;
|
||
input.endLineUV = t1;
|
||
}
|
||
else // ByDistance
|
||
{
|
||
float totalDist = cachedTotalDist >= 0f
|
||
? cachedTotalDist
|
||
: CalculateRangeTotalDistance(range);
|
||
|
||
if (totalDist < float.Epsilon)
|
||
{
|
||
input.startLineUV = (float)inSegmentIndex / segCount;
|
||
input.endLineUV = (float)(inSegmentIndex + 1) / segCount;
|
||
}
|
||
else
|
||
{
|
||
float distSoFar = 0f;
|
||
for (int i = 0; i < inSegmentIndex; i++)
|
||
{
|
||
int idx = range.startVertexIndex + i;
|
||
distSoFar += Vector2.Distance(m_VertexData[idx].position,
|
||
m_VertexData[idx + 1].position);
|
||
}
|
||
float segDist = Vector2.Distance(start.position, end.position);
|
||
input.startLineUV = distSoFar / totalDist;
|
||
input.endLineUV = (distSoFar + segDist) / totalDist;
|
||
}
|
||
}
|
||
|
||
return input;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region OnPopulateMesh
|
||
|
||
protected override void OnPopulateMesh(VertexHelper vh)
|
||
{
|
||
vh.Clear();
|
||
|
||
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0)
|
||
return;
|
||
|
||
// 将缓存的顶点和索引写入 VertexHelper
|
||
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 生命周期
|
||
|
||
protected override void OnEnable()
|
||
{
|
||
base.OnEnable();
|
||
RebuildAll();
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
base.OnDisable();
|
||
}
|
||
|
||
protected override void OnDestroy()
|
||
{
|
||
base.OnDestroy();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|