mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-02 15:50:51 +08:00
124 lines
5.2 KiB
C#
124 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
|
|
|
namespace AIStudio.Wpf.DiagramDesigner.Layout
|
|
{
|
|
internal class Engine
|
|
{
|
|
internal void ApplyRandomShift(DiagramViewModel network)
|
|
{
|
|
Random random = new Random();
|
|
foreach (var node in network.Items.OfType<DesignerItemViewModelBase>())
|
|
{
|
|
node.Position = node.Position + new VectorBase(random.NextDouble(), random.NextDouble());
|
|
}
|
|
}
|
|
|
|
internal void Update(int deltaTMillis, IState state, Configuration config)
|
|
{
|
|
// Calculate forces
|
|
int nodeCount = config.Network.Items.Count;
|
|
IList<(DesignerItemViewModelBase, VectorBase)> nodeForces = new List<(DesignerItemViewModelBase, VectorBase)>(nodeCount);
|
|
|
|
foreach (var node in config.Network.Items.OfType<DesignerItemViewModelBase>())
|
|
{
|
|
if (!config.IsFixedNode(node))
|
|
{
|
|
nodeForces.Add((node, CalculateNodeForce(node, state, config)));
|
|
}
|
|
}
|
|
|
|
// Apply forces
|
|
foreach (var (node, force) in nodeForces)
|
|
{
|
|
VectorBase speed = state.GetNodeSpeed(node);
|
|
VectorBase pos = state.GetNodePosition(node);
|
|
double deltaT = deltaTMillis / 1000.0;
|
|
state.SetNodePosition(node, pos + ((speed * deltaT) + (force * deltaT * deltaT / 2)));
|
|
state.SetNodeSpeed(node, speed + ((force / config.NodeMass(node)) * deltaT));
|
|
}
|
|
}
|
|
|
|
private VectorBase CalculateNodeForce(DesignerItemViewModelBase node, IState state, Configuration config)
|
|
{
|
|
VectorBase force = new VectorBase();
|
|
|
|
// Calculate total force on node from endpoints
|
|
if (node.InputConnectors.Count > 0 || node.OutputConnectors.Count > 0)
|
|
{
|
|
force += node.InputConnectors.Concat(node.OutputConnectors)
|
|
.Select(e => CalculateEndpointForce(e, state, config))
|
|
.Aggregate((v1, v2) => v1 + v2);
|
|
}
|
|
|
|
// Apply node repulsion force so nodes don't overlap
|
|
var nodeCenter = state.GetNodePosition(node) + (new VectorBase(node.Size.Width, node.Size.Height) / 2.0);
|
|
foreach (var otherNode in config.Network.Items.OfType<DesignerItemViewModelBase>())
|
|
{
|
|
if (node == otherNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var otherNodeCenter = state.GetNodePosition(otherNode) + (new VectorBase(otherNode.Size.Width, otherNode.Size.Height) / 2.0);
|
|
var thisToOther = otherNodeCenter - nodeCenter;
|
|
var dist = thisToOther.Length;
|
|
thisToOther.Normalize();
|
|
|
|
var repulsionX = thisToOther.X * (-1 * ((node.Size.Width + otherNode.Size.Width) / 2) / dist);
|
|
var repulsionY = thisToOther.Y * (-1 * ((node.Size.Height + otherNode.Size.Height) / 2) / dist);
|
|
force += new VectorBase(repulsionX, repulsionY) * config.NodeRepulsionForce;
|
|
}
|
|
|
|
// Apply friction to make the movement converge to a stable state.
|
|
float gravity = 9.8f;
|
|
float normalForce = gravity * config.NodeMass(node);
|
|
float kineticFriction = normalForce * config.FrictionCoefficient(node);
|
|
VectorBase frictionVector = new VectorBase();
|
|
var nodeSpeed = state.GetNodeSpeed(node);
|
|
if (nodeSpeed.Length > 0)
|
|
{
|
|
frictionVector = new VectorBase(nodeSpeed.X, nodeSpeed.Y);
|
|
frictionVector.Normalize();
|
|
frictionVector *= -1.0 * kineticFriction;
|
|
}
|
|
force += frictionVector;
|
|
|
|
return force;
|
|
}
|
|
|
|
private VectorBase CalculateEndpointForce(ConnectorInfoBase endpoint, IState state, Configuration config)
|
|
{
|
|
var pos = state.GetEndpointPosition(endpoint);
|
|
|
|
VectorBase force = new VectorBase();
|
|
|
|
foreach (var conn in config.Network.Items.OfType<ConnectionViewModel>().Where(p => p.SinkConnectorInfo == endpoint || p.SourceConnectorInfo == endpoint).ToList())
|
|
{
|
|
var otherSide = conn.SourceConnectorInfo == endpoint ? conn.SinkConnectorInfo : conn.SourceConnectorInfo;
|
|
var otherSidePos = state.GetEndpointPosition(otherSide);
|
|
var dist = (otherSidePos - pos).Length;
|
|
var angle = Math.Acos((otherSidePos.X - pos.X) / dist);
|
|
if (otherSidePos.Y < pos.Y)
|
|
{
|
|
angle *= -1.0;
|
|
}
|
|
|
|
// Put a spring between connected endpoints.
|
|
var hookForce = (dist - config.EquilibriumDistance(conn)) * config.SpringConstant(conn);
|
|
force += new VectorBase(Math.Cos(angle), Math.Sin(angle)) * hookForce;
|
|
|
|
// Try to 'straighten' out the graph horizontally.
|
|
var isLeftSide = endpoint.Orientation == ConnectorOrientation.Left;
|
|
var rowForce = (isLeftSide ? 1 : -1) * config.RowForce(conn);
|
|
force.X += rowForce;
|
|
}
|
|
|
|
return force;
|
|
}
|
|
}
|
|
}
|