ComboInput/Source/ComboInputEditor/Private/Layout/ComboActionForceDirectedSolveLayoutStrategy.cpp
2023-09-29 15:11:48 -04:00

167 lines
4.7 KiB
C++

// 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<float>::Max();
}
void UComboActionForceDirectedSolveLayoutStrategy::Layout(UEdGraph *InEdGraph)
{
this->EdGraph = Cast<UEdComboActionGraph>(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<UEdGraphNode*, FVector2D> NodeToDisplacement;
for (const TObjectPtr<UEdGraphNode> &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<UComboActionGraphNode*> CurrLevelNodes = { RootNode };
TArray<UComboActionGraphNode*> 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;
}