update 0.6.1

This commit is contained in:
2026-07-09 17:08:14 +08:00
parent 97d41a1385
commit 53d8d539a3
89 changed files with 4486 additions and 241 deletions
-33
View File
@@ -1,33 +0,0 @@
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
/.vs/
.vs/
.idea/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# dll
!*.dll
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
/Developer/DLLDev/.idea
/Developer/DLLDev/CentralizeLog/.vs
/Developer/DLLDev/Deconstruction/.vs
/Developer/DLLDev/EditorLevel/.vs
/Developer/DLLDev/MainSolution/.vs
/Developer/DLLDev/ManagerStyle/.vs
/Developer/DLLDev/RescissionRework/.vs
/Developer/DLLDev/XericLibrary/.vs
+16 -4
View File
@@ -2,7 +2,21 @@
## [Unrealse]
* 添加运行时蓝图框架
## [0.6.1] 2026-07-09
添加:
* 添加支持跨平台的剪贴板功能。(webgl除外)
* 增加webgl的模板管理面板,可以管理并添加预设的js功能。
* 添加MacroPrefs库。
* 添加密码对话框。
* 嵌入了quickgraph
* 添加了更多高级绘制组件
* 添加了文本过滤器库
删除:
* 删除了nav导航接口,现在可以通过quickgraph实现导航。
## [0.6.0] 2026-06-26
@@ -27,10 +41,8 @@
* 添加了unity上预处理命令管理器
修复:
* SingleMonoBase.EditorInstance 重写:移除 Obsolete,父节点+子节点统一 HideAndDontSave,编辑器内不再因 GlobalInstance 报错。
* XericLifeCycleCore.GlobalInstance:编辑器模式自动走 EditorInstance(场景世界),不再走 DontDestroyOnLoad。
* 修复 PID 控制器导数公式(除以 Δt² 改为 Δt),解决无穷大溢出问题。
* 删除 PID 中无效的 NaN 安全检查(只能拦截 NaN,无法拦截 Infinity)。
* SingleMonoBase.EditorInstance 重写:修复编辑器报错的问题
* 修复 PID 控制器导数公式中无穷大溢出问题。
优化:
* 优化按键宏逻辑,添加了按键趋势统计。
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 43995da0c5deada479d25647709b2854
guid: 02e804ccbe95d97408762108b8b929c7
folderAsset: yes
DefaultImporter:
externalObjects: {}
+250
View File
@@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibraryEditor.XericComponent
{
[CustomEditor(typeof(UICurveRenderer))]
public class UICurveRendererEditor : Editor
{
private UICurveRenderer script;
// MaskableGraphic 基础属性
private SerializedProperty m_RaycastTarget;
private SerializedProperty m_Maskable;
private SerializedProperty m_Material;
private SerializedProperty m_Color;
// 曲线属性
private SerializedProperty controlPointsProp;
private SerializedProperty startColorProp;
private SerializedProperty endColorProp;
private SerializedProperty tessellationSegmentsProp;
private SerializedProperty arrowsProp;
private SerializedProperty autoRebuildProp;
// 折叠状态
private bool m_ControlPointsFoldout = true;
private bool m_ArrowsFoldout = true;
protected void OnEnable()
{
script = (UICurveRenderer)target;
FetchProperties();
}
private void FetchProperties()
{
m_RaycastTarget = serializedObject.FindProperty("m_RaycastTarget");
m_Maskable = serializedObject.FindProperty("m_Maskable");
m_Material = serializedObject.FindProperty("m_Material");
m_Color = serializedObject.FindProperty("m_Color");
controlPointsProp = serializedObject.FindProperty("controlPoints");
startColorProp = serializedObject.FindProperty("startColor");
endColorProp = serializedObject.FindProperty("endColor");
tessellationSegmentsProp = serializedObject.FindProperty("tessellationSegments");
arrowsProp = serializedObject.FindProperty("arrows");
autoRebuildProp = serializedObject.FindProperty("autoRebuild");
}
public override void OnInspectorGUI()
{
// 确保 SerializedProperty 已初始化
if (arrowsProp == null)
FetchProperties();
serializedObject.Update();
// MaskableGraphic 基础属性
EditorGUILayout.PropertyField(m_RaycastTarget);
EditorGUILayout.PropertyField(m_Maskable);
EditorGUILayout.PropertyField(m_Material, new GUIContent("材质"));
EditorGUILayout.PropertyField(m_Color, new GUIContent("顶点颜色"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("颜色", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(startColorProp, new GUIContent("起始颜色"));
EditorGUILayout.PropertyField(endColorProp, new GUIContent("结束颜色"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("曲线参数", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(tessellationSegmentsProp, new GUIContent("细分段数"));
// 控制点折叠列表
EditorGUILayout.Space();
m_ControlPointsFoldout = EditorGUILayout.Foldout(m_ControlPointsFoldout,
new GUIContent($"控制点 ({GetArrayLength(controlPointsProp)})"), true);
if (m_ControlPointsFoldout)
{
EditorGUI.indentLevel++;
DrawControlPointsList();
EditorGUI.indentLevel--;
}
// 箭头折叠列表
EditorGUILayout.Space();
m_ArrowsFoldout = EditorGUILayout.Foldout(m_ArrowsFoldout,
new GUIContent($"箭头装饰 ({arrowsProp.arraySize})"), true);
if (m_ArrowsFoldout)
{
EditorGUI.indentLevel++;
DrawArrowsList();
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(autoRebuildProp, new GUIContent("自动刷新"));
serializedObject.ApplyModifiedProperties();
if (GUI.changed)
{
if (script.autoRebuild && script.isActiveAndEnabled)
{
script.RebuildCurve();
}
}
}
private int GetArrayLength(SerializedProperty prop)
{
if (prop == null) return 0;
return prop.arraySize;
}
private void DrawControlPointsList()
{
if (controlPointsProp == null) return;
int count = controlPointsProp.arraySize;
int newCount = EditorGUILayout.IntField("数量", count);
if (newCount < 2) newCount = 2;
if (newCount != count)
{
controlPointsProp.arraySize = newCount;
}
for (int i = 0; i < controlPointsProp.arraySize; i++)
{
EditorGUILayout.BeginHorizontal();
var cp = controlPointsProp.GetArrayElementAtIndex(i);
// Vector3: (x,y)=位置, z=宽度
Vector3 val = cp.vector3Value;
EditorGUI.BeginChangeCheck();
val = EditorGUILayout.Vector3Field($"CP {i}", val);
if (EditorGUI.EndChangeCheck())
cp.vector3Value = val;
// 删除按钮(至少保留 2 个控制点)
GUI.enabled = controlPointsProp.arraySize > 2;
if (GUILayout.Button("×", GUILayout.Width(25)))
{
RemoveControlPointAt(i);
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
}
// 添加按钮
if (GUILayout.Button("+ 添加控制点"))
{
int idx = controlPointsProp.arraySize;
controlPointsProp.arraySize++;
if (idx > 0)
{
var prev = controlPointsProp.GetArrayElementAtIndex(idx - 1);
var prev2 = idx > 1 ? controlPointsProp.GetArrayElementAtIndex(idx - 2) : prev;
Vector3 prevVal = prev.vector3Value;
Vector3 prev2Val = prev2.vector3Value;
Vector3 dir = new Vector3(
(prevVal.x - prev2Val.x) * 0.5f,
(prevVal.y - prev2Val.y) * 0.5f,
10f);
dir = dir.normalized * 50f;
var newCp = controlPointsProp.GetArrayElementAtIndex(idx);
newCp.vector3Value = new Vector3(
prevVal.x + dir.x,
prevVal.y + dir.y,
prevVal.z);
}
else
{
var newCp = controlPointsProp.GetArrayElementAtIndex(0);
newCp.vector3Value = Vector3.zero;
}
}
}
private void RemoveControlPointAt(int index)
{
controlPointsProp.DeleteArrayElementAtIndex(index);
}
private void DrawArrowsList()
{
if (arrowsProp == null) return;
for (int i = 0; i < arrowsProp.arraySize; i++)
{
var arrow = arrowsProp.GetArrayElementAtIndex(i);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
var shapeProp = arrow.FindPropertyRelative("shape");
var reversedProp = arrow.FindPropertyRelative("reversed");
var progressProp = arrow.FindPropertyRelative("progress");
var widthProp = arrow.FindPropertyRelative("width");
var heightProp = arrow.FindPropertyRelative("height");
var depthProp = arrow.FindPropertyRelative("depthCompensation");
var colorProp = arrow.FindPropertyRelative("color");
EditorGUILayout.PropertyField(shapeProp, new GUIContent("形状"));
EditorGUILayout.PropertyField(reversedProp, new GUIContent("反向"));
EditorGUILayout.Slider(progressProp, 0f, 1f, new GUIContent("进度"));
EditorGUILayout.PropertyField(widthProp, new GUIContent("宽度"));
EditorGUILayout.PropertyField(heightProp, new GUIContent("高度"));
EditorGUILayout.Slider(depthProp, -1f, 1f, new GUIContent("深度补偿"));
EditorGUILayout.PropertyField(colorProp, new GUIContent("颜色"));
if (GUILayout.Button("× 删除此箭头"))
{
arrowsProp.DeleteArrayElementAtIndex(i);
break;
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(2);
}
if (GUILayout.Button("+ 添加箭头"))
{
int idx = arrowsProp.arraySize;
arrowsProp.arraySize++;
var newArrow = arrowsProp.GetArrayElementAtIndex(idx);
var shapeProp = newArrow.FindPropertyRelative("shape");
var reversedProp = newArrow.FindPropertyRelative("reversed");
var progressProp = newArrow.FindPropertyRelative("progress");
var widthProp = newArrow.FindPropertyRelative("width");
var heightProp = newArrow.FindPropertyRelative("height");
var depthProp = newArrow.FindPropertyRelative("depthCompensation");
var colorProp = newArrow.FindPropertyRelative("color");
shapeProp.enumValueIndex = 0;
reversedProp.boolValue = false;
progressProp.floatValue = 0.5f;
widthProp.floatValue = 20f;
heightProp.floatValue = 20f;
depthProp.floatValue = 0f;
colorProp.colorValue = Color.white;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de176d833dcb3f84b9a7da6addacc5da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+51
View File
@@ -0,0 +1,51 @@
using UnityEditor;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibraryEditor.XericComponent
{
[CustomEditor(typeof(UILineRendererV2))]
public class UILineRendererV2Editor : Editor
{
private UILineRendererV2 script;
private SerializedProperty m_RaycastTarget;
private SerializedProperty m_Maskable;
private SerializedProperty m_Material;
private SerializedProperty m_Color;
private SerializedProperty defaultThicknessProp;
private SerializedProperty defaultColorProp;
private SerializedProperty defaultUVModeProp;
protected virtual void OnEnable()
{
script = (UILineRendererV2)target;
m_RaycastTarget = serializedObject.FindProperty("m_RaycastTarget");
m_Maskable = serializedObject.FindProperty("m_Maskable");
m_Material = serializedObject.FindProperty("m_Material");
m_Color = serializedObject.FindProperty("m_Color");
defaultThicknessProp = serializedObject.FindProperty("defaultThickness");
defaultColorProp = serializedObject.FindProperty("defaultColor");
defaultUVModeProp = serializedObject.FindProperty("defaultUVMode");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// MaskableGraphic 基础属性
EditorGUILayout.PropertyField(m_RaycastTarget);
EditorGUILayout.PropertyField(m_Maskable);
EditorGUILayout.PropertyField(m_Material, new GUIContent("材质"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("全局默认设置", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(defaultThicknessProp);
EditorGUILayout.PropertyField(defaultColorProp);
EditorGUILayout.PropertyField(defaultUVModeProp);
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: baeb976551b20704e9120f9ca2b5ba3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+109
View File
@@ -0,0 +1,109 @@
using UnityEditor;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibraryEditor.XericComponent
{
[CustomEditor(typeof(UIPrimitiveRenderer))]
public class UIPrimitiveRendererEditor : Editor
{
private UIPrimitiveRenderer script;
private SerializedProperty m_RaycastTarget;
private SerializedProperty m_Maskable;
private SerializedProperty m_Material;
private SerializedProperty m_Color;
private SerializedProperty primitiveTypeProp;
private SerializedProperty sizeModeProp;
private SerializedProperty sizeProp;
private SerializedProperty centerOffsetProp;
private SerializedProperty sideCountProp;
private SerializedProperty chamferSizeProp;
private SerializedProperty chamferSegmentsProp;
private SerializedProperty bgColorProp;
private SerializedProperty centerColorProp;
private SerializedProperty borderColorProp;
private SerializedProperty borderThicknessProp;
private SerializedProperty autoRebuildProp;
protected virtual void OnEnable()
{
script = (UIPrimitiveRenderer)target;
m_RaycastTarget = serializedObject.FindProperty("m_RaycastTarget");
m_Maskable = serializedObject.FindProperty("m_Maskable");
m_Material = serializedObject.FindProperty("m_Material");
m_Color = serializedObject.FindProperty("m_Color");
primitiveTypeProp = serializedObject.FindProperty("primitiveType");
sizeModeProp = serializedObject.FindProperty("sizeMode");
sizeProp = serializedObject.FindProperty("size");
centerOffsetProp = serializedObject.FindProperty("centerOffset");
sideCountProp = serializedObject.FindProperty("sideCount");
chamferSizeProp = serializedObject.FindProperty("chamferSize");
chamferSegmentsProp = serializedObject.FindProperty("chamferSegments");
bgColorProp = serializedObject.FindProperty("bgColor");
centerColorProp = serializedObject.FindProperty("centerColor");
borderColorProp = serializedObject.FindProperty("borderColor");
borderThicknessProp = serializedObject.FindProperty("borderThickness");
autoRebuildProp = serializedObject.FindProperty("autoRebuild");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// MaskableGraphic 基础属性
EditorGUILayout.PropertyField(m_RaycastTarget);
EditorGUILayout.PropertyField(m_Maskable);
EditorGUILayout.PropertyField(m_Material, new GUIContent("材质"));
EditorGUILayout.PropertyField(m_Color, new GUIContent("顶点颜色"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("图元类型", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(primitiveTypeProp);
EditorGUILayout.PropertyField(sizeModeProp);
bool isPolygon = script.primitiveType == XericLibrary.Runtime.UIGraph.PrimitiveType.Polygon;
EditorGUILayout.Space();
EditorGUILayout.LabelField("尺寸", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(sizeProp, new GUIContent("尺寸 (XY)"));
EditorGUILayout.PropertyField(centerOffsetProp, new GUIContent("中心偏移"));
if (isPolygon)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("多边形设置", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(sideCountProp, new GUIContent("边数"));
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("倒角", EditorStyles.boldLabel);
EditorGUILayout.Slider(chamferSizeProp, 0f, 0.5f, new GUIContent("倒角尺寸"));
EditorGUILayout.IntSlider(chamferSegmentsProp, 1, 16, new GUIContent("细分段数"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("颜色", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(bgColorProp, new GUIContent("背景颜色"));
EditorGUILayout.PropertyField(centerColorProp, new GUIContent("中心颜色"));
EditorGUILayout.PropertyField(borderColorProp, new GUIContent("边框颜色"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("边框", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(borderThicknessProp, new GUIContent("边框厚度"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(autoRebuildProp, new GUIContent("自动刷新"));
serializedObject.ApplyModifiedProperties();
if (GUI.changed)
{
if (script.autoRebuild && script.isActiveAndEnabled)
{
script.RebuildPrimitive();
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24d520025eb64a84b91f7eb469d97e2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7758a0679c9038144a4b0d007d44618f
guid: 2a72833fcf054be4e83325b8149bfe8d
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,43 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: XUIDotBasic
m_Shader: {fileID: 4800000, guid: 858173d3f3725a1468cac9f50da734a4, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _ColorMask: 15
- _Hardness: 0.001
- _Keyword0: 0
- _Radius: 0.28
- _RectSize: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UseUIAlphaClip: 0
m_Colors:
- _BackgroundColor: {r: 1, g: 1, b: 1, a: 0}
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27854d851ac720b4db774811988c6b95
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,169 @@
Shader "XUIDotBasic"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_BackgroundColor( "BackgroundColor", Color ) = ( 1, 1, 1, 0 )
_Radius( "Radius", Range( 0, 1 ) ) = 0.5
_RectSize( "RectSize", Range( 0, 1 ) ) = 1
}
SubShader
{
LOD 0
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
Stencil
{
Ref [_Stencil]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
Comp [_StencilComp]
Pass [_StencilOp]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#define ASE_VERSION 19908
#pragma vertex vert
#pragma fragment frag
#pragma target 3.5
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#define ASE_NEEDS_FRAG_COLOR
#define ASE_NEEDS_TEXTURE_COORDINATES0
#define ASE_NEEDS_FRAG_TEXTURE_COORDINATES0
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
uniform float4 _BackgroundColor;
uniform float _RectSize;
uniform float _Radius;
v2f vert(appdata_t v )
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
v.vertex.xyz += float3( 0, 0, 0 ) ;
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = v.texcoord;
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
float2 texCoord3 = IN.texcoord.xy * float2( 1,1 ) + float2( 0,0 );
float temp_output_2_0_g2 = _RectSize;
float temp_output_3_0_g2 = _RectSize;
float2 appendResult21_g2 = (float2(temp_output_2_0_g2 , temp_output_3_0_g2));
float Radius25_g2 = max( min( min( abs( ( _Radius * 2 ) ), abs( temp_output_2_0_g2 ) ), abs( temp_output_3_0_g2 ) ), 1E-05 );
float temp_output_30_0_g2 = ( length( max( ( ( abs( (texCoord3*2.0 + -1.0) ) - appendResult21_g2 ) + Radius25_g2 ), 0.0 ) ) / Radius25_g2 );
float temp_output_23_0 = saturate( ( ( 1.0 - temp_output_30_0_g2 ) / fwidth( temp_output_30_0_g2 ) ) );
float4 lerpResult7 = lerp( _BackgroundColor , IN.color , temp_output_23_0);
half4 color = lerpResult7;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
color.rgb *= color.a;
return color;
}
ENDCG
}
}
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
@@ -1,5 +1,4 @@
// Made with Amplify Shader Editor v1.9.9.1
// Available at the Unity Asset Store - http://u3d.as/y3X
Shader "XUIBasic"
{
Properties
@@ -222,71 +221,4 @@ Shader "XUIBasic"
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
/*ASEBEGIN
Version=19901
Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;5;-1408,256;Inherit;False;Property;_CoordRotateAngle;CoordRotateAngle;2;0;Create;True;0;0;0;False;0;False;0;0;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.Vector4Node, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;28;-1920,0;Inherit;False;Property;_CoordTransform;CoordTransform;0;0;Create;True;0;0;0;False;0;False;1,1,0,0;1,1,0,0;0;5;FLOAT4;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.SimpleMultiplyOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;9;-1184,256;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;2;False;1;FLOAT;0
Node;AmplifyShaderEditor.ComponentMaskNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;30;-1664,96;Inherit;False;False;False;True;True;1;0;FLOAT4;0,0,0,0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.ComponentMaskNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;29;-1664,16;Inherit;False;True;True;False;False;1;0;FLOAT4;0,0,0,0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.TextureCoordinatesNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;1;-1408,0;Inherit;False;0;-1;2;3;2;SAMPLER2D;;False;0;FLOAT2;1,1;False;1;FLOAT2;0,0;False;5;FLOAT2;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.PiNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;8;-1024,256;Inherit;False;1;0;FLOAT;1;False;1;FLOAT;0
Node;AmplifyShaderEditor.Vector2Node, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;4;-1408,128;Inherit;False;Property;_CoordCenterv2;CoordCenter(v2);1;0;Create;True;0;0;0;False;0;False;0.5,0.5;0.5,0.5;0;3;FLOAT2;0;FLOAT;1;FLOAT;2
Node;AmplifyShaderEditor.Vector2Node, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;34;-768,-128;Inherit;False;Constant;_Vector0;Vector 0;11;0;Create;True;0;0;0;False;0;False;1,1;0,0;0;3;FLOAT2;0;FLOAT;1;FLOAT;2
Node;AmplifyShaderEditor.RotatorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;2;-896,0;Inherit;False;3;0;FLOAT2;0,0;False;1;FLOAT2;0,0;False;2;FLOAT;1;False;1;FLOAT2;0
Node;AmplifyShaderEditor.FunctionNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;32;-512,0;Inherit;False;PMod;-1;;3;f21979a9080cb044ca372458f5a37d88;1,33,0;2;2;FLOAT2;0,0;False;3;FLOAT2;1,1;False;1;FLOAT2;0
Node;AmplifyShaderEditor.AbsOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;36;-480,128;Inherit;False;1;0;FLOAT2;0,0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.StaticSwitch, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;26;-256,-128;Inherit;False;Property;_CoordRange;CoordRange;10;0;Create;True;0;0;0;False;0;False;0;0;0;True;;KeywordEnum;3;Normal;Mode;ABS;Create;True;True;All;9;1;FLOAT2;0,0;False;0;FLOAT2;0,0;False;2;FLOAT2;0,0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT2;0,0;False;6;FLOAT2;0,0;False;7;FLOAT2;0,0;False;8;FLOAT2;0,0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.ComponentMaskNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;18;0,-128;Inherit;False;True;False;True;True;1;0;FLOAT2;0,0;False;1;FLOAT;0
Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;23;0,96;Inherit;False;Property;_ExpNumber;ExpNumber;5;0;Create;True;0;0;0;False;0;False;1;0;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.SinOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;19;256,-64;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.CosOpNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;20;256,0;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.PowerNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;22;256,64;Inherit;False;False;2;0;FLOAT;0;False;1;FLOAT;1;False;1;FLOAT;0
Node;AmplifyShaderEditor.Vector2Node, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;25;0,384;Inherit;False;Property;_PolygonSizev2;PolygonSize(v2);9;0;Create;True;0;0;0;False;0;False;0.5,0.5;0,0;0;3;FLOAT2;0;FLOAT;1;FLOAT;2
Node;AmplifyShaderEditor.RangedFloatNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;24;0,288;Inherit;False;Property;_PolygonSides;PolygonSides;8;0;Create;True;0;0;0;False;0;False;5;0;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.FunctionNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;10;256,256;Inherit;False;Polygon;-1;;4;6906ef7087298c94c853d6753e182169;0;4;1;FLOAT2;0,0;False;2;FLOAT;5;False;3;FLOAT;0.5;False;4;FLOAT;0.5;False;1;FLOAT;0
Node;AmplifyShaderEditor.StaticSwitch, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;13;512,-128;Inherit;False;Property;_GradientType;GradientType;4;0;Create;True;0;0;0;False;0;False;0;0;0;True;;KeywordEnum;4;Linear;Sin;Cos;Exponential;Create;True;True;All;9;1;FLOAT;0;False;0;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;5;FLOAT;0;False;6;FLOAT;0;False;7;FLOAT;0;False;8;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.StaticSwitch, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;11;896,-128;Inherit;False;Property;_ElementType;Element Type;3;0;Create;True;0;0;0;False;0;False;0;0;0;True;;KeywordEnum;2;Gradient;Polygon;Create;True;True;All;9;1;FLOAT;0;False;0;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;5;FLOAT;0;False;6;FLOAT;0;False;7;FLOAT;0;False;8;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.ColorNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;15;896,-512;Inherit;False;Property;_ColorA;Color A;6;0;Create;True;0;0;0;False;0;False;0,1,0.07612991,1;0,1,0.07612991,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;16;896,-320;Inherit;False;Property;_ColorB;Color B;7;0;Create;True;0;0;0;False;0;False;1,0,0,1;1,0,0,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;14;1152,-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;7;1312,-384;Inherit;False;FLOAT4;4;0;FLOAT4;0,0,0,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;1472,-384;Float;False;True;-1;3;AmplifyShaderEditor.MaterialInspector;0;3;XUIBasic;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;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;True;3;False;0;;0;0;Standard;0;0;1;True;False;;False;0
WireConnection;9;0;5;0
WireConnection;30;0;28;0
WireConnection;29;0;28;0
WireConnection;1;0;29;0
WireConnection;1;1;30;0
WireConnection;8;0;9;0
WireConnection;2;0;1;0
WireConnection;2;1;4;0
WireConnection;2;2;8;0
WireConnection;32;2;2;0
WireConnection;32;3;34;0
WireConnection;36;0;2;0
WireConnection;26;1;2;0
WireConnection;26;0;32;0
WireConnection;26;2;36;0
WireConnection;18;0;26;0
WireConnection;19;0;18;0
WireConnection;20;0;18;0
WireConnection;22;0;18;0
WireConnection;22;1;23;0
WireConnection;10;1;26;0
WireConnection;10;2;24;0
WireConnection;10;3;25;1
WireConnection;10;4;25;2
WireConnection;13;1;18;0
WireConnection;13;0;19;0
WireConnection;13;2;20;0
WireConnection;13;3;22;0
WireConnection;11;1;13;0
WireConnection;11;0;10;0
WireConnection;14;0;15;0
WireConnection;14;1;16;0
WireConnection;14;2;11;0
WireConnection;7;0;14;0
WireConnection;0;0;7;0
ASEEND*/
//CHKSM=EF1BCF878CBE9DBF87503B5564BA985D4602AE12
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b66080c7d48f7f342b79af0fdbedec87
guid: df25563ae0441ef41b5b4c5b02eb44e2
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,47 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UICurve
m_Shader: {fileID: 4800000, guid: ed13c09bee4e83848ad102719497b140, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords:
- _COLORMODE_SPHERE
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _ColorMask: 15
- _Colormode: 1
- _EdgeExp: 0.001
- _EdgeStep: 0
- _EdgeStepRange: 0.1
- _Float0: 0.9
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UseUIAlphaClip: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6dd17cf52efd94849995132ec6a6854c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,41 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UILineRender
m_Shader: {fileID: 4800000, guid: 753025f901250db45b87f87c632c0bb0, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _ColorMask: 15
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UseUIAlphaClip: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7fd93918d0371ef419caaf4d889b9e8f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UIPattern
m_Shader: {fileID: 4800000, guid: f4e5237cd89192c4f8361db3dec5cc3b, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BackGroundColorAlpha: 0
- _BorderColorAlpha: 1
- _BorderExp: 0.001
- _BorderThinkness: 1
- _ColorMask: 15
- _ContentExp: 0.001
- _ContentThinkness: 1
- _Exp: 1
- _Exp1: 0.001
- _Fill: -0.51
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _Thinkness: 0
- _UseUIAlphaClip: 0
m_Colors:
- _BorderColor: {r: 1, g: 1, b: 1, a: 1}
- _BorderColor2: {r: 0, g: 0, b: 0, a: 0}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _FillColor: {r: 0.6698113, g: 0.6698113, b: 0.6698113, a: 0.7803922}
- _InlineBorderColor2: {r: 0, g: 0, b: 0, a: 0}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 546be89eca1f2ec42a5eb603594282c0
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,167 @@
Shader "XericLibrary/UIGraph/XericUICurve"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_EdgeExp( "Edge Exp", Range( 0.001, 1 ) ) = 0.8671591
_EdgeStep( "Edge Step", Range( 0, 1 ) ) = 0
_EdgeStepRange( "Edge Step Range", Range( 0, 0.5 ) ) = 0.1
}
SubShader
{
LOD 0
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
Stencil
{
Ref [_Stencil]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
Comp [_StencilComp]
Pass [_StencilOp]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#define ASE_VERSION 19908
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#define ASE_NEEDS_FRAG_COLOR
#define ASE_NEEDS_TEXTURE_COORDINATES0
#define ASE_NEEDS_FRAG_TEXTURE_COORDINATES0
#define ASE_NEEDS_TEXTURE_COORDINATES2
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
float4 ase_texcoord2 : TEXCOORD2;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
float4 ase_texcoord3 : TEXCOORD3;
};
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
uniform float _EdgeStep;
uniform float _EdgeStepRange;
uniform float _EdgeExp;
v2f vert(appdata_t v )
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.ase_texcoord3 = v.ase_texcoord2;
v.vertex.xyz += float3( 0, 0, 0 ) ;
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = v.texcoord;
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
float2 texCoord1 = IN.texcoord.xy * float2( 1,1 ) + float2( 0,0 );
float smoothstepResult24 = smoothstep( ( _EdgeStep - ( _EdgeStepRange * 2.0 ) ) , ( _EdgeStep + _EdgeStepRange ) , sin( ( texCoord1.x * UNITY_PI ) ));
float4 texCoord8 = IN.ase_texcoord3;
texCoord8.xy = IN.ase_texcoord3.xy * float2( 1,1 ) + float2( 0,0 );
float4 lerpResult19 = lerp( IN.color , ( IN.color * pow( saturate( smoothstepResult24 ) , _EdgeExp ) ) , texCoord8.x);
half4 color = lerpResult19;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
color.rgb *= color.a;
return color;
}
ENDCG
}
}
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ed13c09bee4e83848ad102719497b140
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -1,6 +1,5 @@
// Made with Amplify Shader Editor v1.9.9.1
// Available at the Unity Asset Store - http://u3d.as/y3X
Shader "XUIDotBasic"
Shader "XericLibrary/UIGraph/UILineRender"
{
Properties
{
@@ -48,7 +47,7 @@ Shader "XUIDotBasic"
{
Name "Default"
CGPROGRAM
#define ASE_VERSION 19901
#define ASE_VERSION 19908
#pragma vertex vert
#pragma fragment frag
@@ -60,7 +59,9 @@ Shader "XUIDotBasic"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#define ASE_NEEDS_TEXTURE_COORDINATES0
#define ASE_NEEDS_FRAG_TEXTURE_COORDINATES0
struct appdata_t
{
@@ -127,10 +128,11 @@ Shader "XUIDotBasic"
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
float4 appendResult2 = (float4(0.0 , 0.0 , 0.0 , 1.0));
float2 texCoord1 = IN.texcoord.xy * float2( 1,1 ) + float2( 0,0 );
float4 appendResult3 = (float4(texCoord1 , 0.0 , 1.0));
half4 color = appendResult2;
half4 color = appendResult3;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
@@ -151,12 +153,4 @@ Shader "XUIDotBasic"
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
/*ASEBEGIN
Version=19901
Node;AmplifyShaderEditor.DynamicAppendNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;2;-256,0;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.TextureCoordinatesNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;3;-1024,-128;Inherit;False;0;-1;2;3;2;SAMPLER2D;;False;0;FLOAT2;1,1;False;1;FLOAT2;0,0;False;5;FLOAT2;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.TemplateMultiPassMasterNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;0;0,0;Float;False;True;-1;3;AmplifyShaderEditor.MaterialInspector;0;3;XUIDotBasic;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;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;True;3;False;0;;0;0;Standard;0;0;1;True;False;;False;0
WireConnection;0;0;2;0
ASEEND*/
//CHKSM=2D7C452CAA9F9AC9B28539E122A9F5072E3B0DC3
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9dacf142d8c5aee44bbbd2bd080fa3f5
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,192 @@
Shader "XericLibrary/UIGraph/UIPattern"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_BorderColorAlpha( "BorderColorAlpha", Range( -0.1, 1 ) ) = 1
_BorderThinkness( "Border Thinkness", Range( 0.001, 1 ) ) = 1
_BorderExp( "Border Exp", Range( 0.001, 1 ) ) = 0.001
_BackGroundColorAlpha( "BackGroundColorAlpha", Range( -0.1, 1 ) ) = 0
_ContentThinkness( "Content Thinkness", Range( 0.001, 1 ) ) = 1
_ContentExp( "Content Exp", Range( 0.001, 1 ) ) = 0.001
}
SubShader
{
LOD 0
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
Stencil
{
Ref [_Stencil]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
Comp [_StencilComp]
Pass [_StencilOp]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#define ASE_VERSION 19908
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#define ASE_NEEDS_FRAG_NORMAL
#define ASE_NEEDS_FRAG_COLOR
#define ASE_NEEDS_TEXTURE_COORDINATES2
#define ASE_NEEDS_FRAG_TANGENT
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
float3 ase_normal : NORMAL;
float4 ase_texcoord2 : TEXCOORD2;
float4 ase_tangent : TANGENT;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
float3 ase_normal : NORMAL;
float4 ase_texcoord3 : TEXCOORD3;
float4 ase_tangent : TANGENT;
};
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
uniform float _BackGroundColorAlpha;
uniform float _ContentThinkness;
uniform float _ContentExp;
uniform float _BorderColorAlpha;
uniform float _BorderThinkness;
uniform float _BorderExp;
v2f vert(appdata_t v )
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.ase_normal = v.ase_normal;
OUT.ase_texcoord3.xy = v.ase_texcoord2.xy;
OUT.ase_tangent = v.ase_tangent;
//setting value to unused interpolator channels and avoid initialization warnings
OUT.ase_texcoord3.zw = 0;
v.vertex.xyz += float3( 0, 0, 0 ) ;
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = v.texcoord;
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
float4 appendResult60 = (float4(IN.ase_normal , saturate( max( max( IN.ase_normal.x, IN.ase_normal.y ), IN.ase_normal.z ) )));
float4 appendResult43 = (float4(IN.ase_normal , _BackGroundColorAlpha));
float4 lerpResult50 = lerp( appendResult60 , appendResult43 , saturate( sign( ( _BackGroundColorAlpha + 0.001 ) ) ));
float4 Background_Color39 = lerpResult50;
float4 Center_Color40 = IN.color;
float2 texCoord4 = IN.ase_texcoord3.xy * float2( 1,1 ) + float2( 0,0 );
float lerpResult107 = lerp( texCoord4.x , 0.0 , texCoord4.y);
float4 lerpResult64 = lerp( Background_Color39 , Center_Color40 , pow( ( ( _ContentThinkness - lerpResult107 ) / _ContentThinkness ) , _ContentExp ));
float4 appendResult57 = (float4(IN.ase_tangent.xyz , saturate( max( max( IN.ase_tangent.xyz.x, IN.ase_tangent.xyz.y ), IN.ase_tangent.xyz.z ) )));
float4 appendResult44 = (float4(IN.ase_tangent.xyz , _BorderColorAlpha));
float4 lerpResult51 = lerp( appendResult57 , appendResult44 , saturate( sign( ( _BorderColorAlpha + 0.001 ) ) ));
float4 Border_Color41 = lerpResult51;
float lerpResult108 = lerp( 0.0 , texCoord4.x , texCoord4.y);
float4 lerpResult82 = lerp( Background_Color39 , Border_Color41 , pow( ( ( _BorderThinkness - lerpResult108 ) / _BorderThinkness ) , _BorderExp ));
float4 lerpResult109 = lerp( lerpResult64 , lerpResult82 , texCoord4.y);
half4 color = lerpResult109;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
color.rgb *= color.a;
return color;
}
ENDCG
}
}
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f4e5237cd89192c4f8361db3dec5cc3b
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
-33
View File
@@ -1,33 +0,0 @@
fileFormatVersion: 2
guid: c754cc17629bc0f4cac54d1417f746b3
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
+1 -1
View File
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c9a8ab2ab6a3f6848a9c0a06cde9f082
guid: 9a4fde83e9857614fadbbb44cf04d8da
folderAsset: yes
DefaultImporter:
externalObjects: {}
+534
View File
@@ -0,0 +1,534 @@
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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad4b412c6e5cfe14194c22997929cdee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,245 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 曲线缓存渲染器基类
/// 管理 CurveCacheEntry 列表 + 扁平控制点/宽度/箭头缓存 + 脏标记增量重建
/// LateUpdate 中只重建脏标记范围内的曲线
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
public abstract class CurveCacheRendererBase : MaskableGraphic
{
#region 默认材质
private static Material s_DefaultMaterial;
private const string DefaultShaderPath = "XericLibrary/UIGraph/XericUICurve";
public override Material defaultMaterial
{
get
{
if (s_DefaultMaterial == null)
{
var shader = Shader.Find(DefaultShaderPath);
if (shader != null)
s_DefaultMaterial = new Material(shader);
else
s_DefaultMaterial = base.defaultMaterial;
}
return s_DefaultMaterial;
}
}
#endregion
#region 数据缓存
/// <summary>曲线条目列表</summary>
protected List<CurveCacheEntry> m_Curves = new List<CurveCacheEntry>();
/// <summary>所有曲线的控制点扁平存储</summary>
protected List<Vector2> m_ControlPoints = new List<Vector2>();
/// <summary>所有曲线的宽度扁平存储(与控制点一一对应)</summary>
protected List<float> m_Widths = new List<float>();
/// <summary>所有曲线的箭头扁平存储</summary>
protected List<ArrowHeadData> m_Arrows = new List<ArrowHeadData>();
/// <summary>模型顶点缓存</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_DirtyStartIndex = -1;
/// <summary>脏标记范围终点(曲线索引)</summary>
protected int m_DirtyEndIndex = -1;
#endregion
#region API
/// <summary>
/// 添加一条曲线
/// </summary>
/// <param name="entry">曲线缓存条目(控制点计数和起始索引需预先设置)</param>
/// <param name="controlPoints">控制点数组</param>
/// <param name="widths">宽度数组(长度需与控制点一致)</param>
/// <param name="arrows">箭头数组(可选)</param>
/// <returns>曲线索引</returns>
public int AddCurve(CurveCacheEntry entry, Vector2[] controlPoints, float[] widths, ArrowHeadData[] arrows = null)
{
int index = m_Curves.Count;
// 填充起始索引
entry.controlPointStartIndex = m_ControlPoints.Count;
entry.widthStartIndex = m_Widths.Count;
entry.arrowStartIndex = (arrows != null && arrows.Length > 0) ? m_Arrows.Count : -1;
entry.arrowCount = arrows?.Length ?? 0;
entry.controlPointCount = controlPoints.Length;
m_Curves.Add(entry);
m_ControlPoints.AddRange(controlPoints);
m_Widths.AddRange(widths);
if (arrows != null)
m_Arrows.AddRange(arrows);
MarkDirty(index);
return index;
}
/// <summary>
/// 移除指定曲线
/// </summary>
public void RemoveCurve(int index)
{
if (index < 0 || index >= m_Curves.Count) return;
m_Curves.RemoveAt(index);
RebuildAll();
}
/// <summary>
/// 标记指定曲线为脏
/// </summary>
public void SetCurveDirty(int index)
{
MarkDirty(index);
}
/// <summary>
/// 清空所有曲线
/// </summary>
public void ClearAll()
{
m_Curves.Clear();
m_ControlPoints.Clear();
m_Widths.Clear();
m_Arrows.Clear();
m_MeshVertexCache.Clear();
m_MeshIndexCache.Clear();
m_DirtyStartIndex = -1;
m_DirtyEndIndex = -1;
SetVerticesDirty();
}
#endregion
#region 脏标记
protected void MarkDirty(int index)
{
if (m_DirtyStartIndex < 0 || index < m_DirtyStartIndex)
m_DirtyStartIndex = index;
if (m_DirtyEndIndex < 0 || index > m_DirtyEndIndex)
m_DirtyEndIndex = index;
}
/// <summary>
/// 标记所有曲线为脏(下次 LateUpdate 全量重绘)
/// 适用于:更新控制点/宽度后,不改变元素数量的重绘
/// </summary>
public void RebuildAll()
{
m_DirtyStartIndex = 0;
m_DirtyEndIndex = m_Curves.Count - 1;
if (m_DirtyEndIndex < 0)
m_DirtyStartIndex = -1;
}
#endregion
#region LateUpdate
protected virtual void LateUpdate()
{
if (m_DirtyStartIndex < 0) return;
int start = m_DirtyStartIndex;
// 统计脏范围前的顶点/索引数(这些无需重建)
int preVertexCount = 0;
int preIndexCount = 0;
for (int i = 0; i < start; i++)
{
preVertexCount += m_Curves[i].vertexCount;
preIndexCount += m_Curves[i].triangleCount * 3;
}
// 从 start 开始重建所有曲线
var tempVerts = new List<UIVertex>();
var tempIndices = new List<int>();
int vAccum = preVertexCount;
int iAccum = preIndexCount;
for (int i = start; i < m_Curves.Count; i++)
{
var entry = m_Curves[i];
int vertBefore = tempVerts.Count;
int idxBefore = tempIndices.Count;
CurveMeshGenerator.GenerateCurve(
entry, m_ControlPoints, m_Widths, m_Arrows,
tempVerts, tempIndices);
var updated = entry;
updated.startVertexIndex = vAccum;
updated.vertexCount = tempVerts.Count - vertBefore;
updated.startIndexIndex = iAccum;
updated.triangleCount = (tempIndices.Count - idxBefore) / 3;
m_Curves[i] = updated;
vAccum += updated.vertexCount;
iAccum += updated.triangleCount * 3;
}
// 合并到主缓存
int totalVertices = vAccum;
int totalIndices = iAccum;
while (m_MeshVertexCache.Count < totalVertices)
m_MeshVertexCache.Add(new UIVertex());
while (m_MeshIndexCache.Count < totalIndices)
m_MeshIndexCache.Add(0);
for (int i = 0; i < tempVerts.Count; i++)
m_MeshVertexCache[preVertexCount + i] = tempVerts[i];
for (int i = 0; i < tempIndices.Count; i++)
m_MeshIndexCache[preIndexCount + i] = tempIndices[i] + preVertexCount;
SetVerticesDirty();
m_DirtyStartIndex = -1;
m_DirtyEndIndex = -1;
}
#endregion
#region OnPopulateMesh
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0) return;
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
}
#endregion
#region 生命周期
protected override void OnEnable()
{
base.OnEnable();
RebuildAll();
}
protected override void OnDestroy()
{
base.OnDestroy();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 905c29aca6f80f64f8da8cf8579868ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+333
View File
@@ -0,0 +1,333 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 曲线网格生成器
/// 将贝塞尔曲线细分为线段带状三角片 + 生成箭头装饰
///
/// 核心算法:
/// 1. 在 t∈[0,1] 上均匀采样 N+1 个点
/// 2. 每个采样点计算位置、切线、宽度、颜色
/// 3. 切线转 90° 得切法线,沿切法线位移 halfWidth 得左右边顶点
/// 4. 顺序连接左右边顶点形成 ribbon 带状:
/// 左[i]──右[i]──左[i+1]──右[i+1]──...
/// 每个四边形拆两个三角形
/// </summary>
public static class CurveMeshGenerator
{
/// <summary>圆圈箭头多边形边数</summary>
private const int CircleArrowSides = 8;
/// <summary>计算切线时的 epsilon 采样偏移</summary>
private const float TangentEpsilon = 0.001f;
/// <summary>
/// 生成单条曲线的网格数据
/// </summary>
public static void GenerateCurve(
in CurveCacheEntry entry,
List<Vector2> controlPointsPool,
List<float> widthsPool,
List<ArrowHeadData> arrowsPool,
List<UIVertex> vertexList,
List<int> indexList)
{
int segCount = entry.tessellationSegments;
if (segCount < 1) segCount = 4;
int sampleCount = segCount + 1; // N+1 个采样点
int cpStart = entry.controlPointStartIndex;
int cpCount = entry.controlPointCount;
int wStart = entry.widthStartIndex;
var ctrlPts = new Vector2[cpCount];
var wPts = new float[cpCount];
for (int i = 0; i < cpCount; i++)
{
ctrlPts[i] = controlPointsPool[cpStart + i];
wPts[i] = widthsPool[wStart + i];
}
int baseVertex = vertexList.Count;
UIVertex vert = new UIVertex
{
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f),
uv1 = new Vector2(1f, 0f),
};
// 1. 采样 + 生成左右边顶点
for (int s = 0; s < sampleCount; s++)
{
float t = (float)s / segCount;
Vector2 pos = MacroCurve.BezierCurve(ctrlPts, t); // 曲线上点
float width = MacroCurve.BezierCurve(wPts, t); // 当前宽度
Vector2 tangent = ComputeTangent(ctrlPts, t); // 切线方向
Vector2 normal = new Vector2(-tangent.y, tangent.x); // 切法线(左转90°)
MacroCurve.BezierCurve1(entry.startColor, entry.endColor, t, out Color col);
Color32 color = col;
float halfW = width * 0.5f;
float uv = (float)s / segCount;
// 左边顶点
vert.position = pos - normal * halfW;
vert.color = color;
vert.uv0 = new Vector2(0f, uv);
vertexList.Add(vert);
// 右边顶点
vert.position = pos + normal * halfW;
vert.color = color;
vert.uv0 = new Vector2(1f, uv);
vertexList.Add(vert);
}
// 2. 连接带状三角形
// 左[i]=v[i*2], 右[i]=v[i*2+1]
// 四边形 i→i+1: (左i, 右i, 左i+1) + (右i, 右i+1, 左i+1)
for (int s = 0; s < segCount; s++)
{
int bv = baseVertex + s * 2;
indexList.Add(bv + 0); // 左i
indexList.Add(bv + 1); // 右i
indexList.Add(bv + 2); // 左i+1
indexList.Add(bv + 1); // 右i
indexList.Add(bv + 3); // 右i+1
indexList.Add(bv + 2); // 左i+1
}
// 3. 生成箭头
if (entry.arrowCount > 0 && entry.arrowStartIndex >= 0)
{
for (int a = 0; a < entry.arrowCount; a++)
{
var arrow = arrowsPool[entry.arrowStartIndex + a];
GenerateArrow(arrow, ctrlPts, vertexList, indexList);
}
}
}
/// <summary>
/// 计算曲线上 t 处的切线方向(差分法)
/// </summary>
private static Vector2 ComputeTangent(Vector2[] ctrlPts, float t)
{
float t0 = Mathf.Max(0f, t - TangentEpsilon);
float t1 = Mathf.Min(1f, t + TangentEpsilon);
Vector2 tangent = MacroCurve.BezierCurve(ctrlPts, t1) - MacroCurve.BezierCurve(ctrlPts, t0);
if (tangent.sqrMagnitude < float.Epsilon)
tangent = Vector2.up;
else
tangent.Normalize();
return tangent;
}
#region 箭头
private static void GenerateArrow(
ArrowHeadData arrow,
Vector2[] controlPoints,
List<UIVertex> vertexList,
List<int> indexList)
{
float t = Mathf.Clamp01(arrow.progress);
Vector2 pos = MacroCurve.BezierCurve(controlPoints, t);
Vector2 tangent = ComputeTangent(controlPoints, t);
if (arrow.reversed)
tangent = -tangent;
// 深度补偿:半长偏移(箭头中心在本地x=0,尖端在+width*0.5,尾部在-width*0.5)
pos += tangent * arrow.width * 0.5f * arrow.depthCompensation;
int baseVert = vertexList.Count;
GenerateArrowShape(arrow.shape, pos, tangent, arrow.width, arrow.height, arrow.color,
vertexList, indexList, baseVert, t);
}
private static void GenerateArrowShape(
ArrowShape shape,
Vector2 position,
Vector2 tangent,
float width,
float height,
Color32 color,
List<UIVertex> vertexList,
List<int> indexList,
int baseVertex,
float progressT)
{
float cos = tangent.x;
float sin = tangent.y;
Vector2 TransformPoint(float ux, float uy)
{
float x = ux * width;
float y = uy * height;
float rx = x * cos - y * sin;
float ry = x * sin + y * cos;
return position + new Vector2(rx, ry);
}
UIVertex vert = new UIVertex
{
color = color,
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f),
uv0 = new Vector2(0.5f, progressT),
uv1 = new Vector2(0f, 1f)
};
switch (shape)
{
case ArrowShape.Triangle:
{
vert.position = TransformPoint(0.5f, 0f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f, -0.3f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f, 0.3f);
vertexList.Add(vert);
indexList.Add(baseVertex + 0);
indexList.Add(baseVertex + 1);
indexList.Add(baseVertex + 2);
break;
}
case ArrowShape.HollowTriangle:
{
float tailOff = -0.1f;
// 外三角
vert.position = TransformPoint(0.5f, 0f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f, -0.3f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f, 0.3f);
vertexList.Add(vert);
// 内三角(缩小 + 向尾部偏移)
vert.position = TransformPoint(0.5f * 0.5f + tailOff, 0f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f * 0.5f + tailOff, -0.3f * 0.5f * 0.6f);
vertexList.Add(vert);
vert.position = TransformPoint(-0.5f * 0.5f + tailOff, 0.3f * 0.5f * 0.6f);
vertexList.Add(vert);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 5);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 2);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 4);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 3);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 3); indexList.Add(baseVertex + 5);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 2);
break;
}
case ArrowShape.Arrow:
{
float headBaseU = 0.5f - 0.35f; // 头部基座 x=0.15
float shaftEndU = -0.8f; // 杆尾
vert.position = TransformPoint(0.5f, 0f);
vertexList.Add(vert); // 0: 尖端
vert.position = TransformPoint(headBaseU, -0.25f);
vertexList.Add(vert); // 1: 左翼
vert.position = TransformPoint(headBaseU, 0.25f);
vertexList.Add(vert); // 2: 右翼
vert.position = TransformPoint(headBaseU, -0.06f);
vertexList.Add(vert); // 3: 杆起点下
vert.position = TransformPoint(headBaseU, 0.06f);
vertexList.Add(vert); // 4: 杆起点上
vert.position = TransformPoint(shaftEndU, -0.06f);
vertexList.Add(vert); // 5: 杆尾下
vert.position = TransformPoint(shaftEndU, 0.06f);
vertexList.Add(vert); // 6: 杆尾上
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 6);
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 1);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 5);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 3);
break;
}
case ArrowShape.Circle:
{
float r = 0.4f;
vert.position = TransformPoint(0f, 0f);
vertexList.Add(vert);
for (int i = 0; i < CircleArrowSides; i++)
{
float angle = i * Mathf.PI * 2f / CircleArrowSides;
vert.position = TransformPoint(Mathf.Cos(angle) * r, Mathf.Sin(angle) * r);
vertexList.Add(vert);
}
for (int i = 0; i < CircleArrowSides; i++)
{
int next = (i + 1) % CircleArrowSides;
indexList.Add(baseVertex + 0);
indexList.Add(baseVertex + 1 + i);
indexList.Add(baseVertex + 1 + next);
}
break;
}
case ArrowShape.Square:
{
float hs = 0.35f;
vert.position = TransformPoint(-hs, -hs);
vertexList.Add(vert);
vert.position = TransformPoint(hs, -hs);
vertexList.Add(vert);
vert.position = TransformPoint(hs, hs);
vertexList.Add(vert);
vert.position = TransformPoint(-hs, hs);
vertexList.Add(vert);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 3);
break;
}
case ArrowShape.ReverseArrow:
{
float rHeadBaseU = 0.5f - 0.35f;
float rShaftEndU = -0.8f;
vert.position = TransformPoint(0.5f, 0f);
vertexList.Add(vert);
vert.position = TransformPoint(rHeadBaseU, -0.25f);
vertexList.Add(vert);
vert.position = TransformPoint(rHeadBaseU, 0.25f);
vertexList.Add(vert);
vert.position = TransformPoint(rHeadBaseU, -0.06f);
vertexList.Add(vert);
vert.position = TransformPoint(rHeadBaseU, 0.06f);
vertexList.Add(vert);
vert.position = TransformPoint(rShaftEndU, -0.06f);
vertexList.Add(vert);
vert.position = TransformPoint(rShaftEndU, 0.06f);
vertexList.Add(vert);
indexList.Add(baseVertex + 0); indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 2);
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 4); indexList.Add(baseVertex + 6);
indexList.Add(baseVertex + 2); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 1);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 6); indexList.Add(baseVertex + 5);
indexList.Add(baseVertex + 1); indexList.Add(baseVertex + 5); indexList.Add(baseVertex + 3);
break;
}
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3b0e0346140ad4847a65df78853dd3dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,86 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 箭头形状枚举
/// </summary>
public enum ArrowShape : byte
{
/// <summary>实心三角箭头</summary>
Triangle,
/// <summary>空心三角箭头</summary>
HollowTriangle,
/// <summary>箭矢箭头(翼+杆)</summary>
Arrow,
/// <summary>圆形头</summary>
Circle,
/// <summary>方形头</summary>
Square,
/// <summary>反向箭头(箭尾)</summary>
ReverseArrow,
}
/// <summary>
/// 箭头装饰数据
/// </summary>
[Serializable]
public struct ArrowHeadData
{
/// <summary>箭头形状</summary>
public ArrowShape shape;
/// <summary>是否反向</summary>
public bool reversed;
/// <summary>箭头出现在曲线上的位置 (0~1)</summary>
[Range(0f, 1f)]
public float progress;
/// <summary>箭头宽度(沿切线方向)</summary>
public float width;
/// <summary>箭头高度(垂直切线方向)</summary>
public float height;
/// <summary>深度补偿:-1~1,箭头沿切线偏移 width*0.5*depth 距离。
/// -1:尾部对准曲线点(头部向前伸)
/// 0:中心对准曲线点
/// +1:头部对准曲线点(尾部向后拉)</summary>
[Range(-1f, 1f)]
public float depthCompensation;
/// <summary>箭头颜色</summary>
public Color32 color;
}
/// <summary>
/// 曲线缓存条目
/// 控制点、宽度、箭头数据统一存放在外部扁平列表中,
/// 本结构体只记录起始索引和计数。
/// </summary>
[Serializable]
public struct CurveCacheEntry
{
/// <summary>在网格顶点缓存中的起始索引(由 Base 类回填)</summary>
public int startVertexIndex;
/// <summary>该曲线占用的顶点数(由 Base 类回填)</summary>
public int vertexCount;
/// <summary>在网格索引缓存中的起始索引(由 Base 类回填)</summary>
public int startIndexIndex;
/// <summary>三角形数量(由 Base 类回填)</summary>
public int triangleCount;
/// <summary>控制点数量(≥4,默认三阶贝塞尔)</summary>
public int controlPointCount;
/// <summary>在统一控制点缓存中的起始索引</summary>
public int controlPointStartIndex;
/// <summary>在统一宽度缓存中的起始索引</summary>
public int widthStartIndex;
/// <summary>起始颜色</summary>
public Color32 startColor;
/// <summary>结束颜色</summary>
public Color32 endColor;
/// <summary>在统一箭头缓存中的起始索引(-1 表示无箭头)</summary>
public int arrowStartIndex;
/// <summary>箭头数量</summary>
public int arrowCount;
/// <summary>曲线细分段数</summary>
public int tessellationSegments;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6ce7575b6b9e6a45bdba82ae35018e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+274
View File
@@ -0,0 +1,274 @@
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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24c8f213d27953c419e17ce974bbf594
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+58
View File
@@ -0,0 +1,58 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 线段端点数据(所有字段需为 blittable 类型以支持 NativeArray/Job)
/// </summary>
[Serializable]
public struct LineVertexData
{
public Vector2 position; // 端点位置(局部坐标)
public Color32 color; // 端点颜色
public float thickness; // 端点宽度
public Vector4 userData; // 自定义扩展数据 → 映射到 UV2
}
/// <summary>
/// UV1 均分模式(每条线段独立)
/// </summary>
public enum UVMode : byte
{
/// <summary>按实际线路距离均分</summary>
ByDistance,
/// <summary>按线段数量均分</summary>
BySegment,
}
/// <summary>
/// 线段范围记录
/// </summary>
[Serializable]
public struct LineSegmentRange
{
public int startVertexIndex; // 在 vertexData 中的起始索引
public int vertexCount; // 该线段端点数量(>= 2)
public UVMode uvMode; // UV1 均分模式
}
/// <summary>
/// Job 输入:单段线段的顶点生成参数
/// </summary>
[Serializable]
public struct SegmentJobInput
{
public LineVertexData start;
public LineVertexData end;
public LineVertexData prevEnd; // 上个线段终点(仅 hasPrev=1 时有效)
public LineVertexData nextStart; // 下个线段起点(仅 hasNext=1 时有效)
public byte hasPrev; // 0=无效, 1=有效
public byte hasNext; // 0=无效, 1=有效
public int segmentIndex;
public int totalSegmentCount;
public float startLineUV;
public float endLineUV;
public UVMode uvMode;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba184a89add023d42b93dbc715acc1c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,229 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 图元缓存渲染器基类
/// 管理 PrimitiveCacheEntry 列表 + 脏标记范围更新
/// LateUpdate 中只重建脏标记范围内的图元
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
public abstract class PrimitiveCacheRendererBase : MaskableGraphic
{
#region 默认材质
private static Material s_DefaultMaterial;
private const string DefaultShaderPath = "XericLibrary/UIGraph/UIPattern";
/// <summary>
/// 获取图元渲染器的默认材质(基于 UIPattern.shader)
/// </summary>
public override Material defaultMaterial
{
get
{
if (s_DefaultMaterial == null)
{
var shader = Shader.Find(DefaultShaderPath);
if (shader != null)
s_DefaultMaterial = new Material(shader);
else
s_DefaultMaterial = base.defaultMaterial;
}
return s_DefaultMaterial;
}
}
#endregion
#region 数据缓存
/// <summary>
/// 图元列表
/// </summary>
protected List<PrimitiveCacheEntry> m_Primitives = new List<PrimitiveCacheEntry>();
/// <summary>
/// 模型顶点缓存
/// </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_DirtyStartIndex = -1;
/// <summary>
/// 脏标记范围终点(图元索引)
/// </summary>
protected int m_DirtyEndIndex = -1;
#endregion
#region API
/// <summary>
/// 添加一个图元
/// </summary>
/// <returns>图元索引</returns>
public int AddPrimitive(PrimitiveCacheEntry entry)
{
int index = m_Primitives.Count;
m_Primitives.Add(entry);
MarkDirty(index);
return index;
}
/// <summary>
/// 移除指定图元
/// </summary>
public void RemovePrimitive(int index)
{
if (index < 0 || index >= m_Primitives.Count) return;
m_Primitives.RemoveAt(index);
RebuildAll();
}
/// <summary>
/// 标记指定图元为脏
/// </summary>
public void SetPrimitiveDirty(int index)
{
MarkDirty(index);
}
/// <summary>
/// 清空所有图元
/// </summary>
public void ClearAll()
{
m_Primitives.Clear();
m_MeshVertexCache.Clear();
m_MeshIndexCache.Clear();
m_DirtyStartIndex = -1;
m_DirtyEndIndex = -1;
SetVerticesDirty();
}
#endregion
#region 脏标记
protected void MarkDirty(int index)
{
if (m_DirtyStartIndex < 0 || index < m_DirtyStartIndex)
m_DirtyStartIndex = index;
if (m_DirtyEndIndex < 0 || index > m_DirtyEndIndex)
m_DirtyEndIndex = index;
}
/// <summary>
/// 标记所有图元为脏(下次 LateUpdate 全量重绘)
/// 适用于:更新顶点坐标后,不改变元素数量的重绘
/// </summary>
public void RebuildAll()
{
m_DirtyStartIndex = 0;
m_DirtyEndIndex = m_Primitives.Count - 1;
if (m_DirtyEndIndex < 0)
m_DirtyStartIndex = -1;
}
#endregion
#region LateUpdate
protected virtual void LateUpdate()
{
if (m_DirtyStartIndex < 0) return;
// 全量重建策略:从脏范围起点开始,重绘该条目及后续所有条目
// 保证所有条目 startVertexIndex/startIndexIndex 始终正确
int start = m_DirtyStartIndex;
// 统计脏范围前的顶点/索引数(这些条目无需重建)
int preVertexCount = 0;
int preIndexCount = 0;
for (int i = 0; i < start; i++)
{
preVertexCount += m_Primitives[i].vertexCount;
preIndexCount += m_Primitives[i].triangleCount * 3;
}
// 从 start 开始重建所有条目
var tempVerts = new List<UIVertex>();
var tempIndices = new List<int>();
int vAccum = preVertexCount;
int iAccum = preIndexCount;
for (int i = start; i < m_Primitives.Count; i++)
{
var entry = m_Primitives[i];
int vertBefore = tempVerts.Count;
int idxBefore = tempIndices.Count;
PrimitiveMeshGenerator.GeneratePrimitive(entry, tempVerts, tempIndices);
var updated = entry;
updated.startVertexIndex = vAccum;
updated.vertexCount = tempVerts.Count - vertBefore;
updated.startIndexIndex = iAccum;
updated.triangleCount = (tempIndices.Count - idxBefore) / 3;
m_Primitives[i] = updated;
vAccum += updated.vertexCount;
iAccum += updated.triangleCount * 3;
}
// 合并到主缓存
int totalVertices = vAccum;
int totalIndices = iAccum;
while (m_MeshVertexCache.Count < totalVertices)
m_MeshVertexCache.Add(new UIVertex());
while (m_MeshIndexCache.Count < totalIndices)
m_MeshIndexCache.Add(0);
for (int i = 0; i < tempVerts.Count; i++)
m_MeshVertexCache[preVertexCount + i] = tempVerts[i];
for (int i = 0; i < tempIndices.Count; i++)
m_MeshIndexCache[preIndexCount + i] = tempIndices[i] + preVertexCount;
SetVerticesDirty();
m_DirtyStartIndex = -1;
m_DirtyEndIndex = -1;
}
#endregion
#region OnPopulateMesh
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0) return;
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
}
#endregion
#region 生命周期
protected override void OnEnable()
{
base.OnEnable();
RebuildAll();
}
protected override void OnDestroy()
{
base.OnDestroy();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28047425f50534b46a5ad5b7a924bd30
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,390 @@
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; // 输出:实际生成的三角形数
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ae63ff723ba749448c841d5afe21ac2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,80 @@
using UnityEngine;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 图元类型
/// </summary>
public enum PrimitiveType : byte
{
/// <summary>正多边形(内切圆/外切圆)</summary>
Polygon,
/// <summary>矩形(内切椭圆/外切圆比例)</summary>
Rectangle,
}
/// <summary>
/// 尺寸模式
/// </summary>
public enum SizeMode : byte
{
/// <summary>内切圆 — 半径延伸到各边中点</summary>
InscribedCircle,
/// <summary>外切圆 — 半径延伸到各顶点</summary>
CircumscribedCircle,
/// <summary>内切椭圆 — XY各自独立的内切半径</summary>
InscribedEllipse,
/// <summary>外切圆比例 — 外切圆半径 + XY比例</summary>
CircumscribedRatio,
}
/// <summary>
/// 图元参数
/// 顶点通道分配:
/// uv0: 面片内归一化坐标 (0~1)
/// uv1: (centerDist, isBorder)
/// normal: (bg.r, bg.g, bg.b) — 0~1
/// tangent: (border.r, border.g, border.b, border.a) — 0~1
/// </summary>
public struct PrimitiveParams
{
public float axisScaleX;
public float axisScaleY;
public float sideCount;
public float chamferSize;
/// <summary>倒角细分段数(仅网格生成使用)</summary>
public int chamferSegments;
public Color32 bgColor;
public Color32 centerColor;
public Color32 borderColor;
public float borderThickness;
}
/// <summary>
/// 图元缓存条目
/// </summary>
public struct PrimitiveCacheEntry
{
/// <summary>在网格顶点缓存中的起始索引</summary>
public int startVertexIndex;
/// <summary>该图元占用的顶点数</summary>
public int vertexCount;
/// <summary>在网格索引缓存中的起始索引</summary>
public int startIndexIndex;
/// <summary>三角形数量</summary>
public int triangleCount;
public PrimitiveType type;
public SizeMode sizeMode;
/// <summary>图元中心(局部坐标)</summary>
public Vector2 center;
/// <summary>图元尺寸</summary>
public Vector2 size;
/// <summary>旋转角度(度数,0 = 正上方)</summary>
public float angle;
/// <summary>图元参数</summary>
public PrimitiveParams @params;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b233b049f5be67e47a89d1f9bee867b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+121
View File
@@ -0,0 +1,121 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 曲线绘制器组件
/// 支持多阶贝塞尔曲线,可配置控制点、宽度渐变、颜色和箭头装饰
/// </summary>
[AddComponentMenu("Xeric Library/UI/UICurveRenderer", 16)]
public class UICurveRenderer : CurveCacheRendererBase
{
/// <summary>控制点,(x,y)=位置, z=宽度</summary>
public Vector3[] controlPoints = new Vector3[]
{
new Vector3(-150f, 0f, 10f),
new Vector3(-50f, 100f, 10f),
new Vector3(50f, -100f, 10f),
new Vector3(150f, 0f, 10f),
};
public Color32 startColor = Color.white;
public Color32 endColor = Color.white;
[Range(4, 128)]
public int tessellationSegments = 32;
public List<ArrowHeadData> arrows = new List<ArrowHeadData>();
public bool autoRebuild = true;
private CurveCacheEntry? m_CurrentEntry;
protected override void OnEnable()
{
base.OnEnable();
if (autoRebuild) RebuildCurve();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (controlPoints == null || controlPoints.Length < 2)
controlPoints = new Vector3[] { new Vector3(0f, 0f, 10f), new Vector3(100f, 0f, 10f) };
if (tessellationSegments < 4) tessellationSegments = 4;
if (autoRebuild && isActiveAndEnabled)
{
EditorApplication.delayCall += () =>
{
if (this != null && isActiveAndEnabled)
RebuildCurve();
};
}
}
#endif
/// <summary>
/// 重建曲线
/// </summary>
public void RebuildCurve()
{
ClearAll();
if (controlPoints == null || controlPoints.Length < 2) return;
// 从 Vector3[] 中提取 Vector2[] 位置和 float[] 宽度
int count = controlPoints.Length;
var ctrlPts2 = new Vector2[count];
var widths = new float[count];
for (int i = 0; i < count; i++)
{
ctrlPts2[i] = (Vector2)controlPoints[i];
widths[i] = controlPoints[i].z;
}
var entry = new CurveCacheEntry
{
startColor = startColor,
endColor = endColor,
tessellationSegments = tessellationSegments,
};
AddCurve(entry, ctrlPts2, widths, arrows?.ToArray());
m_CurrentEntry = m_Curves.Count > 0 ? m_Curves[0] : (CurveCacheEntry?)null;
}
/// <summary>
/// 设置控制点(z=宽度)
/// </summary>
public void SetControlPoints(Vector3[] points)
{
controlPoints = points;
RebuildCurve();
}
/// <summary>
/// 添加箭头
/// </summary>
public void AddArrow(ArrowHeadData arrow)
{
if (arrows == null) arrows = new List<ArrowHeadData>();
arrows.Add(arrow);
RebuildCurve();
}
/// <summary>
/// 移除箭头
/// </summary>
public void RemoveArrow(int index)
{
if (arrows == null || index < 0 || index >= arrows.Count) return;
arrows.RemoveAt(index);
RebuildCurve();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3189cafe7a5628048b2243986703e3e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+115
View File
@@ -0,0 +1,115 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 新一代线段渲染器组件
/// 基于 CacheLineRendererBase 的缓存 + Jobs 架构
/// </summary>
[AddComponentMenu("Xeric Library/UI/UILineRenderer V2", 15)]
public class UILineRendererV2 : CacheLineRendererBase
{
[Header("全局默认设置")]
[Range(0.01f, 200f)]
[Tooltip("默认线段宽度")]
public float defaultThickness = 10f;
[Tooltip("默认线段颜色")]
public Color defaultColor = Color.white;
[Tooltip("默认 UV1 均分模式")]
public UVMode defaultUVMode = UVMode.ByDistance;
/// <summary>
/// 绘制一条简单线段(2个端点)
/// </summary>
public void DrawLine(Vector2 from, Vector2 to)
{
DrawLine(from, to, defaultColor, defaultThickness, defaultUVMode);
}
/// <summary>
/// 绘制一条简单线段(指定颜色和宽度)
/// </summary>
public void DrawLine(Vector2 from, Vector2 to, Color color, float thickness, UVMode uvMode = UVMode.ByDistance)
{
BeginLine(uvMode);
AddLineNode(from, color, thickness);
AddLineNode(to, color, thickness);
EndLine();
}
/// <summary>
/// 绘制折线(多点连接)
/// </summary>
public void DrawPolyline(Vector2[] points, Color color, float thickness, UVMode uvMode = UVMode.ByDistance)
{
if (points == null || points.Length < 2) return;
LineVertexData[] vertices = new LineVertexData[points.Length];
for (int i = 0; i < points.Length; i++)
{
vertices[i] = new LineVertexData
{
position = points[i],
color = color,
thickness = thickness,
userData = Vector4.zero
};
}
AddLine(vertices, uvMode);
}
/// <summary>
/// 绘制折线(每个端点独立颜色/宽度)
/// </summary>
public void DrawPolyline(LineVertexData[] vertices, UVMode uvMode = UVMode.ByDistance)
{
if (vertices == null || vertices.Length < 2) return;
AddLine(vertices, uvMode);
}
/// <summary>
/// 简单绘制矩形框
/// </summary>
public void DrawRect(Rect rect, Color color, float thickness)
{
Vector2 center = rect.center;
float hw = rect.width * 0.5f;
float hh = rect.height * 0.5f;
var pts = new Vector2[]
{
new Vector2(center.x - hw, center.y - hh),
new Vector2(center.x + hw, center.y - hh),
new Vector2(center.x + hw, center.y + hh),
new Vector2(center.x - hw, center.y + hh),
new Vector2(center.x - hw, center.y - hh),
};
DrawPolyline(pts, color, thickness, UVMode.ByDistance);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (defaultThickness < 0.01f)
defaultThickness = 0.01f;
}
[MenuItem("GameObject/Xeric Library/UI/UILineRenderer V2", false, 10)]
private static void CreateGameObject()
{
var go = new GameObject(nameof(UILineRendererV2), typeof(UILineRendererV2));
if (Selection.activeGameObject != null)
go.transform.SetParent(Selection.activeGameObject.transform);
Undo.RegisterCreatedObjectUndo(go, $"Create {nameof(UILineRendererV2)}");
Selection.activeObject = go;
}
#endif
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d43989c17d4c7d640988acb51713390a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+144
View File
@@ -0,0 +1,144 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 图元绘制器组件
/// 支持多边形/矩形的内切圆/外切圆/椭圆等尺寸模式
/// 参数写入 UV2-UV7 通道
/// </summary>
[AddComponentMenu("Xeric Library/UI/UIPrimitiveRenderer", 15)]
public class UIPrimitiveRenderer : PrimitiveCacheRendererBase
{
public PrimitiveType primitiveType = PrimitiveType.Polygon;
public SizeMode sizeMode = SizeMode.InscribedCircle;
public Vector2 size = new Vector2(100f, 100f);
public Vector2 centerOffset = Vector2.zero;
[Range(-180f, 180f)]
public float angle = 0f;
[Range(3, 64)]
public int sideCount = 6;
[Range(0f, 1f)]
public float chamferSize = 0f;
[Range(1, 16)]
public int chamferSegments = 4;
public Color bgColor = Color.gray;
public Color centerColor = Color.white;
public Color borderColor = Color.black;
[Range(0f, 50f)]
public float borderThickness = 2f;
public bool autoRebuild = true;
private PrimitiveCacheEntry? m_CurrentEntry;
protected override void OnEnable()
{
base.OnEnable();
if (autoRebuild) RebuildPrimitive();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (sideCount < 3) sideCount = 3;
if (autoRebuild && isActiveAndEnabled)
{
// 使用 EditorApplication.delayCall 避免 OnValidate 中的 SetVerticesDirty 警告
EditorApplication.delayCall += () =>
{
if (this != null && isActiveAndEnabled)
RebuildPrimitive();
};
}
}
#endif
/// <summary>
/// 重建图元
/// </summary>
public void RebuildPrimitive()
{
// 清除旧的图元
ClearAll();
var entry = new PrimitiveCacheEntry
{
type = primitiveType,
sizeMode = sizeMode,
center = centerOffset,
size = size,
angle = angle,
@params = new PrimitiveParams
{
axisScaleX = 1f,
axisScaleY = 1f,
sideCount = sideCount,
chamferSize = chamferSize,
chamferSegments = chamferSegments,
bgColor = bgColor,
centerColor = centerColor,
borderColor = borderColor,
borderThickness = borderThickness,
}
};
AddPrimitive(entry);
m_CurrentEntry = entry;
}
/// <summary>
/// 更新图元参数(不触发完全重建,只标记脏)
/// </summary>
public void UpdateParams(System.Action<PrimitiveCacheEntry> updater)
{
if (m_Primitives.Count == 0) return;
var entry = m_Primitives[0];
updater?.Invoke(entry);
m_Primitives[0] = entry;
SetPrimitiveDirty(0);
}
#region 便捷方法
/// <summary>
/// 快速设置为正多边形
/// </summary>
public void SetPolygon(int sides, float radius, Color? fill = null, Color? border = null)
{
primitiveType = PrimitiveType.Polygon;
sizeMode = SizeMode.InscribedCircle;
size = new Vector2(radius * 2f, radius * 2f);
sideCount = Mathf.Max(3, sides);
if (fill.HasValue) bgColor = fill.Value;
if (border.HasValue) borderColor = border.Value;
RebuildPrimitive();
}
/// <summary>
/// 快速设置为矩形
/// </summary>
public void SetRectangle(float width, float height, Color? fill = null, Color? border = null)
{
primitiveType = PrimitiveType.Rectangle;
sizeMode = SizeMode.InscribedEllipse;
size = new Vector2(width, height);
if (fill.HasValue) bgColor = fill.Value;
if (border.HasValue) borderColor = border.Value;
RebuildPrimitive();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7f57f5609d9f3a46aa9d50dc1a47904
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+478
View File
@@ -0,0 +1,478 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// UILineRenderer 的工具类,提供静态方法用于线条渲染相关的操作
/// </summary>
internal static class XericRendererUtils
{
#region 数学计算
/// <summary>
/// 计算线路的总长度
/// </summary>
public static float CalculateTotalLength(this Vector2[] points, bool cycleLoop = false)
{
if (points.Length < 2)
return 0f;
var totalLength = 0f;
for (int i = 0; i < points.Length - 1; i++)
totalLength += Vector2.Distance(points[i], points[i + 1]);
if (cycleLoop)
totalLength += Vector2.Distance(points[0], points[^1]);
return totalLength;
}
/// <summary>
/// 计算一个点指向另一个点的角度(以度为单位)
/// </summary>
public static float RotatePointTowards(Vector2 vertex, Vector2 target)
{
return (Mathf.Atan2(target.y - vertex.y, target.x - vertex.x) * Mathf.Rad2Deg);
}
/// <summary>
/// 如果需要居中显示,则偏移到组件中心
/// </summary>
public static Vector2 SwitchCenterOffset(bool center, Vector2 rectSize) =>
center ? Vector2.zero : -(rectSize / 2);
private static bool GetNormalizedVector(Vector2 startPoint, Vector2 endPoint,
out Vector2 vector, out float distance, out Vector2 normal, out Vector2 subTangent,
bool reverseVector = false)
{
vector = reverseVector ? startPoint - endPoint : endPoint - startPoint;
distance = vector.magnitude;
normal = Vector2.zero;
subTangent = Vector2.zero;
if (distance <= float.Epsilon)
return false;
normal = vector / distance;
subTangent = Vector2.Perpendicular(normal);
return true;
}
#endregion
#region uv计算
private static void GetCurrentLengthUVY(float startLength, float currentLength, float totalLength,
out float startUVY, out float endUVY)
{
startUVY = totalLength > 0 ? startLength / totalLength : 0f;
endUVY = totalLength > 0 ? (startLength + currentLength) / totalLength : 0f;
}
#endregion
#region 简易线路绘制 DSL5
public static List<UIVertex> GetSingleLineSegmentVerts_DSL5(List<UIVertex> vertices, Vector2 startPoint,
Vector2 endPoint,
float thickness, Color color, Vector2 normal, Vector2 subTangent, float startUVY, float endUVY,
Vector3 offset)
{
if (vertices == null)
throw new Exception("没有指定线段片段顶点容器列表");
vertices.Clear();
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
var width = thickness / 2;
vertex.position = (startPoint + subTangent * width);
vertex.position += offset;
vertex.uv0 = new Vector2(0f, startUVY);
vertices.Add(vertex);
vertex.position = (startPoint + subTangent * -width);
vertex.position += offset;
vertex.uv0 = new Vector2(1f, startUVY);
vertices.Add(vertex);
vertex.position = (endPoint + subTangent * width);
vertex.position += offset;
vertex.uv0 = new Vector2(0f, endUVY);
vertices.Add(vertex);
vertex.position = (endPoint + subTangent * -width);
vertex.position += offset;
vertex.uv0 = new Vector2(1f, endUVY);
vertices.Add(vertex);
vertex.position = endPoint;
vertex.position += offset;
vertex.uv0 = new Vector2(0.5f, endUVY);
vertices.Add(vertex);
return vertices;
}
public static void AddSingleLineSegmentVertsAndTriangle_DSL5(this VertexHelper vh, int segmentPartIndex,
IEnumerable<UIVertex> verts)
{
foreach (var vert in verts)
vh.AddVert(vert);
int index = segmentPartIndex * 5;
vh.AddTriangle(index, index + 1, index + 3);
vh.AddTriangle(index + 3, index + 2, index);
if (segmentPartIndex != 0)
{
vh.AddTriangle(index, index - 1, index - 3);
vh.AddTriangle(index + 1, index - 1, index - 2);
}
}
public static void PopulateLineByPointsArray_DSL5(this VertexHelper vh, Vector2[] points,
float thickness, bool cycleLoop, Color lineColor, Vector3 offset,
bool enableArrow,
Color arrowColor, Vector2 arrowSize, float pointProgress, float arrowPointProgress,
bool absProgressPoint = true, bool reverseDir = false)
{
if (points.Length < 2)
return;
var totalLength = points.CalculateTotalLength(cycleLoop);
var currentLength = 0f;
var lineVertices = ListPool<UIVertex>.Get();
var arrowVertices = ListPool<UIVertex>.Get();
var arrowindices = ListPool<int>.Get();
for (int i = 0; i < points.Length - (cycleLoop ? 0 : 1); i++)
{
var thisPoint = points[i];
var nextPoint = points[(i + 1) % points.Length];
if (!GetNormalizedVector(thisPoint, nextPoint,
out var vector, out var distance, out var normal, out var subTangent))
continue;
GetCurrentLengthUVY(currentLength, distance, totalLength, out var startUvy, out var endUvy);
vh.AddSingleLineSegmentVertsAndTriangle_DSL5(i,
GetSingleLineSegmentVerts_DSL5(lineVertices, thisPoint, nextPoint, thickness, lineColor, normal,
subTangent, startUvy, endUvy, offset));
if (enableArrow)
{
PopulateTriangleArrowByPointArray(arrowVertices, thisPoint, nextPoint, arrowColor, arrowSize.y,
arrowSize.x, pointProgress, arrowPointProgress, absProgressPoint, reverseDir);
arrowindices.AddFaceByConsecutiveVertices(arrowVertices, i * 3, 3, i * 3);
}
currentLength += distance;
}
if (enableArrow)
{
vh.AddUIVertexStream(arrowVertices, arrowindices);
}
ListPool<UIVertex>.Release(lineVertices);
ListPool<UIVertex>.Release(arrowVertices);
ListPool<int>.Release(arrowindices);
}
#endregion
#region 圆弧过度的线路绘制 CFAL
public static void AddSingleLineSegmentVertsAndTriangle_CFAL(this VertexHelper vh, int segmentPartIndex,
IEnumerable<UIVertex> verts)
{
foreach (var vert in verts)
vh.AddVert(vert);
int index = segmentPartIndex * 5;
vh.AddTriangle(index, index + 1, index + 3);
vh.AddTriangle(index + 3, index + 2, index);
if (segmentPartIndex != 0)
{
vh.AddTriangle(index, index - 1, index - 3);
vh.AddTriangle(index + 1, index - 1, index - 2);
}
}
public static List<UIVertex> GetSingleLineSegmentVerts_CFAL(List<UIVertex> vertices, Vector2 startPoint,
Vector2 endPoint, Vector2 nextPoint, Vector2 prevPoint,
float thickness, Color color, float startLength, out float distance, float totalLength, Vector3 offset,
int chamferSegments = 0, float innerRadius = 0f)
{
if (vertices == null)
throw new Exception("没有指定线段片段顶点容器列表");
vertices.Clear();
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
if (!GetNormalizedVector(startPoint, endPoint,
out var vector, out distance, out var normal, out var subTangent))
return vertices;
var width = thickness / 2;
GetCurrentLengthUVY(startLength, distance, totalLength, out var startUVY, out var endUVY);
float startAngle = 0f;
float endAngle = 0f;
if (prevPoint != startPoint)
{
var prevVector = startPoint - prevPoint;
var prevNormal = prevVector.normalized;
startAngle = Vector2.Angle(normal, -prevNormal);
}
if (nextPoint != endPoint)
{
var nextVector = nextPoint - endPoint;
var nextNormal = nextVector.normalized;
endAngle = Vector2.Angle(-normal, nextNormal);
}
float startOffset = 0f;
float endOffset = 0f;
if (Mathf.Abs(startAngle) > 0.1f)
{
if (innerRadius > 0f)
{
startOffset = (width + innerRadius) / Mathf.Sin(startAngle * Mathf.Deg2Rad / 2) - innerRadius;
}
else
{
startOffset = width / Mathf.Sin(startAngle * Mathf.Deg2Rad / 2);
}
}
if (Mathf.Abs(endAngle) > 0.1f)
{
if (innerRadius > 0f)
{
endOffset = (width + innerRadius) / Mathf.Sin(endAngle * Mathf.Deg2Rad / 2) - innerRadius;
}
else
{
endOffset = width / Mathf.Sin(endAngle * Mathf.Deg2Rad / 2);
}
}
vertex.position = (startPoint + subTangent * width + normal * startOffset);
vertex.position += offset;
vertex.uv0 = new Vector2(0f, startUVY);
vertices.Add(vertex);
vertex.position = (startPoint + subTangent * -width + normal * startOffset);
vertex.position += offset;
vertex.uv0 = new Vector2(1f, startUVY);
vertices.Add(vertex);
vertex.position = (endPoint + subTangent * width + normal * -endOffset);
vertex.position += offset;
vertex.uv0 = new Vector2(0f, endUVY);
vertices.Add(vertex);
vertex.position = (endPoint + subTangent * -width + normal * -endOffset);
vertex.position += offset;
vertex.uv0 = new Vector2(1f, endUVY);
vertices.Add(vertex);
vertex.position = (endPoint + normal * -endOffset);
vertex.position += offset;
vertex.uv0 = new Vector2(0.5f, endUVY);
vertices.Add(vertex);
return vertices;
}
public static void PopulateLineByPointsArray_CFAL(this VertexHelper vh, Vector2[] points, float thickness,
bool cycleLoop, Color color, Vector3 offset,
int chamferSegments = 0, float innerRadius = 0f)
{
if (points.Length < 2)
return;
var totalLength = points.CalculateTotalLength(cycleLoop);
var currentLength = 0f;
var vertices = ListPool<UIVertex>.Get();
for (int i = 0; i < points.Length - (cycleLoop ? 0 : 1); i++)
{
var startPoint = points[i];
var endPoint = points[(i + 1) % points.Length];
var prevPoint = i > 0 ? points[i - 1] : cycleLoop ? points[^1] : startPoint;
var nextPoint = points[(i + 2) % points.Length];
vh.AddSingleLineSegmentVertsAndTriangle_CFAL(i,
GetSingleLineSegmentVerts_CFAL(vertices, startPoint, endPoint, nextPoint, prevPoint,
thickness, color, currentLength, out var distance, totalLength, offset,
chamferSegments, innerRadius));
currentLength += distance;
}
ListPool<UIVertex>.Release(vertices);
}
public static void CreateLineSegment_CFAL(this VertexHelper vh, Vector2 startPoint, Vector2 endPoint,
float angle0, float angle3, float thickness, bool center, Vector2 rectSize, Color color, float startLength,
float totalLength)
{
Vector2 offset = center ? (rectSize / 2) : Vector2.zero;
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
if (!GetNormalizedVector(startPoint, endPoint,
out var vector, out var distance, out var normal, out var subTangent))
return;
var width = thickness / 2;
var pointOffset1 = Mathf.Abs(angle0) <= 0.1f
? 0
: width / Mathf.Sin(angle0 * Mathf.Deg2Rad) + (angle0 > 90 ? width : 0);
var pointOffset2 = Mathf.Abs(angle3) <= 0.1f
? 0
: width / Mathf.Sin(angle3 * Mathf.Deg2Rad) + (angle3 > 90 ? width : 0);
float startUVY = totalLength > 0 ? startLength / totalLength : 0f;
float endUVY = totalLength > 0 ? (startLength + distance) / totalLength : 0f;
vertex.position = (Vector3)(startPoint + subTangent * width + normal * pointOffset1) - (Vector3)offset;
vertex.uv0 = new Vector2(0f, startUVY);
vh.AddVert(vertex);
vertex.position = (Vector3)(startPoint + subTangent * -width + normal * pointOffset1) - (Vector3)offset;
vertex.uv0 = new Vector2(1f, startUVY);
vh.AddVert(vertex);
vertex.position = (Vector3)(endPoint + subTangent * width + normal * -pointOffset2) - (Vector3)offset;
vertex.uv0 = new Vector2(0f, endUVY);
vh.AddVert(vertex);
vertex.position = (Vector3)(endPoint + subTangent * -width + normal * -pointOffset2) - (Vector3)offset;
vertex.uv0 = new Vector2(1f, endUVY);
vh.AddVert(vertex);
vertex.position = (Vector3)(endPoint + normal * -pointOffset2) - (Vector3)offset;
vertex.uv0 = new Vector2(0.5f, endUVY);
vh.AddVert(vertex);
}
#endregion
#region 箭头绘制工具
public static List<UIVertex> PopulateTriangleArrowByPointArray(List<UIVertex> vertices, Vector2 start,
Vector2 end, Color color, float sizeL, float sizeW, float pointProgress, float arrowPointProgress,
bool absProgressPoint = true, bool reverseDir = false)
{
GetNormalizedVector(start, end, out var vector, out var length, out var normal, out var tangent,
reverseDir);
var progressPosition = absProgressPoint
? pointProgress * normal
: pointProgress * vector;
progressPosition += reverseDir ? end : start;
progressPosition += arrowPointProgress * sizeL * normal;
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
vertex.position = progressPosition;
vertices.Add(vertex);
vertex.position = progressPosition + -sizeL * normal + sizeW * 0.5f * tangent;
vertices.Add(vertex);
vertex.position += (Vector3)(-sizeW * tangent);
vertices.Add(vertex);
return vertices;
}
#endregion
#region 顶点工具集
/// <summary>
/// 获取点在矩形中的UV坐标
/// </summary>
public static void GetUVAtRect(this Vector2 pos, Rect rect, out float u, out float v)
{
u = (pos.x - rect.xMin) / rect.width;
v = (pos.y - rect.yMin) / rect.height;
}
public static void Get(this IList<UIVertex> vertexList, Vector4 uv2)
{
}
#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);
/// <summary>
/// 添加一个顶点信息
/// </summary>
public static void AddVert(this ICollection<UIVertex> 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
});
}
/// <summary>
/// 添加一个三角面索引
/// </summary>
public static void AddTriangle(this ICollection<int> indices, int index0, int index1, int index2)
{
indices.Add(index0);
indices.Add(index1);
indices.Add(index2);
}
/// <summary>
/// 向顶点管理器添加顶点列表中的顶点构成的面
/// </summary>
public static void AddFaceByConsecutiveVertices(this ICollection<int> indices, IList<UIVertex> vertexList,
int startVertexIndex, int faceVertexRange, int centerVertexIndex)
{
if (faceVertexRange < 3)
throw new IndexOutOfRangeException("无法创建小于3个点的面");
var length = startVertexIndex + faceVertexRange - 1;
for (int i = startVertexIndex + 1; i < length; i++)
{
if (i < 0 || i > vertexList.Count)
throw new IndexOutOfRangeException(
$"在向顶点集添加顶点时,从{startVertexIndex}开始的{faceVertexRange}个顶点(以及{centerVertexIndex})可能超出{vertexList.Count}的范围(预定范围{length}),导致构建超出预期");
var next = startVertexIndex + 1;
if (next >= vertexList.Count)
return;
indices.AddTriangle(centerVertexIndex, startVertexIndex, startVertexIndex + 1);
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a46796ddebc719488396e15845faa2e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+7 -7
View File
@@ -302,16 +302,16 @@ public static class MacroDXXL
#region 标注 / 标签 / 文本
/// <summary>在 3D 位置绘制文本标签</summary>
public static void Label(Vector3 position, string text, Color? color = null, float size = 0.1f)
/// <summary>在 3D 位置绘制文本标签(rotation 控制朝向,默认 Quaternion.identity 即 xy 平面正向)</summary>
public static void Label(Vector3 position, string text, Color? color = null, float size = 0.1f, Quaternion? rotation = null)
{
DrawText.Write(text, position, color ?? Color.white, size, default(Vector3), default(Vector3));
}
DrawText.Write(text, position, color ?? Color.white, size, rotation ?? Quaternion.identity);
}
/// <summary>在 3D 位置绘制带框文本标签</summary>
public static void LabelFramed(Vector3 position, string text, Color? color = null, float size = 0.1f)
/// <summary>在 3D 位置绘制带框文本标签(rotation 控制朝向,默认 Quaternion.identity 即 xy 平面正向)</summary>
public static void LabelFramed(Vector3 position, string text, Color? color = null, float size = 0.1f, Quaternion? rotation = null)
{
DrawText.WriteFramed(text, position, color ?? Color.white, size, default(Vector3), default(Vector3));
DrawText.WriteFramed(text, position, color ?? Color.white, size, rotation ?? Quaternion.identity);
}
/// <summary>给 GameObject 绘制屏幕空间标签(始终面向相机)</summary>
-1
View File
@@ -30,7 +30,6 @@ namespace Deconstruction.Runtime
{
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTrans);
}
private static HashSet<RectTransform> _rebuildSet = new HashSet<RectTransform>();
/// <summary>
Binary file not shown.
-33
View File
@@ -1,33 +0,0 @@
fileFormatVersion: 2
guid: 39959342f844a444e83cf0f85dcdda9f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7758a0679c9038144a4b0d007d44618f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f8777f3ce94f12747a9c712730114c5b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-16
View File
@@ -1,16 +0,0 @@
This package contains third-party software components governed by the license(s) indicated below:
---------
Component Name: [provide component name]
License Type: [Provide license type, i.e. "MIT", "Apache 2.0"]
[Provide License Details]
---------
Component Name: [provide component name]
License Type: [Provide license type, i.e. "MIT", "Apache 2.0"]
[Provide License Details]
-7
View File
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 63d179caae5c9e049a9a6ed376a54f9c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+1 -1
View File
File diff suppressed because one or more lines are too long