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()) { 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()) { 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()) { 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().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; } } }