Files
XericLibrary-WebGL-Publish/Runtime/Blueprint/Render/QuickUGUIGraphWireRenderTool.cs
T
2026-07-13 11:44:25 +08:00

182 lines
5.3 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 快速 UGUI 图论连线渲染工具。
/// 通过共享的 LayerContainerManager 使用与节点相同的层容器,
/// 连线绘制在两端节点所在层的较低层中(不遮挡节点)。
/// 可调参数由 QuickGraphWireRenderConfig 配置资产提供。
/// </summary>
[BlueprintTool(phase: ToolPhase.Render, order: 110)]
[BlueprintTheme("QuickGraph")]
public class QuickUGUIGraphWireRenderTool : BlueprintTool
{
private QuickGraphWireRenderConfig _defaultConfig;
private QuickGraphWireRenderConfig Config
{
get
{
if (ToolConfig is QuickGraphWireRenderConfig external)
return external;
if (_defaultConfig == null)
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphWireRenderConfig>();
return _defaultConfig;
}
}
private Transform _renderRoot;
private LayerContainerManager _layerMgr;
public override void OnRender()
{
if (Graph == null) return;
EnsureRenderRoot();
if (_renderRoot == null) return;
var buckets = Graph.RenderLayers;
if (buckets == null) return;
_layerMgr = LayerContainerManager.GetForGraph(Graph, _renderRoot);
var cfg = Config;
int bucketCount = buckets.BucketCount;
// 视口裁剪
var viewport = Graph.Canvas.GetViewportRect();
float margin = cfg.WireWidth * 20f;
viewport.xMin -= margin;
viewport.xMax += margin;
viewport.yMin -= margin;
viewport.yMax += margin;
var layerWires = new List<BlueprintWire>[bucketCount];
for (int i = 0; i < bucketCount; i++)
layerWires[i] = new List<BlueprintWire>();
foreach (var wire in Graph.Wires)
{
int srcLayer = wire.SourcePort?.OwnerNode?.RenderLayer ?? 0;
int tgtLayer = wire.TargetPort?.OwnerNode?.RenderLayer ?? 0;
int wireLayer = Mathf.Min(srcLayer, tgtLayer);
if (wireLayer < 0 || wireLayer >= bucketCount) wireLayer = 0;
// 两端都不在视口内则跳过
if (wire.SourcePort?.OwnerNode != null && wire.TargetPort?.OwnerNode != null)
{
if (!viewport.Contains(wire.SourcePort.OwnerNode.Position) &&
!viewport.Contains(wire.TargetPort.OwnerNode.Position))
continue;
}
layerWires[wireLayer].Add(wire);
}
for (int i = 0; i < bucketCount; i++)
{
if (layerWires[i].Count > 0)
{
var entry = _layerMgr.GetOrCreateLayer(i);
RenderLayerWires(entry, layerWires[i], cfg, Graph.Canvas);
}
else
{
var entry = _layerMgr.TryGetLayer(i);
entry?.CurveRenderer?.ClearAll();
}
}
}
private static void RenderLayerWires(LayerContainerEntry entry, List<BlueprintWire> wires,
QuickGraphWireRenderConfig cfg, IBlueprintCanvas canvas)
{
entry.CurveRenderer.ClearAll();
float zoom = canvas.ZoomLevel;
foreach (var wire in wires)
{
if (wire.SourcePort == null || wire.TargetPort == null) continue;
if (wire.SourcePort.OwnerNode == null || wire.TargetPort.OwnerNode == null) continue;
Vector2 startPos = canvas.CanvasToLocal(wire.SourcePort.GetWorldPosition());
Vector2 endPos = canvas.CanvasToLocal(wire.TargetPort.GetWorldPosition());
// 手柄方向 = 端口在节点上的朝向
// 端口在节点右侧 (RelativePosition.x > 0) → 手柄朝右 (+X)
// 端口在节点左侧 (RelativePosition.x < 0) → 手柄朝左 (-X)
float srcDirX = Mathf.Sign(wire.SourcePort.RelativePosition.x);
float tgtDirX = Mathf.Sign(wire.TargetPort.RelativePosition.x);
// 同一侧(如两个端口都在右侧)则方向取反
if (srcDirX == tgtDirX) srcDirX *= -1f;
// 手柄距离 = 配置值 × zoom(画布单位 → local 坐标,与 CanvasToLocal 一致)
Vector2 handle1 = startPos + new Vector2(srcDirX * cfg.SourceHandleLength * zoom, 0f);
Vector2 handle2 = endPos + new Vector2(tgtDirX * cfg.TargetHandleLength * zoom, 0f);
float ww = cfg.WireWidth * zoom;
var ctrlPts = new Vector3[]
{
new Vector3(startPos.x, startPos.y, ww),
new Vector3(handle1.x, handle1.y, ww),
new Vector3(handle2.x, handle2.y, ww),
new Vector3(endPos.x, endPos.y, ww),
};
Color32 srcColor = wire.SourcePort.OwnerNode.NodeColor;
Color32 tgtColor = wire.TargetPort.OwnerNode.NodeColor;
var arrow = new ArrowHeadData
{
shape = cfg.ArrowShape,
reversed = cfg.ArrowReversed,
progress = cfg.ArrowProgress,
width = cfg.ArrowWidth * zoom,
height = cfg.ArrowHeight * zoom,
depthCompensation = cfg.ArrowDepthCompensation,
color = tgtColor,
};
int count = ctrlPts.Length;
var ctrlPts2 = new Vector2[count];
var widths = new float[count];
for (int i = 0; i < count; i++)
{
ctrlPts2[i] = ctrlPts[i];
widths[i] = ctrlPts[i].z;
}
var curveEntry = new CurveCacheEntry
{
startColor = srcColor,
endColor = tgtColor,
tessellationSegments = cfg.TessellationSegments,
};
entry.CurveRenderer.AddCurve(curveEntry, ctrlPts2, widths, new[] { arrow });
}
if (wires.Count > 0)
entry.CurveRenderer.RebuildAll();
}
private void EnsureRenderRoot()
{
if (_renderRoot != null) return;
if (Graph.Canvas == null) return;
_renderRoot = Graph.Canvas.GetRootTransform();
}
public override void OnDestroy()
{
_layerMgr = null;
_renderRoot = null;
}
}
}