Files
XericLibrary-WebGL-Publish/Runtime/Generation/Line/LineMeshGenerator.cs
T
2026-07-20 11:08:12 +08:00

275 lines
10 KiB
C#

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.UI;
#if ENABLE_BURST
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 线段→模型顶点生成器
/// 核心算法:每段线生成 5 个模型顶点 + 3 个三角形
/// </summary>
public static class LineMeshGenerator
{
/// <summary>
/// 每段线的模型顶点数
/// </summary>
public const int VerticesPerSegment = 5;
/// <summary>
/// 每段线的三角形索引数(3 个三角形 × 3 索引)
/// </summary>
public const int IndicesPerSegment = 9;
/// <summary>
/// 计算单段线段对应的 5 个模型顶点(串行 &amp; Job 共用)
/// </summary>
/// <param name="input">线段生成参数</param>
/// <param name="outputVertices">输出顶点数组,须预留 VerticesPerSegment 个位置</param>
/// <param name="outputStart">写入 outputVertices 的起始偏移</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GenerateSingleSegment(in SegmentJobInput input,
UIVertex[] outputVertices, int outputStart = 0)
{
Vector2 startPos = input.start.position;
Vector2 endPos = input.end.position;
Vector2 dir = endPos - startPos;
float length = dir.magnitude;
if (length < float.Epsilon)
{
// 退化为零长度线段,生成空顶点
for (int i = 0; i < VerticesPerSegment; i++)
{
outputVertices[outputStart + i] = new UIVertex
{
position = startPos,
color = Color.clear,
uv0 = Vector2.zero,
uv1 = Vector2.zero,
uv2 = Vector4.zero,
uv3 = Vector4.zero
};
}
return;
}
Vector2 normal = dir / length;
Vector2 subTangent = Vector2.Perpendicular(normal);
// 起点和终点的实际宽度(两端可不同)
float halfW0 = input.start.thickness * 0.5f;
float halfW1 = input.end.thickness * 0.5f;
// 5 顶点布局
// V0 = start + normal * -halfW (左下)
// V1 = start + normal + halfW (左上)
// V2 = end + normal * -halfW (右下)
// V3 = end + normal + halfW (右上)
// V4 = end (中心 → 转角连接)
Vector3 v0 = startPos - subTangent * halfW0;
Vector3 v1 = startPos + subTangent * halfW0;
Vector3 v2 = endPos - subTangent * halfW1;
Vector3 v3 = endPos + subTangent * halfW1;
Vector3 v4 = endPos;
Color32 c0 = Color32.Lerp(input.start.color, input.end.color, 0f);
Color32 c1 = c0;
Color32 c2 = Color32.Lerp(input.start.color, input.end.color, 1f);
Color32 c3 = c2;
Color32 c4 = c2;
float uvSX = 0f;
float uvEX = 1f;
float uvSY = input.startLineUV;
float uvEY = input.endLineUV;
UIVertex vert = new UIVertex
{
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f)
};
Vector4 userDataStart = input.start.userData;
Vector4 userDataEnd = input.end.userData;
// V0
vert.position = v0;
vert.color = c0;
vert.uv0 = new Vector2(uvSX, uvSY);
vert.uv1 = new Vector2(0f, uvSY); // 整线UV (u=0 表示线的起点侧)
vert.uv2 = userDataStart;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 0] = vert;
// V1
vert.position = v1;
vert.color = c1;
vert.uv0 = new Vector2(uvEX, uvSY);
vert.uv1 = new Vector2(1f, uvSY); // 整线UV (u=1 表示线的终点侧)
vert.uv2 = userDataStart;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 1] = vert;
// V2
vert.position = v2;
vert.color = c2;
vert.uv0 = new Vector2(uvSX, uvEY);
vert.uv1 = new Vector2(0f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 2] = vert;
// V3
vert.position = v3;
vert.color = c3;
vert.uv0 = new Vector2(uvEX, uvEY);
vert.uv1 = new Vector2(1f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 3] = vert;
// V4
vert.position = v4;
vert.color = c4;
vert.uv0 = new Vector2(0.5f, uvEY);
vert.uv1 = new Vector2(0.5f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 4] = vert;
}
/// <summary>
/// 生成单段线段的三角形索引(写入 indexList)
/// </summary>
/// <param name="segmentIndex">子段全局索引</param>
/// <param name="hasPrevInLine">上一子段是否属于同一条线(控制转角连接三角形)</param>
/// <param name="indexList">输出的三角形索引列表</param>
public static void GenerateSegmentIndices(int segmentIndex, bool hasPrevInLine, List<int> indexList)
{
int baseVert = segmentIndex * VerticesPerSegment;
// 三角形 1: V0-V1-V3 (矩形上半)
indexList.Add(baseVert + 0);
indexList.Add(baseVert + 1);
indexList.Add(baseVert + 3);
// 三角形 2: V3-V2-V0 (矩形下半)
indexList.Add(baseVert + 3);
indexList.Add(baseVert + 2);
indexList.Add(baseVert + 0);
// 三角形 3: 转角连接三角形
// 仅在「同一折线上的相邻子段」之间绘制
// 连接上一段的 V4(上一段终点中心)到当前段的 V0, V1
if (hasPrevInLine)
{
int prevV4 = (segmentIndex - 1) * VerticesPerSegment + 4;
indexList.Add(prevV4);
indexList.Add(baseVert + 0);
indexList.Add(baseVert + 1);
}
}
#region Jobs 模式
#if ENABLE_BURST
/// <summary>
/// Burst 编译的 IJobParallelFor:并行生成各段线的模型顶点
/// </summary>
[BurstCompile]
public struct GenerateVerticesJob : IJobParallelFor
{
[ReadOnly] public NativeArray<SegmentJobInput> Inputs;
[WriteOnly] public NativeArray<UIVertex> OutputVertices;
public void Execute(int index)
{
int start = index * VerticesPerSegment;
var input = Inputs[index];
Vector2 startPos = input.start.position;
Vector2 endPos = input.end.position;
Vector2 dir = endPos - startPos;
float length = dir.magnitude;
if (length < float.Epsilon)
{
for (int i = 0; i < VerticesPerSegment; i++)
OutputVertices[start + i] = new UIVertex();
return;
}
Vector2 normal = dir / length;
Vector2 subTangent = new Vector2(-normal.y, normal.x);
float halfW0 = input.start.thickness * 0.5f;
float halfW1 = input.end.thickness * 0.5f;
Vector3 v0 = startPos - subTangent * halfW0;
Vector3 v1 = startPos + subTangent * halfW0;
Vector3 v2 = endPos - subTangent * halfW1;
Vector3 v3 = endPos + subTangent * halfW1;
Vector3 v4 = endPos;
Color32 c0 = Color32.Lerp(input.start.color, input.end.color, 0f);
Color32 c2 = Color32.Lerp(input.start.color, input.end.color, 1f);
float uvSY = input.startLineUV;
float uvEY = input.endLineUV;
Vector4 userDataStart = input.start.userData;
Vector4 userDataEnd = input.end.userData;
UIVertex vert = new UIVertex
{
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f)
};
vert.position = v0; vert.color = c0;
vert.uv0 = new Vector2(0f, uvSY); vert.uv1 = new Vector2(0f, uvSY); vert.uv2 = userDataStart;
OutputVertices[start + 0] = vert;
vert.position = v1; vert.color = c0;
vert.uv0 = new Vector2(1f, uvSY); vert.uv1 = new Vector2(1f, uvSY); vert.uv2 = userDataStart;
OutputVertices[start + 1] = vert;
vert.position = v2; vert.color = c2;
vert.uv0 = new Vector2(0f, uvEY); vert.uv1 = new Vector2(0f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 2] = vert;
vert.position = v3; vert.color = c2;
vert.uv0 = new Vector2(1f, uvEY); vert.uv1 = new Vector2(1f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 3] = vert;
vert.position = v4; vert.color = c2;
vert.uv0 = new Vector2(0.5f, uvEY); vert.uv1 = new Vector2(0.5f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 4] = vert;
}
}
/// <summary>
/// 使用 Jobs 并行生成线段顶点
/// </summary>
public static JobHandle GenerateSegmentsJobs(
NativeArray<SegmentJobInput> inputs,
NativeArray<UIVertex> outputVertices,
JobHandle dependsOn = default)
{
var job = new GenerateVerticesJob
{
Inputs = inputs,
OutputVertices = outputVertices
};
return job.Schedule(inputs.Length, 1, dependsOn);
}
#endif
#endregion
}
}