From 22ae0d9293b1424f8b79141d68634151dbe13156 Mon Sep 17 00:00:00 2001 From: lrc <571244399@qq.com> Date: Tue, 7 Apr 2026 17:48:48 +0800 Subject: [PATCH] =?UTF-8?q?*=20=E5=B0=86=E7=BB=84=E4=BB=B6=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=88=B0=E8=8F=9C=E5=8D=95=E4=B8=AD=E3=80=82=20*=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=B8=AA=E4=BB=BB=E6=84=8F=E5=BD=A2?= =?UTF-8?q?=E7=8A=B6=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/Core.meta | 3 + Runtime/Core/TriangulateHelp.cs | 221 +++++ Runtime/Core/TriangulateHelp.cs.meta | 3 + .../{Renderer => Core}/XericRendererUtils.cs | 52 +- .../XericRendererUtils.cs.meta | 0 Runtime/Material/UIPattern.shader | 42 +- Runtime/RenderParam.meta | 3 + .../RenderParam/UIRenderScriptableObject.cs | 10 + .../UIRenderScriptableObject.cs.meta | 3 + Runtime/Renderer/UIAnyPatternRenderer.cs | 800 ++++++++++++++++++ Runtime/Renderer/UIAnyPatternRenderer.cs.meta | 3 + Runtime/Renderer/UILineRenderer.cs | 4 +- Runtime/Renderer/UIPatternRenderer.cs | 56 +- Runtime/Renderer/XericRendererComponent.cs | 58 +- 14 files changed, 1203 insertions(+), 55 deletions(-) create mode 100644 Runtime/Core.meta create mode 100644 Runtime/Core/TriangulateHelp.cs create mode 100644 Runtime/Core/TriangulateHelp.cs.meta rename Runtime/{Renderer => Core}/XericRendererUtils.cs (93%) rename Runtime/{Renderer => Core}/XericRendererUtils.cs.meta (100%) create mode 100644 Runtime/RenderParam.meta create mode 100644 Runtime/RenderParam/UIRenderScriptableObject.cs create mode 100644 Runtime/RenderParam/UIRenderScriptableObject.cs.meta create mode 100644 Runtime/Renderer/UIAnyPatternRenderer.cs create mode 100644 Runtime/Renderer/UIAnyPatternRenderer.cs.meta diff --git a/Runtime/Core.meta b/Runtime/Core.meta new file mode 100644 index 0000000..74a5c8d --- /dev/null +++ b/Runtime/Core.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ba47379e46484bd5960c303610fcca35 +timeCreated: 1775549160 \ No newline at end of file diff --git a/Runtime/Core/TriangulateHelp.cs b/Runtime/Core/TriangulateHelp.cs new file mode 100644 index 0000000..6995aaa --- /dev/null +++ b/Runtime/Core/TriangulateHelp.cs @@ -0,0 +1,221 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace XericLibrary.Runtime.Renderer +{ + public static class TriangulateHelp + { + + /// + /// 添加填充多边形 + /// + public static void AddFilledPolygon(VertexHelper vh, List vertices, List triangles, Color color) + { + int vertexStartIndex = vh.currentVertCount; + + // 添加顶点 + foreach (var vertex in vertices) + { + vh.AddVert(vertex, color, Vector2.zero); + } + + // 添加三角形索引 + for (int i = 0; i < triangles.Count; i += 3) + { + vh.AddTriangle( + vertexStartIndex + triangles[i], + vertexStartIndex + triangles[i + 1], + vertexStartIndex + triangles[i + 2] + ); + } + } + + + /// + /// 添加描边 + /// + public static void AddStroke(VertexHelper vh, List vertices, float width, Color 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); + } + } + + + + /// + /// 耳切法三角剖分 + /// + public static List TriangulatePolygon(List polygonVertices) + { + List triangles = new List(); + + if (polygonVertices == null || polygonVertices.Count < 3) + return triangles; + + // 复制顶点列表以便修改 + List verticesCopy = new List(polygonVertices); + List indices = Enumerable.Range(0, verticesCopy.Count).ToList(); + + // 确保顶点顺序是逆时针(耳切法要求) + if (!IsCounterClockwise(verticesCopy)) + { + verticesCopy.Reverse(); + indices.Reverse(); + } + + int n = verticesCopy.Count; + + // 当顶点数大于3时继续切耳朵 + while (n > 3) + { + bool foundEar = false; + + for (int i = 0; i < n; i++) + { + int prev = (i - 1 + n) % n; + int curr = i; + int next = (i + 1) % n; + + // 检查是否是耳朵 + if (IsEar(verticesCopy, prev, curr, next, n)) + { + // 添加三角形 + triangles.Add(indices[prev]); + triangles.Add(indices[curr]); + triangles.Add(indices[next]); + + // 移除耳朵顶点 + verticesCopy.RemoveAt(curr); + indices.RemoveAt(curr); + n--; + foundEar = true; + break; + } + } + + // 如果没有找到耳朵,可能是复杂多边形,退出 + if (!foundEar) + { + Debug.LogWarning("无法完成三角剖分,可能是自相交多边形"); + break; + } + } + + // 添加最后一个三角形 + if (n == 3) + { + triangles.Add(indices[0]); + triangles.Add(indices[1]); + triangles.Add(indices[2]); + } + + return triangles; + } + + /// + /// 检查顶点顺序是否为逆时针 + /// + public static bool IsCounterClockwise(List vertices) + { + float sum = 0; + int n = vertices.Count; + + for (int i = 0; i < n; i++) + { + Vector2 v1 = vertices[i]; + Vector2 v2 = vertices[(i + 1) % n]; + sum += (v2.x - v1.x) * (v2.y + v1.y); + } + + return sum < 0; + } + /// + /// 检查三个顶点是否构成耳朵 + /// + public static bool IsEar(List vertices, int prev, int curr, int next, int n) + { + Vector2 a = vertices[prev]; + Vector2 b = vertices[curr]; + Vector2 c = vertices[next]; + + // 检查是否是凸角 + if (!IsConvex(a, b, c)) + return false; + + // 检查三角形内部是否包含其他顶点 + for (int i = 0; i < n; i++) + { + if (i == prev || i == curr || i == next) + continue; + + if (PointInTriangle(vertices[i], a, b, c)) + return false; + } + + return true; + } + + /// + /// 检查点是否在三角形内部 + /// + public static bool PointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c) + { + float area = Mathf.Abs(CrossProduct(a, b, c)); + float area1 = Mathf.Abs(CrossProduct(p, a, b)); + float area2 = Mathf.Abs(CrossProduct(p, b, c)); + float area3 = Mathf.Abs(CrossProduct(p, c, a)); + + return Mathf.Abs(area - (area1 + area2 + area3)) < 0.001f; + } + + /// + /// 检查三点是否构成凸角(逆时针) + /// + public static bool IsConvex(Vector2 a, Vector2 b, Vector2 c) + { + return CrossProduct(a, b, c) > 0; + } + + /// + /// 计算叉积 + /// + public static float CrossProduct(Vector2 a, Vector2 b, Vector2 c) + { + return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + } + } +} \ No newline at end of file diff --git a/Runtime/Core/TriangulateHelp.cs.meta b/Runtime/Core/TriangulateHelp.cs.meta new file mode 100644 index 0000000..3c43f24 --- /dev/null +++ b/Runtime/Core/TriangulateHelp.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45ff8ace448e4691b1db19cb52906ddd +timeCreated: 1775549185 \ No newline at end of file diff --git a/Runtime/Renderer/XericRendererUtils.cs b/Runtime/Core/XericRendererUtils.cs similarity index 93% rename from Runtime/Renderer/XericRendererUtils.cs rename to Runtime/Core/XericRendererUtils.cs index 77b2ed0..5ef6f64 100644 --- a/Runtime/Renderer/XericRendererUtils.cs +++ b/Runtime/Core/XericRendererUtils.cs @@ -7,13 +7,15 @@ using UnityEngine.Pool; using UnityEngine.UI; using XericLibrary.Runtime.MacroLibrary; -namespace XericLibrary +namespace XericLibrary.Runtime.Renderer { /// /// UILineRenderer 的工具类,提供静态方法用于线条渲染相关的操作 /// internal static class XericRendererUtils { + #region 数学计算 + /// /// 计算线路的总长度 /// @@ -84,6 +86,10 @@ namespace XericLibrary return true; } + #endregion + + #region uv计算 + /// /// 计算起点和终点的UV坐标Y值 /// @@ -97,6 +103,8 @@ namespace XericLibrary startUVY = totalLength > 0 ? startLength / totalLength : 0f; endUVY = totalLength > 0 ? (startLength + currentLength) / totalLength : 0f; } + + #endregion #region 简易线路绘制 @@ -514,5 +522,47 @@ namespace XericLibrary } #endregion + + #region 顶点列表工具 + + private static readonly Color32 s_DefaultColor = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + private static readonly Vector4 s_DefaultTangent = new Vector4(1f, 0.0f, 0.0f, -1f); + + /// + /// 添加一个顶点信息 + /// + /// + /// + /// + /// + public static void AddVert(this ICollection vertexList, Vector3 position, Vector4 uv, Color32 color) + { + vertexList.Add(new UIVertex() + { + position = position, + normal = Vector3.back, + tangent = s_DefaultTangent, + color = color, + uv0 = uv, + uv1 = uv, + uv2 = uv, + uv3 = uv + }); + } + /// + /// 添加一个三角面索引 + /// + /// + /// + /// + /// + public static void AddTriangle(this ICollection indices, int index0, int index1, int index2) + { + indices.Add(index0); + indices.Add(index1); + indices.Add(index2); + } + + #endregion } } \ No newline at end of file diff --git a/Runtime/Renderer/XericRendererUtils.cs.meta b/Runtime/Core/XericRendererUtils.cs.meta similarity index 100% rename from Runtime/Renderer/XericRendererUtils.cs.meta rename to Runtime/Core/XericRendererUtils.cs.meta diff --git a/Runtime/Material/UIPattern.shader b/Runtime/Material/UIPattern.shader index 194df28..9abdd58 100644 --- a/Runtime/Material/UIPattern.shader +++ b/Runtime/Material/UIPattern.shader @@ -20,6 +20,7 @@ Shader "XericLibrary/UILine/UIPattern" _Thinkness( "Thinkness", Range( 0, 1 ) ) = 0.1 _BorderColor( "Border Color", Color ) = ( 1, 1, 1, 1 ) _FillColor( "Fill Color", Color ) = ( 0.6698113, 0.6698113, 0.6698113, 0.7803922 ) + _BorderColor2( "Border Color2", Color ) = ( 0, 0, 0, 0 ) } @@ -96,6 +97,7 @@ Shader "XericLibrary/UILine/UIPattern" uniform float4 _FillColor; uniform float4 _BorderColor; + uniform float4 _BorderColor2; uniform float _Thinkness; @@ -136,8 +138,9 @@ Shader "XericLibrary/UILine/UIPattern" float4 texCoord4 = IN.ase_texcoord3; texCoord4.xy = IN.ase_texcoord3.xy * float2( 1,1 ) + float2( 0,0 ); - float temp_output_18_0 = saturate( ( ( ( _Thinkness - ( 1.0 - texCoord4.z ) ) / _Thinkness ) * 100.0 ) ); - float4 lerpResult19 = lerp( _FillColor , _BorderColor , temp_output_18_0); + float temp_output_14_0 = ( _Thinkness - ( 1.0 - texCoord4.z ) ); + float4 lerpResult24 = lerp( _BorderColor , _BorderColor2 , saturate( ( temp_output_14_0 / _Thinkness ) )); + float4 lerpResult19 = lerp( _FillColor , lerpResult24 , saturate( ( temp_output_14_0 * 1000.0 ) )); half4 color = lerpResult19; @@ -165,34 +168,35 @@ Shader "XericLibrary/UILine/UIPattern" /*ASEBEGIN Version=19908 Node;AmplifyShaderEditor.TextureCoordinatesNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;4;-640,-384;Inherit;False;2;-1;4;3;2;SAMPLER2D;;False;0;FLOAT2;1,1;False;1;FLOAT2;0,0;False;5;FLOAT4;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4 -Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;2;-288,-176;Inherit;False;Property;_Thinkness;Thinkness;0;0;Create;True;0;0;0;False;0;False;0.1;0;0;1;0;1;FLOAT;0 -Node;AmplifyShaderEditor.OneMinusNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;13;-256,-368;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0 +Node;AmplifyShaderEditor.OneMinusNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;13;-384,-384;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0 +Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;2;-384,-256;Inherit;False;Property;_Thinkness;Thinkness;0;0;Create;True;0;0;0;False;0;False;0.1;0;0;1;0;1;FLOAT;0 Node;AmplifyShaderEditor.SimpleSubtractOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;14;-96,-384;Inherit;False;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0 -Node;AmplifyShaderEditor.SimpleDivideOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;15;113.2046,-377.9782;Inherit;False;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0 -Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;17;256,-240;Inherit;False;Constant;_Float0;Float 0;2;0;Create;True;0;0;0;False;0;False;100;0;0;0;0;1;FLOAT;0 +Node;AmplifyShaderEditor.SimpleDivideOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;15;64,-384;Inherit;False;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0 +Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;17;256,-256;Inherit;False;Constant;_Float0;Float 0;2;0;Create;True;0;0;0;False;0;False;1000;0;0;0;0;1;FLOAT;0 +Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;20;512,-256;Inherit;False;Property;_BorderColor;Border Color;1;0;Create;True;0;0;0;False;0;False;1,1,1,1;1,1,1,1;True;True;0;6;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4;FLOAT3;5 +Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;23;512,-32;Inherit;False;Property;_BorderColor2;Border Color2;3;0;Create;True;0;0;0;False;0;False;0,0,0,0;0,0,0,0;True;True;0;6;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4;FLOAT3;5 +Node;AmplifyShaderEditor.SaturateNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;28;256,-128;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0 Node;AmplifyShaderEditor.SimpleMultiplyOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;16;256,-384;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0 +Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;21;512,-640;Inherit;False;Property;_FillColor;Fill Color;2;0;Create;True;0;0;0;False;0;False;0.6698113,0.6698113,0.6698113,0.7803922;0,0,0,0;True;True;0;6;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4;FLOAT3;5 +Node;AmplifyShaderEditor.LerpOp, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;24;848,-176;Inherit;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0 Node;AmplifyShaderEditor.SaturateNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;18;416,-384;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0 -Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;21;480,-592;Inherit;False;Property;_FillColor;Fill Color;2;0;Create;True;0;0;0;False;0;False;0.6698113,0.6698113,0.6698113,0.7803922;0,0,0,0;True;True;0;6;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4;FLOAT3;5 -Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;20;480,-96;Inherit;False;Property;_BorderColor;Border Color;1;0;Create;True;0;0;0;False;0;False;1,1,1,1;1,1,1,1;True;True;0;6;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4;FLOAT3;5 -Node;AmplifyShaderEditor.LerpOp, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;19;784,-384;Inherit;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0 -Node;AmplifyShaderEditor.DynamicAppendNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;6;864,-160;Inherit;False;FLOAT4;4;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;1;False;1;FLOAT4;0 -Node;AmplifyShaderEditor.DynamicAppendNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;22;176,-592;Inherit;False;FLOAT4;4;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;1;False;1;FLOAT4;0 -Node;AmplifyShaderEditor.TemplateMultiPassMasterNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;0;1008,-368;Float;False;True;-1;3;AmplifyShaderEditor.MaterialInspector;0;12;XericLibrary/UILine/UIPattern;5056123faa0c79b47ab6ad7e8bf059a4;True;Default;0;0;Default;2;False;True;3;1;False;;10;False;;0;1;False;;0;False;;False;False;False;False;False;False;False;False;False;False;False;False;True;2;False;;False;True;True;True;True;True;0;True;_ColorMask;False;False;False;False;False;False;False;True;True;0;True;_Stencil;255;True;_StencilReadMask;255;True;_StencilWriteMask;0;True;_StencilComp;0;True;_StencilOp;0;False;;0;False;;0;False;;0;False;;0;False;;0;False;;False;True;2;False;;True;0;True;unity_GUIZTestMode;False;False;True;5;Queue=Transparent=Queue=0;IgnoreProjector=True;RenderType=Transparent=RenderType;PreviewType=Plane;CanUseSpriteAtlas=True;False;False;0;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;3;False;0;;0;0;Standard;0;0;1;True;False;;False;0 +Node;AmplifyShaderEditor.LerpOp, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;19;1024,-384;Inherit;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0 +Node;AmplifyShaderEditor.TemplateMultiPassMasterNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;0;1280,-384;Float;False;True;-1;3;AmplifyShaderEditor.MaterialInspector;0;12;XericLibrary/UILine/UIPattern;5056123faa0c79b47ab6ad7e8bf059a4;True;Default;0;0;Default;2;False;True;3;1;False;;10;False;;0;1;False;;0;False;;False;False;False;False;False;False;False;False;False;False;False;False;True;2;False;;False;True;True;True;True;True;0;True;_ColorMask;False;False;False;False;False;False;False;True;True;0;True;_Stencil;255;True;_StencilReadMask;255;True;_StencilWriteMask;0;True;_StencilComp;0;True;_StencilOp;0;False;;0;False;;0;False;;0;False;;0;False;;0;False;;False;True;2;False;;True;0;True;unity_GUIZTestMode;False;False;True;5;Queue=Transparent=Queue=0;IgnoreProjector=True;RenderType=Transparent=RenderType;PreviewType=Plane;CanUseSpriteAtlas=True;False;False;0;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;3;False;0;;0;0;Standard;0;0;1;True;False;;False;0 WireConnection;13;0;4;3 WireConnection;14;0;2;0 WireConnection;14;1;13;0 WireConnection;15;0;14;0 WireConnection;15;1;2;0 -WireConnection;16;0;15;0 +WireConnection;28;0;15;0 +WireConnection;16;0;14;0 WireConnection;16;1;17;0 +WireConnection;24;0;20;0 +WireConnection;24;1;23;0 +WireConnection;24;2;28;0 WireConnection;18;0;16;0 WireConnection;19;0;21;0 -WireConnection;19;1;20;0 +WireConnection;19;1;24;0 WireConnection;19;2;18;0 -WireConnection;6;0;18;0 -WireConnection;22;0;4;1 -WireConnection;22;1;4;2 -WireConnection;22;2;4;3 WireConnection;0;0;19;0 ASEEND*/ -//CHKSM=0563E16505FEE01A149F7DEE4B80FAB154BB7859 \ No newline at end of file +//CHKSM=84080EAB92253329FAA94F86E05E0DFA50E904E6 \ No newline at end of file diff --git a/Runtime/RenderParam.meta b/Runtime/RenderParam.meta new file mode 100644 index 0000000..1efb6bf --- /dev/null +++ b/Runtime/RenderParam.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 498048bc479945ccab166de5c805557b +timeCreated: 1774857180 \ No newline at end of file diff --git a/Runtime/RenderParam/UIRenderScriptableObject.cs b/Runtime/RenderParam/UIRenderScriptableObject.cs new file mode 100644 index 0000000..7340ac4 --- /dev/null +++ b/Runtime/RenderParam/UIRenderScriptableObject.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace RenderParam +{ + // [CreateAssetMenu(fileName = "UIRenderScriptableObject", menuName = "MyCategory/My Custom Data", order = 1)] + public class UIRenderScriptableObject : ScriptableObject + { + + } +} \ No newline at end of file diff --git a/Runtime/RenderParam/UIRenderScriptableObject.cs.meta b/Runtime/RenderParam/UIRenderScriptableObject.cs.meta new file mode 100644 index 0000000..c6ac278 --- /dev/null +++ b/Runtime/RenderParam/UIRenderScriptableObject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8a4b2bc556f349c1936462d382601754 +timeCreated: 1774857200 \ No newline at end of file diff --git a/Runtime/Renderer/UIAnyPatternRenderer.cs b/Runtime/Renderer/UIAnyPatternRenderer.cs new file mode 100644 index 0000000..2bfbac4 --- /dev/null +++ b/Runtime/Renderer/UIAnyPatternRenderer.cs @@ -0,0 +1,800 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace XericLibrary.Runtime.Renderer +{ + /// + /// 任意图案渲染器 + /// + [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; + } + #endif + + } +} \ No newline at end of file diff --git a/Runtime/Renderer/UIAnyPatternRenderer.cs.meta b/Runtime/Renderer/UIAnyPatternRenderer.cs.meta new file mode 100644 index 0000000..b2819b9 --- /dev/null +++ b/Runtime/Renderer/UIAnyPatternRenderer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e0096bca1b7b40bf90e0aa7ebbda48f0 +timeCreated: 1775548842 \ No newline at end of file diff --git a/Runtime/Renderer/UILineRenderer.cs b/Runtime/Renderer/UILineRenderer.cs index 9dbf355..f684f84 100644 --- a/Runtime/Renderer/UILineRenderer.cs +++ b/Runtime/Renderer/UILineRenderer.cs @@ -3,15 +3,17 @@ using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; -namespace XericLibrary +namespace XericLibrary.Runtime.Renderer { /// /// UI上线条绘制组件 /// 支持设置线条厚度、居中显示,并为线段之间添加斜角边缘 /// 继承自 MaskableGraphic,可与 UI 遮罩系统配合使用 /// + [AddComponentMenu("Xeric Library/UI/UILineRenderer", 15)] public class UILineRenderer : XericRendererComponent { + // todo 根据优弧劣弧绘制线段,这么说可能不太准确,但我需要在90度以内绘制漂亮的转角,大于90度则用中心圆代替 #region 字段属性 /// diff --git a/Runtime/Renderer/UIPatternRenderer.cs b/Runtime/Renderer/UIPatternRenderer.cs index eb811f7..9cacf1e 100644 --- a/Runtime/Renderer/UIPatternRenderer.cs +++ b/Runtime/Renderer/UIPatternRenderer.cs @@ -10,21 +10,24 @@ using XericLibrary.Runtime.MacroLibrary; #if ODIN_INSPECTOR using Sirenix.OdinInspector; #endif -namespace XericLibrary +namespace XericLibrary.Runtime.Renderer { /// /// UI多边形图形图案渲染组件(三角形,菱形,五边形...三十二边形) /// 支持倒角 /// + [AddComponentMenu("Xeric Library/UI/UIPatternRenderer", 15)] public class UIPatternRenderer : XericRendererComponent { + #region 字段属性 + public override string ShaderName => "XericLibrary/UILine/UIPattern"; [Header("多边形设置"), Range(3, 32)] public int sides = 6; [SerializeField, Tooltip("多边形的大小 (相对于矩形区域的比例 0-1)")] #if ODIN_INSPECTOR - [ProgressBar("Scalemin", "GetMaxScale")] + [PropertyRange("ScaleMin", "GetMaxScale")] #else [Range(0f, 1f)] #endif @@ -42,13 +45,14 @@ namespace XericLibrary private float borderThickness = .1f; [SerializeField, Tooltip("边框颜色")] private Color borderColor = Color.white; + [SerializeField, Tooltip("边框颜色渐变")] private Color borderColor2 = Color.white; [SerializeField, Tooltip("填充颜色")] private Color fillColor = Color.gray; /// /// 获取最大缩放(一个参考值) /// public float GetMaxScale => 1 / Mathf.Cos(GetSideRadius); - private float Scalemin => 0.01f; + private float ScaleMin => 0.01f; /// /// 获取多边形的旋转弧度 /// @@ -62,6 +66,7 @@ namespace XericLibrary }; private static readonly int BorderColor1 = Shader.PropertyToID("_BorderColor"); + private static readonly int BorderColor2 = Shader.PropertyToID("_BorderColor2"); private static readonly int FillColor1 = Shader.PropertyToID("_FillColor"); private static readonly int Thinkness = Shader.PropertyToID("_Thinkness"); @@ -90,6 +95,18 @@ namespace XericLibrary } } /// + /// 边框颜色渐变 + /// + public Color BorderColorLinear + { + get => borderColor2; + set + { + borderColor2 = value; + m_Material.SetColor(BorderColor2, borderColor2); + } + } + /// /// 填充颜色 /// public Color FillColor @@ -101,6 +118,7 @@ namespace XericLibrary m_Material.SetColor(FillColor1, fillColor); } } + /// /// 设置缩放 /// @@ -110,17 +128,20 @@ namespace XericLibrary // 缓存顶点列表以避免每帧分配内存 private readonly List verticesCache = new List(); + #endregion + + #region 类内类型 + public enum AngleSelectMode { Angle, Multiples, } + + #endregion - protected override void OnPopulateMesh(VertexHelper vh) + protected override void OnPopulatePrefabricateVertex(List vertexList, List indexList) { - if (vh == null) return; - vh.Clear(); - // 获取绘图区域的中心点和尺寸 Rect rect = GetPixelAdjustedRect(); Vector2 center = rect.center; @@ -154,12 +175,13 @@ namespace XericLibrary // 此渲染组件的颜色和中心uv Color32 color32 = this.color; Vector2 uvCenter = new Vector4(0.5f, 0.5f, 0, 0); + // 没有倒角,使用常规流程 if (currentRounding <= 0f) { verticesCache.AddRange(OriginPatternVectorPoints(sides, radius, sideAngle, center)); // 添加中心点 - vh.AddVert(center, color32, uvCenter, uvCenter, uvCenter, Vector4.zero); + vertexList.AddVert(center, uvCenter, color32); float angleStep = 360f / sides; // 添加外围点 @@ -170,24 +192,23 @@ namespace XericLibrary XericRendererUtils.GetUVAtRect(verticesCache[i], rect, out var u, out var v); GetNormalizedAngleByPos(angleStep, i, out var normalizedAngle); var uv = new Vector4(u, v, 1, normalizedAngle); - vh.AddVert(verticesCache[i], color32, uv, uv, uv, Vector4.zero); + vertexList.AddVert(verticesCache[i], uv, color32); } // 三角形顺序:中心点, 当前外点, 下一个外点 for (int i = 1; i < sides; i++) - vh.AddTriangle(0, i, i + 1); + indexList.AddTriangle(0, i, i + 1); // 连接最后一个点和第一个点以闭合形状 - vh.AddTriangle(0, sides, 1); + indexList.AddTriangle(0, sides, 1); } // 启用倒角 else { verticesCache.AddRange(OriginPatternVectorPoints(sides, radius, sideAngle, center)); // 添加中心点 - vh.AddVert(center, color32, uvCenter, uvCenter, uvCenter, Vector4.zero); + vertexList.AddVert(center, uvCenter, color32); var maxVpCount = sides * roundingTime + 1; var l = 2 * radius * Mathf.Sin(Mathf.PI / sides); - var lastPos = center; for (var i = 0; i < sides; i++) { Vector2 pCurr = verticesCache[i]; @@ -226,19 +247,18 @@ namespace XericLibrary MacroCurve.BezierCurve3(p0, p1, p2, p3, jTime, out var pos); pos.GetUVAtRect(rect, out var u, out var v); var uv = new Vector4(u, v, 1, 1); - vh.AddVert(pos, this.color, uv, uv, uv, Vector4.zero); + vertexList.AddVert(pos, uv, this.color); var index = roundingTime * i + j + 1; var next = index + 1; if (next >= maxVpCount) next = (next % maxVpCount) + 1; - vh.AddTriangle(0, index, next); - Debug.Log($"{jTime}|{pos}"); - lastPos = pos; + indexList.AddTriangle(0, index, next); } } } } + /// /// 图元基础外围图形顶点 /// @@ -286,9 +306,9 @@ namespace XericLibrary if (sides < 3) sides = 3; if (scale <= float.Epsilon) scale = float.Epsilon; SetVerticesDirty(); - BorderThickness = borderThickness; BorderColor = borderColor; + BorderColorLinear = borderColor2; FillColor = fillColor; } #endif diff --git a/Runtime/Renderer/XericRendererComponent.cs b/Runtime/Renderer/XericRendererComponent.cs index e3c476d..323aa9a 100644 --- a/Runtime/Renderer/XericRendererComponent.cs +++ b/Runtime/Renderer/XericRendererComponent.cs @@ -4,7 +4,7 @@ using UnityEngine; using UnityEngine.Pool; using UnityEngine.UI; -namespace XericLibrary +namespace XericLibrary.Runtime.Renderer { /// /// 自定义渲染组件 @@ -20,13 +20,13 @@ namespace XericLibrary private readonly List _vertexList = new List(); private readonly List _indexList = new List(); // 阵列原点 - private readonly List _arrayTransformList = new List(); + private List _arrayTransformList = new List(); // 中间数据 private List _temp_vertexList = new List(); private List _temp_indexList = new List(); private bool _dirty; - + #endregion #if UNITY_EDITOR @@ -54,6 +54,8 @@ namespace XericLibrary /// protected override void OnPopulateMesh(VertexHelper vh) { + if (vh == null) return; + vh.Clear(); ClearCache(); // 填充图元 OnPopulatePrefabricateVertex(_vertexList, _indexList); @@ -80,7 +82,6 @@ namespace XericLibrary { _vertexList.Clear(); _indexList.Clear(); - _arrayTransformList.Clear(); } /// @@ -88,9 +89,30 @@ namespace XericLibrary /// /// 偏移 /// 角度 - protected void AddArrayModifier(Vector3 offset, float angle) + public int AddArrayModifier(Vector3 offset, float angle) { + var index = _arrayTransformList.Count; _arrayTransformList.Add(new Vector4(offset.x, offset.y, offset.z, angle)); + Refresh(); + return index; + } + + /// + /// 清空阵列节点 + /// + public void ClearArrayModifier() + { + _arrayTransformList.Clear(); + Refresh(); + } + + public bool ModifyArrayModifier(int index, Vector3 offset, float angle) + { + if (index < 0 || index >= _arrayTransformList.Count) + return false; + _arrayTransformList[index] = new Vector4(offset.x, offset.y, offset.z, angle); + Refresh(); + return true; } #endregion @@ -104,27 +126,31 @@ namespace XericLibrary /// protected void ArrayVertexList(List trans) { + _temp_vertexList.Clear(); + _temp_indexList.Clear(); + if (trans == null || trans.Count == 0) { Copy(); return; } - foreach (var t in trans) - Copy2(t); + + for (int i = 0; i < trans.Count; i++) + Copy2(i, trans[i]); void Copy() { - _temp_vertexList = _vertexList.GetRange(0, _vertexList.Count); - _temp_indexList = _indexList.GetRange(0, _indexList.Count); + _temp_vertexList.AddRange(_vertexList.GetRange(0, _vertexList.Count)); + _temp_indexList.AddRange(_indexList.GetRange(0, _indexList.Count)); } - void Copy2(Vector4 transform) + void Copy2(int index, Vector4 transform) { - _temp_vertexList = _vertexList.Select(a => + _temp_vertexList.AddRange(_vertexList.Select(a => { - var newPos = new Vector3(a.position.x, a.position.y, a.position.z); - if (a.position.z != 0) + var newPos = a.position; + if (transform.w != 0) { - var rot = Quaternion.Euler(0, 0, a.position.z); + var rot = Quaternion.Euler(0, 0, transform.w); newPos = rot * newPos; } newPos += (Vector3)transform; @@ -139,8 +165,8 @@ namespace XericLibrary uv2 = a.uv2, uv3 = a.uv3, }; - }).ToList(); - _temp_indexList = _indexList.GetRange(0, _indexList.Count); + })); + _temp_indexList.AddRange(_indexList.Select(a => index * _vertexList.Count + a)); } }