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

391 lines
15 KiB
C#

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; // 输出:实际生成的三角形数
}
}
}