using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif namespace XericLibrary.Runtime.UIGraph { /// /// 任意图案渲染器 /// [AddComponentMenu("Xeric Library/UI/UIAnyPatternRenderer", 15)] public class UIAnyPatternRenderer : XericRendererComponent { public enum DistanceFieldMode { /// /// 简单模式:仅使用中心点(适用于凸多边形) /// SimpleCenter, /// /// 高级模式:使用中轴变换(适用于任意多边形) /// MedialAxis, /// /// 网格采样模式:在网格上采样距离(最精确但最慢) /// GridSampling } [System.Serializable] public class PatternGroup { [Tooltip("该组包含的顶点索引")] public List vertexIndices = new List(); [Tooltip("该组的填充颜色")] public Color32 fillColor = Color.white; [Tooltip("该组的描边颜色")] public Color32 strokeColor = Color.black; [Tooltip("描边宽度(像素)")] [Range(0f, 20f)] public float strokeWidth = 2f; [Tooltip("是否填充该组")] public bool fillEnabled = true; [Tooltip("是否显示描边")] public bool strokeEnabled = true; [Header("距离场设置")] [Tooltip("距离场生成模式")] public DistanceFieldMode distanceFieldMode = DistanceFieldMode.SimpleCenter; [Tooltip("距离场分辨率(仅GridSampling模式使用)")] [Range(16, 256)] public int distanceFieldResolution = 64; [Tooltip("是否在着色器中使用距离场(需要自定义着色器)")] public bool useDistanceFieldInShader = false; [Tooltip("距离场衰减系数")] [Range(0.1f, 10f)] public float distanceFalloff = 1f; [HideInInspector] public List cachedMedialAxisPoints = new List(); [HideInInspector] public List cachedDistances = new List(); [HideInInspector] public Texture2D distanceFieldTexture; } [Header("顶点设置")] [Tooltip("所有顶点的列表(局部坐标)")] public List vertices = new List(); [Header("图形分组")] [Tooltip("定义哪些顶点构成一个图形")] public List patternGroups = new List(); [Header("全局设置")] [Tooltip("是否显示所有顶点的辅助标记")] public bool showVertexMarkers = false; [Tooltip("顶点标记大小")] [Range(1f, 20f)] public float vertexMarkerSize = 5f; [Tooltip("顶点标记颜色")] public Color vertexMarkerColor = Color.red; [Header("调试设置")] [Tooltip("显示中轴线")] public bool showMedialAxis = false; [Tooltip("中轴线颜色")] public Color medialAxisColor = Color.yellow; [Tooltip("显示距离场可视化")] public bool visualizeDistanceField = false; // 三角剖分和距离场缓存 private Dictionary> triangulationCache = new Dictionary>(); private Dictionary uvCache = new Dictionary(); private bool isDirty = true; protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); if (vertices == null || vertices.Count == 0 || patternGroups == null || patternGroups.Count == 0) { return; } // 为每个图形分组生成网格 foreach (var group in patternGroups) { if (group.vertexIndices == null || group.vertexIndices.Count < 3) continue; // 获取该组的顶点 List groupVertices = new List(); foreach (int index in group.vertexIndices) { if (index >= 0 && index < vertices.Count) { groupVertices.Add(vertices[index]); } } if (groupVertices.Count < 3) continue; // 三角剖分 List triangles; int cacheKey = GetCacheKey(group.vertexIndices); if (!triangulationCache.TryGetValue(cacheKey, out triangles) || isDirty) { triangles = TriangulatePolygon(groupVertices); triangulationCache[cacheKey] = triangles; } // 生成UV(距离场) Vector2[] uvs; if (!uvCache.TryGetValue(cacheKey, out uvs) || isDirty) { uvs = GenerateDistanceFieldUVs(group, groupVertices, triangles); uvCache[cacheKey] = uvs; } // 添加填充 if (group.fillEnabled && triangles != null && triangles.Count > 0) { AddFilledPolygon(vh, groupVertices, triangles, uvs, group.fillColor); } // 添加描边 if (group.strokeEnabled && group.strokeWidth > 0) { AddStroke(vh, groupVertices, group.strokeWidth, group.strokeColor); } // 显示中轴线 if (showMedialAxis && group.cachedMedialAxisPoints.Count > 0) { AddMedialAxis(vh, group.cachedMedialAxisPoints, medialAxisColor); } } // 添加顶点标记 if (showVertexMarkers) { AddVertexMarkers(vh); } isDirty = false; } /// /// 生成距离场UV坐标 /// private Vector2[] GenerateDistanceFieldUVs(PatternGroup group, List vertices, List triangles) { Vector2[] uvs = new Vector2[vertices.Count]; switch (group.distanceFieldMode) { case DistanceFieldMode.SimpleCenter: uvs = GenerateSimpleCenterUVs(vertices); break; case DistanceFieldMode.MedialAxis: uvs = GenerateMedialAxisUVs(group, vertices, triangles); break; case DistanceFieldMode.GridSampling: uvs = GenerateGridSamplingUVs(group, vertices, triangles); break; } return uvs; } /// /// 简单中心模式:计算质心并生成线性距离 /// private Vector2[] GenerateSimpleCenterUVs(List vertices) { Vector2[] uvs = new Vector2[vertices.Count]; // 计算质心 Vector2 centroid = CalculateCentroid(vertices); // 计算最大距离(归一化用) float maxDistance = 0; foreach (var vertex in vertices) { float dist = Vector2.Distance(vertex, centroid); if (dist > maxDistance) maxDistance = dist; } if (maxDistance < 0.001f) maxDistance = 1f; // 生成UV:质心处为(0.5, 0.5),边界处根据距离变化 for (int i = 0; i < vertices.Count; i++) { float dist = Vector2.Distance(vertices[i], centroid); float normalizedDist = dist / maxDistance; // UV的x分量表示距离(0-1),y分量可以用于其他用途 uvs[i] = new Vector2(normalizedDist, 0.5f); } return uvs; } /// /// 中轴变换模式:计算多边形的中轴线 /// private Vector2[] GenerateMedialAxisUVs(PatternGroup group, List vertices, List triangles) { Vector2[] uvs = new Vector2[vertices.Count]; // 清空缓存 group.cachedMedialAxisPoints.Clear(); group.cachedDistances.Clear(); // 计算中轴线 List medialAxis = ComputeMedialAxis(vertices); group.cachedMedialAxisPoints = medialAxis; if (medialAxis.Count == 0) { // 如果中轴线计算失败,回退到简单中心模式 return GenerateSimpleCenterUVs(vertices); } // 计算每个顶点到最近的中轴线点的距离 float maxDistance = 0; for (int i = 0; i < vertices.Count; i++) { float minDist = float.MaxValue; foreach (var axisPoint in medialAxis) { float dist = Vector2.Distance(vertices[i], axisPoint); if (dist < minDist) minDist = dist; } group.cachedDistances.Add(minDist); if (minDist > maxDistance) maxDistance = minDist; } if (maxDistance < 0.001f) maxDistance = 1f; // 生成UV for (int i = 0; i < vertices.Count; i++) { float normalizedDist = group.cachedDistances[i] / maxDistance; uvs[i] = new Vector2(normalizedDist, 0.5f); } return uvs; } /// /// 计算多边形的中轴线(简化版) /// 实际应用中可以使用更复杂的算法如Voronoi图或直线骨架算法 /// private List ComputeMedialAxis(List vertices) { List medialAxis = new List(); // 方法1:使用Voronoi图的简化版本 // 对于每条边,计算其法线方向上的点 int n = vertices.Count; // 添加质心作为起点 Vector2 centroid = CalculateCentroid(vertices); medialAxis.Add(centroid); // 对于每条边,计算中垂线与相邻边中垂线的交点 for (int i = 0; i < n; i++) { int prev = (i - 1 + n) % n; int curr = i; int next = (i + 1) % n; // 计算三条边的中垂线 Vector2 edge1Mid = (vertices[prev] + vertices[curr]) * 0.5f; Vector2 edge1Dir = vertices[curr] - vertices[prev]; Vector2 edge1Normal = new Vector2(-edge1Dir.y, edge1Dir.x).normalized; Vector2 edge2Mid = (vertices[curr] + vertices[next]) * 0.5f; Vector2 edge2Dir = vertices[next] - vertices[curr]; Vector2 edge2Normal = new Vector2(-edge2Dir.y, edge2Dir.x).normalized; // 计算两条中垂线的交点 Vector2 intersection; if (LineLineIntersection(edge1Mid, edge1Normal, edge2Mid, edge2Normal, out intersection)) { // 检查交点是否在多边形内部 if (PointInPolygon(intersection, vertices)) { medialAxis.Add(intersection); } } } // 去重 medialAxis = RemoveDuplicatePoints(medialAxis, 0.1f); return medialAxis; } /// /// 网格采样模式:在网格上采样距离场(最精确) /// private Vector2[] GenerateGridSamplingUVs(PatternGroup group, List vertices, List triangles) { Vector2[] uvs = new Vector2[vertices.Count]; // 计算包围盒 Vector2 min = vertices[0]; Vector2 max = vertices[0]; foreach (var v in vertices) { min = Vector2.Min(min, v); max = Vector2.Max(max, v); } float width = max.x - min.x; float height = max.y - min.y; // 创建距离场纹理 if (group.distanceFieldTexture == null || group.distanceFieldTexture.width != group.distanceFieldResolution) { group.distanceFieldTexture = new Texture2D( group.distanceFieldResolution, group.distanceFieldResolution, TextureFormat.R8, false); group.distanceFieldTexture.filterMode = FilterMode.Bilinear; } Color32[] pixels = new Color32[group.distanceFieldResolution * group.distanceFieldResolution]; // 对每个像素采样 for (int y = 0; y < group.distanceFieldResolution; y++) { for (int x = 0; x < group.distanceFieldResolution; x++) { // 计算世界坐标 float worldX = min.x + (x / (float)(group.distanceFieldResolution - 1)) * width; float worldY = min.y + (y / (float)(group.distanceFieldResolution - 1)) * height; Vector2 worldPos = new Vector2(worldX, worldY); // 计算到边界的最小距离 float minDistance = float.MaxValue; // 检查是否在多边形内部 bool inside = PointInPolygon(worldPos, vertices); // 计算到每条边的距离 for (int i = 0; i < vertices.Count; i++) { int next = (i + 1) % vertices.Count; float dist = DistancePointToSegment(worldPos, vertices[i], vertices[next]); if (dist < minDistance) minDistance = dist; } // 归一化距离 float normalizedDist = Mathf.Clamp01(minDistance / Mathf.Max(width, height)); // 如果在外部,距离为负 if (!inside) normalizedDist = -normalizedDist; // 存储到纹理 int pixelIndex = y * group.distanceFieldResolution + x; byte distByte = (byte)(Mathf.Clamp01(Mathf.Abs(normalizedDist)) * 255); pixels[pixelIndex] = new Color32(distByte, distByte, distByte, 255); // 为顶点生成UV if (visualizeDistanceField) { for (int i = 0; i < vertices.Count; i++) { if (Vector2.Distance(vertices[i], worldPos) < 1f) { uvs[i] = new Vector2(x / (float)group.distanceFieldResolution, y / (float)group.distanceFieldResolution); } } } } } group.distanceFieldTexture.SetPixels32(pixels); group.distanceFieldTexture.Apply(); // 如果不显示可视化,使用简单模式的UV if (!visualizeDistanceField) { return GenerateSimpleCenterUVs(vertices); } return uvs; } /// /// 计算多边形的质心 /// private Vector2 CalculateCentroid(List vertices) { float signedArea = 0; float centerX = 0; float centerY = 0; int n = vertices.Count; for (int i = 0; i < n; i++) { int next = (i + 1) % n; float temp = vertices[i].x * vertices[next].y - vertices[next].x * vertices[i].y; signedArea += temp; centerX += (vertices[i].x + vertices[next].x) * temp; centerY += (vertices[i].y + vertices[next].y) * temp; } signedArea *= 0.5f; if (Mathf.Abs(signedArea) < 0.001f) return vertices.Count > 0 ? vertices[0] : Vector2.zero; centerX /= (6 * signedArea); centerY /= (6 * signedArea); return new Vector2(centerX, centerY); } /// /// 检查点是否在多边形内部 /// private bool PointInPolygon(Vector2 point, List vertices) { int n = vertices.Count; bool inside = false; for (int i = 0, j = n - 1; i < n; j = i++) { if (((vertices[i].y > point.y) != (vertices[j].y > point.y)) && (point.x < (vertices[j].x - vertices[i].x) * (point.y - vertices[i].y) / (vertices[j].y - vertices[i].y) + vertices[i].x)) { inside = !inside; } } return inside; } /// /// 计算点到线段的最短距离 /// private float DistancePointToSegment(Vector2 point, Vector2 a, Vector2 b) { Vector2 ab = b - a; Vector2 ap = point - a; float dot = Vector2.Dot(ap, ab); float lenSq = ab.sqrMagnitude; float t = dot / lenSq; t = Mathf.Clamp01(t); Vector2 projection = a + t * ab; return Vector2.Distance(point, projection); } /// /// 计算两条直线的交点 /// private bool LineLineIntersection(Vector2 p1, Vector2 d1, Vector2 p2, Vector2 d2, out Vector2 intersection) { intersection = Vector2.zero; float det = d1.x * d2.y - d1.y * d2.x; if (Mathf.Abs(det) < 0.001f) return false; Vector2 diff = p2 - p1; float t = (diff.x * d2.y - diff.y * d2.x) / det; intersection = p1 + t * d1; return true; } /// /// 去除重复点 /// private List RemoveDuplicatePoints(List points, float threshold) { List result = new List(); foreach (var p in points) { bool isDuplicate = false; foreach (var r in result) { if (Vector2.Distance(p, r) < threshold) { isDuplicate = true; break; } } if (!isDuplicate) result.Add(p); } return result; } // ========== 以下是原有代码的简化版本 ========== /// /// 生成缓存键(基于顶点索引列表) /// private int GetCacheKey(List indices) { int hash = 17; foreach (int index in indices) { hash = hash * 31 + index; } return hash; } /// /// 耳切法三角剖分(简化版) /// private List TriangulatePolygon(List polygonVertices) { // 这里使用简化版本,实际应用中可以使用更复杂的三角剖分算法 List triangles = new List(); if (polygonVertices.Count < 3) return triangles; // 简单的扇形三角剖分(仅适用于凸多边形) for (int i = 1; i < polygonVertices.Count - 1; i++) { triangles.Add(0); triangles.Add(i); triangles.Add(i + 1); } return triangles; } /// /// 添加填充多边形 /// private void AddFilledPolygon(VertexHelper vh, List vertices, List triangles, Vector2[] uvs, Color32 color) { int vertexStartIndex = vh.currentVertCount; // 添加顶点 for (int i = 0; i < vertices.Count; i++) { Vector2 uv = uvs != null && i < uvs.Length ? uvs[i] : Vector2.zero; vh.AddVert(vertices[i], color, uv); } // 添加三角形索引 for (int i = 0; i < triangles.Count; i += 3) { vh.AddTriangle( vertexStartIndex + triangles[i], vertexStartIndex + triangles[i + 1], vertexStartIndex + triangles[i + 2] ); } } /// /// 添加描边 /// private void AddStroke(VertexHelper vh, List vertices, float width, Color32 color) { if (vertices.Count < 2) return; float halfWidth = width * 0.5f; // 为每条边生成两个三角形(形成矩形) for (int i = 0; i < vertices.Count; i++) { int next = (i + 1) % vertices.Count; Vector2 p1 = vertices[i]; Vector2 p2 = vertices[next]; // 计算边的法线 Vector2 edge = p2 - p1; Vector2 normal = new Vector2(-edge.y, edge.x).normalized; // 生成四个顶点(矩形) Vector2 v1 = p1 + normal * halfWidth; Vector2 v2 = p1 - normal * halfWidth; Vector2 v3 = p2 - normal * halfWidth; Vector2 v4 = p2 + normal * halfWidth; int startIndex = vh.currentVertCount; vh.AddVert(v1, color, Vector2.zero); vh.AddVert(v2, color, Vector2.zero); vh.AddVert(v3, color, Vector2.zero); vh.AddVert(v4, color, Vector2.zero); // 两个三角形 vh.AddTriangle(startIndex, startIndex + 1, startIndex + 2); vh.AddTriangle(startIndex, startIndex + 2, startIndex + 3); } } /// /// 添加中轴线可视化 /// private void AddMedialAxis(VertexHelper vh, List points, Color32 color) { if (points.Count < 2) return; // 绘制线段 for (int i = 0; i < points.Count - 1; i++) { Vector2 p1 = points[i]; Vector2 p2 = points[i + 1]; float lineWidth = 3f; Vector2 edge = p2 - p1; Vector2 normal = new Vector2(-edge.y, edge.x).normalized; Vector2 v1 = p1 + normal * lineWidth * 0.5f; Vector2 v2 = p1 - normal * lineWidth * 0.5f; Vector2 v3 = p2 - normal * lineWidth * 0.5f; Vector2 v4 = p2 + normal * lineWidth * 0.5f; int startIndex = vh.currentVertCount; vh.AddVert(v1, color, Vector2.zero); vh.AddVert(v2, color, Vector2.zero); vh.AddVert(v3, color, Vector2.zero); vh.AddVert(v4, color, Vector2.zero); vh.AddTriangle(startIndex, startIndex + 1, startIndex + 2); vh.AddTriangle(startIndex, startIndex + 2, startIndex + 3); } } /// /// 添加顶点标记(小圆圈) /// private void AddVertexMarkers(VertexHelper vh) { int segments = 8; foreach (var vertex in vertices) { List circleVertices = new List(); List circleTriangles = new List(); circleVertices.Add(vertex); for (int i = 0; i <= segments; i++) { float angle = (i / (float)segments) * 2 * Mathf.PI; Vector2 point = vertex + new Vector2( Mathf.Cos(angle) * vertexMarkerSize, Mathf.Sin(angle) * vertexMarkerSize ); circleVertices.Add(point); } for (int i = 1; i <= segments; i++) { circleTriangles.Add(0); circleTriangles.Add(i); circleTriangles.Add(i + 1); } int startIndex = vh.currentVertCount; foreach (var v in circleVertices) { vh.AddVert(v, vertexMarkerColor, Vector2.zero); } for (int i = 0; i < circleTriangles.Count; i += 3) { vh.AddTriangle( startIndex + circleTriangles[i], startIndex + circleTriangles[i + 1], startIndex + circleTriangles[i + 2] ); } } } /// /// 标记为需要重新生成网格 /// public void SetVerticesDirty() { isDirty = true; SetAllDirty(); } /// /// 设置顶点列表 /// public void SetVertices(List newVertices) { vertices = newVertices ?? new List(); SetVerticesDirty(); } /// /// 设置图形分组 /// public void SetPatternGroups(List newGroups) { patternGroups = newGroups ?? new List(); SetVerticesDirty(); } /// /// 添加一个新的图形分组 /// public void AddPatternGroup(List vertexIndices, Color fillColor, Color strokeColor, float strokeWidth = 2f) { PatternGroup group = new PatternGroup { vertexIndices = vertexIndices, fillColor = fillColor, strokeColor = strokeColor, strokeWidth = strokeWidth, fillEnabled = true, strokeEnabled = true, distanceFieldMode = DistanceFieldMode.SimpleCenter }; if (patternGroups == null) patternGroups = new List(); patternGroups.Add(group); SetVerticesDirty(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); isDirty = true; } [MenuItem("GameObject/Xeric Library/UI/UIAnyPatternRenderer", false, 10)] private static void CreateGameObject() { GameObject newObject = new GameObject(nameof(UIAnyPatternRenderer), typeof(UIAnyPatternRenderer)); if (Selection.activeGameObject != null) newObject.transform.SetParent(Selection.activeGameObject.transform); Undo.RegisterCreatedObjectUndo(newObject, $"Create {nameof(UIAnyPatternRenderer)}"); Selection.activeObject = newObject; } #endif } }