How to add flow effect to Edges in GraphView?

Hello, I’m trying to add some flow effect to Edges in a GraphView, and I meet some problems.

I found that there are a ‘lineMat’ property in EdgeControl.cs, but it was never used, and I can’t create my own mesh with material, because there are too many internal APIs used in EdgeControl.

void DrawEdge(MeshGenerationContext mgc)
{
    // ...
    var md = mgc.Allocate((int)wantedLength, (int)indexCount, null, null, MeshGenerationContext.MeshFlags.UVisDisplacement);
    // ...
}

Then I try to add a Image element to EdgeControl and change its position to simulate flow effect. But when I draw the content of GraphView, the Image element will wander off. Any idea to fix this problem? Thanks!

8382438--1105482--upload_2022-8-23_13-22-33.gif

And here is my code:

using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace _TEST_.UIE
{
    public class MyEdgeControl : EdgeControl
    {
        private readonly Image _flowImg;
        private readonly float _flowSpeed;
        private int _flowStartIndex;
        private double _flowPhaseStartTime;
        private double _flowPhaseDuration;

        public MyEdgeControl(float flowSpeed = 150)
        {
            _flowImg = new Image()
            {
                style =
                {
                    position = Position.Absolute,
                    width = new Length(8, LengthUnit.Pixel),
                    height = new Length(8, LengthUnit.Pixel),
                    backgroundColor = Color.green,
                }
            };
            Add(_flowImg);

            _flowSpeed = flowSpeed;
        }

        // private void OnGeometryChanged(GeometryChangedEvent evt)
        // {
        //     ComputeControlPoints(); // still wrong
        // }

        public override void UpdateLayout()
        {
            base.UpdateLayout();

            var progress = (EditorApplication.timeSinceStartup - _flowPhaseStartTime) / _flowPhaseDuration;
            var flowPos = Vector2.Lerp(controlPoints[_flowStartIndex],
                controlPoints[_flowStartIndex + 1], (float)progress);
            _flowImg.transform.position = this.WorldToLocal(flowPos);

            if (progress >= 0.99999f)
            {
                _flowStartIndex++;
                if (_flowStartIndex >= controlPoints.Length - 1)
                {
                    _flowStartIndex = 0;
                }

                _flowPhaseStartTime = EditorApplication.timeSinceStartup;
                _flowPhaseDuration =
                    Vector2.Distance(controlPoints[_flowStartIndex], controlPoints[_flowStartIndex + 1]) /
                    _flowSpeed;
            }
        }
    }
}

I know the reason, the code should be like this:

var flowStartPoint = parent.ChangeCoordinatesTo(this, controlPoints[_flowStartIndex]);
var flowEndPoint = parent.ChangeCoordinatesTo(this, controlPoints[_flowStartIndex + 1]);
var flowPos = Vector2.Lerp(flowStartPoint, flowEndPoint, (float)progress);

Here is my final solution:

using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

public class FlowingEdge : Edge
{
    public bool enableFlow
    {
        get => _isEnableFlow;
        set
        {
            if (_isEnableFlow == value)
            {
                return;
            }

            _isEnableFlow = value;
            if (_isEnableFlow)
            {
                Add(_flowImg);
            }
            else
            {
                Remove(_flowImg);
            }
        }
    }

    private bool _isEnableFlow;

    public float flowSize
    {
        get => _flowSize;
        set
        {
            _flowSize = value;
            _flowImg.style.width = new Length(_flowSize, LengthUnit.Pixel);
            _flowImg.style.height = new Length(_flowSize, LengthUnit.Pixel);
        }
    }

    private float _flowSize = 6f;

    public float flowSpeed { get; set; } = 150f;

    private readonly Image _flowImg;


    public FlowingEdge()
    {
        _flowImg = new Image
        {
            name = "flow-image",
            style =
            {
                width = new Length(flowSize, LengthUnit.Pixel),
                height = new Length(flowSize, LengthUnit.Pixel),
                borderTopLeftRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderTopRightRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderBottomLeftRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderBottomRightRadius = new Length(flowSize / 2, LengthUnit.Pixel),
            }
        };
        // Add(_flowImg);

        edgeControl.RegisterCallback<GeometryChangedEvent>(OnEdgeControlGeometryChanged);
    }

