// All rights reserved Dominik Pavlicek 2023 #include "Layout/ComboActionForceDirectedSolveLayoutStrategy.h" #include "ComboActionGraph.h" #include "Ed/EdComboActionGraph.h" #include "Ed/EdComboActionGraphNode.h" #include "Nodes/ComboActionGraphNode.h" #include "Settings/ComboActionGraphEditorSettings.h" UComboActionForceDirectedSolveLayoutStrategy::UComboActionForceDirectedSolveLayoutStrategy() { this->bRandomInit = false; this->CoolDownRate = 10; this->InitTemperature = 10.f; } static inline float CoolDown(float Temp, float CoolDownRate) { if (Temp < .01) return .01; return Temp - (Temp / CoolDownRate); } static inline float GetAttractForce(float X, float K) { return (X * X) / K; } static inline float GetRepulseForce(float X, float k) { return X != 0 ? k * k / X : TNumericLimits::Max(); } void UComboActionForceDirectedSolveLayoutStrategy::Layout(UEdGraph *InEdGraph) { this->EdGraph = Cast(InEdGraph); check(this->EdGraph != nullptr); this->EdGraph->RebuildComboActionGraph(); this->Graph = this->EdGraph->GetComboActionGraph(); check(this->Graph != nullptr); if (this->Settings != nullptr) { this->OptimalDistance = Settings->GetOptimalDistance(); this->MaxIteration = Settings->GetMaxIteration(); this->bRandomInit = Settings->IsRandomInit(); } FBox2D PreTreeBound(ForceInitToZero); for (int32 i = 0; i < Graph->RootNodes.Num(); ++i) { PreTreeBound = this->LayoutOneTree(Graph->RootNodes[i], PreTreeBound); } } FBox2D UComboActionForceDirectedSolveLayoutStrategy::LayoutOneTree(UComboActionGraphNode *RootNode, const FBox2D &PreTreeBound) { float Temp = this->InitTemperature; FBox2D TreeBound = GetActualBounds(RootNode); TreeBound.Min.X += PreTreeBound.Max.X + this->OptimalDistance; TreeBound.Max.X += PreTreeBound.Max.X + this->OptimalDistance; if (this->bRandomInit) { this->RandomLayoutOneTree(RootNode, TreeBound); } float RepulseForce, AttractForce, Distance; FVector2D Diff; TMap NodeToDisplacement; for (const TObjectPtr &EdNode : this->EdGraph->Nodes) { NodeToDisplacement.Add(EdNode, FVector2D(0.0f, 0.0f)); } for (int32 IterationNum = 0; IterationNum < this->MaxIteration; IterationNum++) { // Calculate the repulsive forces. for (int32 i = 0; i < this->EdGraph->Nodes.Num(); i++) { for (int32 j = 0; j < this->EdGraph->Nodes.Num(); j++) { if (i == j) { continue; } Diff.X = this->EdGraph->Nodes[i]->NodePosX - this->EdGraph->Nodes[j]->NodePosX; Diff.Y = this->EdGraph->Nodes[i]->NodePosY - this->EdGraph->Nodes[j]->NodePosY; Distance = Diff.Size(); Diff.Normalize(); RepulseForce = Distance > 2 * this->OptimalDistance ? 0 : GetRepulseForce(Distance, this->OptimalDistance); NodeToDisplacement[this->EdGraph->Nodes[i]] += Diff * RepulseForce; } } // Calculate the attractive forces. int Level = 0; TArray CurrLevelNodes = { RootNode }; TArray NextLevelNodes; while (CurrLevelNodes.Num() != 0) { for (int32 i = 0; i < CurrLevelNodes.Num(); i++) { UComboActionGraphNode *Node = CurrLevelNodes[i]; check(Node != nullptr); UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[Node]; for (int32 j = 0; j < Node->ChildrenNodes.Num(); j++) { NextLevelNodes.Add(Node->ChildrenNodes[j]); UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Node->ChildrenNodes[j]]; Diff.X = EdNode_ChildNode->NodePosX - EdNode_ParentNode->NodePosY; Diff.Y = EdNode_ChildNode->NodePosY - EdNode_ParentNode->NodePosY; Distance = Diff.Size(); Diff.Normalize(); AttractForce = GetAttractForce(Distance, this->OptimalDistance); NodeToDisplacement[EdNode_ParentNode] += Distance * Diff; NodeToDisplacement[EdNode_ChildNode] -= Distance * Diff; } } CurrLevelNodes = NextLevelNodes; NextLevelNodes.Reset(); Level++; } for (UEdGraphNode *EdNode : this->EdGraph->Nodes) { Distance = NodeToDisplacement[EdNode].Size(); NodeToDisplacement[EdNode].Normalize(); float Minimum = Distance < Temp ? Distance : Temp; EdNode->NodePosX += NodeToDisplacement[EdNode].X * Minimum; EdNode->NodePosY += NodeToDisplacement[EdNode].Y * Minimum; } Temp = CoolDown(Temp, this->CoolDownRate); } FBox2D ActualBound = GetActualBounds(RootNode); FVector2D Center = ActualBound.GetCenter(); FVector2D TreeCenter = TreeBound.GetCenter(); FVector2D Scale = (TreeBound.Max - TreeBound.Min) / (ActualBound.Max - ActualBound.Min); for (UEdGraphNode *EdNode : EdGraph->Nodes) { EdNode->NodePosX = TreeCenter.X + Scale.X * (EdNode->NodePosX - Center.X); EdNode->NodePosY = TreeCenter.Y + Scale.Y * (EdNode->NodePosY - Center.Y); } return TreeBound; }