    public override bool UpdateEdgeControl()
    {
        if (!base.UpdateEdgeControl())
        {
            return false;
        }

        UpdateFlow();
        return true;
    }


    #region Flow

    private float _totalEdgeLength;

    private float _passedEdgeLength;

    private int _flowPhaseIndex;

    private double _flowPhaseStartTime;

    private double _flowPhaseDuration;

    private float _currentPhaseLength;


    public void UpdateFlow()
    {
        if (!enableFlow)
        {
            return;
        }

        // Position
        var posProgress = (EditorApplication.timeSinceStartup - _flowPhaseStartTime) / _flowPhaseDuration;
        var flowStartPoint = edgeControl.controlPoints[_flowPhaseIndex];
        var flowEndPoint = edgeControl.controlPoints[_flowPhaseIndex + 1];
        var flowPos = Vector2.Lerp(flowStartPoint, flowEndPoint, (float)posProgress);
        _flowImg.transform.position = flowPos - Vector2.one * flowSize / 2;

        // Color
        var colorProgress = (_passedEdgeLength + _currentPhaseLength * posProgress) / _totalEdgeLength;
        var startColor = edgeControl.outputColor;
        var endColor = edgeControl.inputColor;
        var flowColor = Color.Lerp(startColor, endColor, (float)colorProgress);
        _flowImg.style.backgroundColor = flowColor;

        // Enter next phase
        if (posProgress >= 0.99999f)
        {
            _passedEdgeLength += _currentPhaseLength;

            _flowPhaseIndex++;
            if (_flowPhaseIndex >= edgeControl.controlPoints.Length - 1)
            {
                // Restart flow
                _flowPhaseIndex = 0;
                _passedEdgeLength = 0;
            }

            _flowPhaseStartTime = EditorApplication.timeSinceStartup;
            _currentPhaseLength = Vector2.Distance(edgeControl.controlPoints[_flowPhaseIndex],
                edgeControl.controlPoints[_flowPhaseIndex + 1]);
            _flowPhaseDuration = _currentPhaseLength / flowSpeed;
        }
    }

    private void OnEdgeControlGeometryChanged(GeometryChangedEvent evt)
    {
        // Restart flow
        _flowPhaseIndex = 0;
        _passedEdgeLength = 0;
        _flowPhaseStartTime = EditorApplication.timeSinceStartup;
        _currentPhaseLength = Vector2.Distance(edgeControl.controlPoints[_flowPhaseIndex],
            edgeControl.controlPoints[_flowPhaseIndex + 1]);
        _flowPhaseDuration = _currentPhaseLength / flowSpeed;

        // Calculate edge path length
        _totalEdgeLength = 0;
        for (int i = 0; i < edgeControl.controlPoints.Length - 1; i++)
        {
            var p = edgeControl.controlPoints[i];
            var pNext = edgeControl.controlPoints[i + 1];
            var phaseLen = Vector2.Distance(p, pNext);
            _totalEdgeLength += phaseLen;
        }
    }

    #endregion
}

8382939--1105521--upload_2022-8-23_16-2-52.gif

2 Likes

@SolarianZ Can you send the code snippet on how you used these in the graphview . how do you create the Flowingedge object rather than the Edge object the graph view gives when a connection is made between two ports ??

@Joyal11 Hi, it’s been too long, and I don’t quite remember. It seems like you need to override the InstantiatePort method in Node, like this:

public override Port InstantiatePort(Orientation orientation, Direction direction, Port.Capacity capacity, Type type)
{
    return Port.Create<FlowingEdge>(orientation, direction, capacity, type);
}

Also, if there’s already connected ports, use var edge = parentNodeView.Output.ConnectTo<FlowingEdge>(childNodeView.Input);