CelticCraft/Plugins/VoxelFree/Source/VoxelGraphEditor/Private/VoxelGraphSchema.cpp

1150 lines
No EOL
37 KiB
C++

// Copyright 2020 Phyronnaz
#include "VoxelGraphSchema.h"
#include "VoxelEdGraph.h"
#include "VoxelGraphEditorUtilities.h"
#include "VoxelGraphEditorCommands.h"
#include "IVoxelGraphEditorToolkit.h"
#include "VoxelAssets/VoxelHeightmapAsset.h"
#include "VoxelAssets/VoxelDataAsset.h"
#include "VoxelNode.h"
#include "VoxelNodes/VoxelGraphMacro.h"
#include "VoxelNodes/VoxelLocalVariables.h"
#include "VoxelNodes/VoxelExecNodes.h"
#include "VoxelNodes/VoxelHeightmapSamplerNode.h"
#include "VoxelNodes/VoxelDataAssetSamplerNode.h"
#include "VoxelNodes/VoxelTextureSamplerNode.h"
#include "VoxelNodes/VoxelCurveNodes.h"
#include "VoxelNodes/VoxelMathNodes.h"
#include "VoxelNodes/VoxelGeneratorSamplerNodes.h"
#include "VoxelGraphNodes/VoxelGraphNode_Knot.h"
#include "VoxelGraphNodes/VoxelGraphNode.h"
#include "VoxelGraphNodes/VoxelGraphNode_Root.h"
#include "ScopedTransaction.h"
#include "EdGraphNode_Comment.h"
#include "GraphEditorSettings.h"
#include "Layout/SlateRect.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Commands/GenericCommands.h"
#include "UObject/UObjectIterator.h"
#include "GraphEditorActions.h"
#include "AssetRegistryModule.h"
#include "Modules/ModuleManager.h"
#include "Engine/StreamableManager.h"
#include "Curves/CurveFloat.h"
#include "Curves/CurveLinearColor.h"
#include "ToolMenu.h"
#include "ToolMenuSection.h"
UEdGraphNode* FVoxelGraphSchemaAction_NewNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
check(VoxelNodeClass);
UVoxelGraphGenerator* Generator = CastChecked<UVoxelEdGraph>(ParentGraph)->GetGenerator();
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New voxel node"));
UVoxelNode* NewNode = Generator->ConstructNewNode(VoxelNodeClass, Location, bSelectNewNode);
NewNode->GraphNode->ReconstructNode();
// Autowire before combining if not vector
if (FromPin && FVoxelPinCategory::FromString(FromPin->PinType.PinCategory) != EVoxelPinCategory::Vector)
{
NewNode->GraphNode->AutowireNewNode(FromPin);
}
// Combine all vector pins on spawn
if (auto* VoxelNode = Cast<UVoxelGraphNode>(NewNode->GraphNode))
{
VoxelNode->CombineAll();
}
// Autowire after combining if vector
if (FromPin && FVoxelPinCategory::FromString(FromPin->PinType.PinCategory) == EVoxelPinCategory::Vector)
{
NewNode->GraphNode->AutowireNewNode(FromPin);
}
// Else the voxel pin arrays are invalid
Generator->CompileVoxelNodesFromGraphNodes();
return NewNode->GraphNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewMacroNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
check(Macro);
UVoxelGraphGenerator* Generator = CastChecked<UVoxelEdGraph>(ParentGraph)->GetGenerator();
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New macro node"));
UVoxelGraphMacroNode* NewNode = Generator->ConstructNewNode<UVoxelGraphMacroNode>(Location, bSelectNewNode);
NewNode->Macro = Macro;
NewNode->GraphNode->ReconstructNode();
// Autowire before combining if not vector
if (FromPin && FVoxelPinCategory::FromString(FromPin->PinType.PinCategory) != EVoxelPinCategory::Vector)
{
NewNode->GraphNode->AutowireNewNode(FromPin);
}
// Combine all vector pins on spawn
if (auto* VoxelNode = Cast<UVoxelGraphNode>(NewNode->GraphNode))
{
VoxelNode->CombineAll();
}
// Autowire after combining if vector
if (FromPin && FVoxelPinCategory::FromString(FromPin->PinType.PinCategory) == EVoxelPinCategory::Vector)
{
NewNode->GraphNode->AutowireNewNode(FromPin);
}
// Else the voxel pin arrays are invalid
Generator->CompileVoxelNodesFromGraphNodes();
return NewNode->GraphNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewLocalVariableDeclaration::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
UVoxelGraphGenerator* Generator = CastChecked<UVoxelEdGraph>(ParentGraph)->GetGenerator();
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New local variable declaration"));
UVoxelLocalVariableDeclaration* Declaration = Generator->ConstructNewNode<UVoxelLocalVariableDeclaration>(Location, bSelectNewNode);
Declaration->SetCategory(PinCategory);
if (!DefaultName.IsNone())
{
Declaration->Name = DefaultName;
}
Declaration->GraphNode->ReconstructNode();
Declaration->GraphNode->AutowireNewNode(FromPin);
// Else the voxel pin arrays are invalid
Generator->CompileVoxelNodesFromGraphNodes();
return Declaration->GraphNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewLocalVariableUsage::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
check(Declaration);
UVoxelGraphGenerator* Generator = CastChecked<UVoxelEdGraph>(ParentGraph)->GetGenerator();
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New local variable usage"));
UVoxelLocalVariableUsage* Usage = Generator->ConstructNewNode<UVoxelLocalVariableUsage>(Location, bSelectNewNode);
Usage->Declaration = Declaration;
Usage->DeclarationGuid = Declaration->VariableGuid;
Usage->GraphNode->ReconstructNode();
Usage->GraphNode->AutowireNewNode(FromPin);
// Else the voxel pin arrays are invalid
Generator->CompileVoxelNodesFromGraphNodes();
return Usage->GraphNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewSetterNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
UVoxelGraphGenerator* Generator = CastChecked<UVoxelEdGraph>(ParentGraph)->GetGenerator();
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New setter node"));
UVoxelNode_SetNode* NewNode = Generator->ConstructNewNode<UVoxelNode_SetNode>(Location, bSelectNewNode);
NewNode->SetIndex(Index);
NewNode->GraphNode->ReconstructNode();
NewNode->GraphNode->AutowireNewNode(FromPin);
// Else the voxel pin arrays are invalid
Generator->CompileVoxelNodesFromGraphNodes();
return NewNode->GraphNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewKnotNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
const FScopedTransaction Transaction(VOXEL_LOCTEXT("New reroute node"));
ParentGraph->Modify();
FGraphNodeCreator<UVoxelGraphNode_Knot> KnotNodeCreator(*ParentGraph);
UVoxelGraphNode_Knot* KnotNode = KnotNodeCreator.CreateNode(bSelectNewNode);
KnotNodeCreator.Finalize();
KnotNode->NodePosX = Location.X;
KnotNode->NodePosY = Location.Y;
KnotNode->AutowireNewNode(FromPin);
KnotNode->PropagatePinType();
return KnotNode;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_NewComment::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
// Add menu item for creating comment boxes
UEdGraphNode_Comment* CommentTemplate = NewObject<UEdGraphNode_Comment>();
FVector2D SpawnLocation = Location;
FSlateRect Bounds;
if (FVoxelGraphEditorUtilities::GetBoundsForSelectedNodes(ParentGraph, Bounds, 50.0f))
{
CommentTemplate->SetBounds(Bounds);
SpawnLocation.X = CommentTemplate->NodePosX;
SpawnLocation.Y = CommentTemplate->NodePosY;
}
return FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate<UEdGraphNode_Comment>(ParentGraph, CommentTemplate, SpawnLocation);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
UEdGraphNode* FVoxelGraphSchemaAction_Paste::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
FVoxelGraphEditorUtilities::PasteNodesHere(ParentGraph, Location);
return NULL;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
bool UVoxelGraphSchema::ConnectionCausesLoop(const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const
{
UEdGraphNode* const StartNode = OutputPin->GetOwningNode();
TSet<UEdGraphNode*> ProcessedNodes;
TArray<UEdGraphNode*> NodesToProcess;
NodesToProcess.Add(InputPin->GetOwningNode());
while (NodesToProcess.Num() > 0)
{
UEdGraphNode* Node = NodesToProcess.Pop(false);
if (ProcessedNodes.Contains(Node))
{
continue;
}
ProcessedNodes.Add(Node);
if (auto* PortalInputGraphNode = Cast<UVoxelGraphNode>(Node))
{
if (auto* Declaration = Cast<UVoxelLocalVariableDeclaration>(PortalInputGraphNode->VoxelNode))
{
if (!ensure(Declaration->Graph))
{
continue;
}
for (UVoxelNode* OtherNode : Declaration->Graph->AllNodes)
{
auto* Usage = Cast<UVoxelLocalVariableUsage>(OtherNode);
if (Usage && Usage->Declaration == Declaration)
{
if (StartNode == Usage->GraphNode)
{
return true;
}
NodesToProcess.Add(Usage->GraphNode);
}
}
}
}
for (UEdGraphPin* Pin : Node->GetAllPins())
{
if (Pin->Direction == EGPD_Output)
{
for (auto& LPin : Pin->LinkedTo)
{
check(LPin->Direction == EGPD_Input);
UEdGraphNode* NewNode = LPin->GetOwningNode();
check(NewNode);
if (StartNode == NewNode)
{
return true;
}
NodesToProcess.Add(NewNode);
}
}
}
}
return false;
}
void UVoxelGraphSchema::GetPaletteActions(FGraphActionMenuBuilder& ActionMenuBuilder) const
{
GetAllVoxelNodeActions(ActionMenuBuilder);
GetCommentAction(ActionMenuBuilder);
}
void UVoxelGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
GetAllVoxelNodeActions(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph);
GetCommentAction(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph);
if (!ContextMenuBuilder.FromPin && FVoxelGraphEditorUtilities::CanPasteNodes(ContextMenuBuilder.CurrentGraph))
{
TSharedPtr<FVoxelGraphSchemaAction_Paste> NewAction(new FVoxelGraphSchemaAction_Paste(FText::GetEmpty(), VOXEL_LOCTEXT("Paste here"), FText::GetEmpty(), 0));
ContextMenuBuilder.AddAction(NewAction);
}
}
bool UVoxelGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
{
bool bModified = UEdGraphSchema::TryCreateConnection(A, B);
auto AK = Cast<UVoxelGraphNode_Knot>(A->GetOwningNode());
auto BK = Cast<UVoxelGraphNode_Knot>(B->GetOwningNode());
if (AK)
{
AK->PropagatePinType();
}
if (BK)
{
BK->PropagatePinType();
}
if (bModified)
{
CastChecked<UVoxelEdGraph>(A->GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes();
}
return bModified;
}
void UVoxelGraphSchema::TrySetDefaultValue(
UEdGraphPin& Pin,
const FString& NewDefaultValue,
bool bMarkAsModified
) const
{
FString DefaultValue = NewDefaultValue;
auto Node = Cast<UVoxelGraphNode>(Pin.GetOwningNode());
if (Node && Node->VoxelNode)
{
int32 Index = Node->GetInputPinIndex(&Pin);
if (Index >= 0)
{
auto Category = FVoxelPinCategory::FromString(Pin.PinType.PinCategory);
if (Category == EVoxelPinCategory::Float)
{
float Value = FCString::Atof(*DefaultValue);
auto Bounds = Node->VoxelNode->GetInputPinDefaultValueBounds(Index);
if (Bounds.Min.IsSet())
{
Value = FMath::Max(Bounds.Min.GetValue(), Value);
}
if (Bounds.Max.IsSet())
{
Value = FMath::Min(Bounds.Max.GetValue(), Value);
}
DefaultValue = FString::SanitizeFloat(Value);
}
else if (Category == EVoxelPinCategory::Int)
{
int32 Value = FCString::Atoi(*DefaultValue);
auto Bounds = Node->VoxelNode->GetInputPinDefaultValueBounds(Index);
if (Bounds.Min.IsSet())
{
Value = FMath::Max(FMath::RoundToInt(Bounds.Min.GetValue()), Value);
}
if (Bounds.Max.IsSet())
{
Value = FMath::Min(FMath::RoundToInt(Bounds.Max.GetValue()), Value);
}
DefaultValue = FString::FromInt(Value);
}
}
}
Super::TrySetDefaultValue(Pin, DefaultValue);
CastChecked<UVoxelEdGraph>(Pin.GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes();
}
bool UVoxelGraphSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
if (PinA->Direction == EGPD_Input)
{
//Swap so that A is the from pin and B is the to pin.
UEdGraphPin* Temp = PinA;
PinA = PinB;
PinB = Temp;
}
EVoxelPinCategory AType = FVoxelPinCategory::FromString(PinA->PinType.PinCategory);
EVoxelPinCategory BType = FVoxelPinCategory::FromString(PinB->PinType.PinCategory);
if (AType != BType && (AType == EVoxelPinCategory::Float || AType == EVoxelPinCategory::Int) && (BType == EVoxelPinCategory::Float || BType == EVoxelPinCategory::Int))
{
UEdGraphNode* ANode = PinA->GetOwningNode();
UEdGraphNode* BNode = PinB->GetOwningNode();
UVoxelEdGraph* Graph = CastChecked<UVoxelEdGraph>(ANode->GetGraph());
UVoxelGraphGenerator* Generator = Graph->GetGenerator();
// Since we'll be adding a node, make sure to modify the graph itself.
Graph->Modify();
UVoxelNode* ConvertNode;
FVector2D Position((ANode->NodePosX + BNode->NodePosX) / 2, (ANode->NodePosY + BNode->NodePosY) / 2);
if (AType == EVoxelPinCategory::Int)
{
ConvertNode = Generator->ConstructNewNode<UVoxelNode_FloatOfInt>(Position, false);
}
else
{
ConvertNode = Generator->ConstructNewNode<UVoxelNode_Round>(Position, false);
}
UVoxelGraphNode* ConvertGraphNode = CastChecked<UVoxelGraphNode>(ConvertNode->GraphNode);
auto InputPin = ConvertGraphNode->GetInputPin(0);
auto OutputPin = ConvertGraphNode->GetOutputPin(0);
check(InputPin->PinType.PinCategory == PinA->PinType.PinCategory);
check(OutputPin->PinType.PinCategory == PinB->PinType.PinCategory);
if (!UEdGraphSchema::TryCreateConnection(PinA, InputPin))
{
Graph->RemoveNode(ConvertGraphNode);
return false;
}
if (!UEdGraphSchema::TryCreateConnection(OutputPin, PinB))
{
InputPin->BreakAllPinLinks();
Graph->RemoveNode(ConvertGraphNode);
return false;
}
return true;
}
else
{
return false;
}
}
TArray<UClass*> UVoxelGraphSchema::VoxelNodeClasses;
bool UVoxelGraphSchema::bVoxelNodeClassesInitialized = false;
FLinearColor UVoxelGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
EVoxelPinCategory Category = FVoxelPinCategory::FromString(PinType.PinCategory);
const UGraphEditorSettings* Settings = GetDefault<UGraphEditorSettings>();
if (Category == EVoxelPinCategory::Exec)
{
return Settings->ExecutionPinTypeColor;
}
else if (Category == EVoxelPinCategory::Float)
{
return Settings->FloatPinTypeColor;
}
else if (Category == EVoxelPinCategory::Boolean)
{
return Settings->BooleanPinTypeColor;
}
else if (Category == EVoxelPinCategory::Int)
{
return Settings->IntPinTypeColor;
}
else if (Category == EVoxelPinCategory::Material)
{
return Settings->ObjectPinTypeColor;
}
else if (Category == EVoxelPinCategory::Color)
{
return Settings->StructPinTypeColor;
}
else if (Category == EVoxelPinCategory::Seed)
{
return Settings->SoftClassPinTypeColor;
}
else if (Category == EVoxelPinCategory::Vector)
{
return Settings->VectorPinTypeColor;
}
// Type does not have a defined color!
return Settings->DefaultPinTypeColor;
}
TSharedPtr<FEdGraphSchemaAction> UVoxelGraphSchema::GetCreateCommentAction() const
{
return TSharedPtr<FEdGraphSchemaAction>(static_cast<FEdGraphSchemaAction*>(new FVoxelGraphSchemaAction_NewComment));
}
int32 UVoxelGraphSchema::GetNodeSelectionCount(const UEdGraph* Graph) const
{
return FVoxelGraphEditorUtilities::GetNumberOfSelectedNodes(Graph);
}
void UVoxelGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
const UEdGraph* CurrentGraph = Context->Graph;
const UEdGraphNode* InGraphNode = Context->Node;
const UEdGraphPin* InGraphPin = Context->Pin;
if (InGraphPin)
{
FToolMenuSection& Section = Menu->AddSection("MaterialGraphSchemaPinActions", VOXEL_LOCTEXT("Pin Actions"));
if (InGraphPin->LinkedTo.Num() > 0)
{
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakPinLinks);
}
if (auto* Node = Cast<UVoxelGraphNode>(InGraphNode))
{
if (UVoxelNode* VoxelNode = Node->VoxelNode)
{
// If on an input that can be deleted, show option
if (InGraphPin->Direction == EGPD_Input)
{
const int32 MinPins = VoxelNode->GetMinInputPins();
const int32 MaxPins = VoxelNode->GetMaxInputPins();
if (MinPins != MaxPins && MinPins < VoxelNode->InputPinCount)
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().DeleteInput);
}
}
// Preview
if (InGraphPin->Direction == EGPD_Output && FVoxelPinCategory::FromString(InGraphPin->PinType.PinCategory) == EVoxelPinCategory::Float)
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().TogglePinPreview);
}
if (Node->CanSplitPin_Voxel(*InGraphPin))
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().SplitPin);
}
if (Node->CanCombinePin(*InGraphPin))
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().CombinePin);
}
}
}
}
else if (InGraphNode)
{
// Add a 'Convert to Local Variables' option to reroute nodes
if (auto* Knot = Cast<UVoxelGraphNode_Knot>(InGraphNode))
{
const EVoxelPinCategory Category = FVoxelPinCategory::FromString(Knot->GetInputPin()->PinType.PinCategory);
if (Category != EVoxelPinCategory::Exec &&
Category != EVoxelPinCategory::Wildcard &&
Category != EVoxelPinCategory::Vector)
{
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenu1");
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().ConvertRerouteToVariables);
}
}
if (auto* Node = Cast<UVoxelGraphNode>(InGraphNode))
{
if (UVoxelNode* VoxelNode = Node->VoxelNode)
{
// Add local variables selection & conversion to reroute nodes
if (VoxelNode->IsA(UVoxelLocalVariableBase::StaticClass()))
{
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenu1");
if (VoxelNode->IsA(UVoxelLocalVariableDeclaration::StaticClass()))
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().SelectLocalVariableUsages);
}
if (VoxelNode->IsA(UVoxelLocalVariableUsage::StaticClass()))
{
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().SelectLocalVariableDeclaration);
}
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().ConvertVariablesToReroute);
}
}
}
{
FToolMenuSection& Section = Menu->AddSection("VoxelGraphNodeEdit");
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
}
{
FToolMenuSection& Section = Menu->AddSection("VoxelGraphNodeMisc");
Section.AddMenuEntry(FVoxelGraphEditorCommands::Get().ReconstructNode);
}
{
FToolMenuSection& Section = Menu->AddSection("VoxelGraphNodeAligment");
Section.AddSubMenu("Alignment", VOXEL_LOCTEXT("Alignment"), FText(), FNewMenuDelegate::CreateLambda([](FMenuBuilder& InMenuBuilder) {
InMenuBuilder.BeginSection("EdGraphSchemaAlignment", VOXEL_LOCTEXT("Align"));
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesTop);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesMiddle);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesBottom);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesLeft);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesCenter);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesRight);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().StraightenConnections);
InMenuBuilder.EndSection();
InMenuBuilder.BeginSection("EdGraphSchemaDistribution", VOXEL_LOCTEXT("Distribution"));
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().DistributeNodesHorizontally);
InMenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().DistributeNodesVertically);
InMenuBuilder.EndSection();
}));
}
}
}
void UVoxelGraphSchema::DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
{
auto* Generator = CastChecked<UVoxelEdGraph>(Graph)->GetGenerator();
FStreamableManager AssetLoader;
for(auto& AssetData : Assets)
{
FStringAssetReference AssetRef(AssetData.ObjectPath.ToString());
UObject* Asset = AssetLoader.LoadSynchronous(AssetRef);
if (Asset->IsA<UVoxelHeightmapAsset>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_HeightmapSampler>(GraphPosition);
if (Asset->IsA<UVoxelHeightmapAssetFloat>())
{
Node->bFloatHeightmap = true;
Node->HeightmapFloat = CastChecked<UVoxelHeightmapAssetFloat>(Asset);
}
else if (ensure(Asset->IsA<UVoxelHeightmapAssetUINT16>()))
{
Node->bFloatHeightmap = false;
Node->HeightmapUINT16 = CastChecked<UVoxelHeightmapAssetUINT16>(Asset);
}
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UVoxelDataAsset>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_DataAssetSampler>(GraphPosition);
Node->Asset = CastChecked<UVoxelDataAsset>(Asset);
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UTexture2D>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_TextureSampler>(GraphPosition);
Node->Texture = CastChecked<UTexture2D>(Asset);
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UCurveFloat>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_Curve>(GraphPosition);
Node->Curve = CastChecked<UCurveFloat>(Asset);
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UCurveLinearColor>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_CurveColor>(GraphPosition);
Node->Curve = CastChecked<UCurveLinearColor>(Asset);
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UVoxelGraphMacro>())
{
auto* Node = Generator->ConstructNewNode<UVoxelGraphMacroNode>(GraphPosition);
Node->Macro = CastChecked<UVoxelGraphMacro>(Asset);
Node->GraphNode->ReconstructNode();
Node->SetEditableName(Asset->GetName());
}
else if (Asset->IsA<UVoxelGenerator>())
{
auto* Node = Generator->ConstructNewNode<UVoxelNode_GetGeneratorValue>(GraphPosition);
Node->Generator = CastChecked<UVoxelGenerator>(Asset);
Node->SetEditableName(Asset->GetName());
}
}
Generator->CompileVoxelNodesFromGraphNodes();
}
void UVoxelGraphSchema::GetAssetsGraphHoverMessage(const TArray<FAssetData>& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const
{
FStreamableManager AssetLoader;
for(auto& AssetData : Assets)
{
FStringAssetReference AssetRef(AssetData.ObjectPath.ToString());
UObject* Asset = AssetLoader.LoadSynchronous(AssetRef);
if (Asset->IsA<UVoxelHeightmapAsset>())
{
OutOkIcon = true;
OutTooltipText = "Add Heightmap Sampler node";
}
else if (Asset->IsA<UTexture2D>())
{
OutOkIcon = true;
OutTooltipText = "Add Texture Sampler node";
}
else if (Asset->IsA<UCurveFloat>())
{
OutOkIcon = true;
OutTooltipText = "Add Curve Sampler node";
}
else if (Asset->IsA<UCurveLinearColor>())
{
OutOkIcon = true;
OutTooltipText = "Add Color Curve Sampler node";
}
else if (Asset->IsA<UVoxelGraphMacro>())
{
OutOkIcon = true;
OutTooltipText = "Add Macro node";
}
else if (Asset->IsA<UVoxelGenerator>())
{
OutOkIcon = true;
OutTooltipText = "Add Generator Sampler node";
}
}
}
void UVoxelGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const
{
FGraphNodeCreator<UVoxelGraphNode_Root> StartNodeCreator(Graph);
UVoxelGraphNode_Root* StartNode = StartNodeCreator.CreateNode();
StartNodeCreator.Finalize();
SetNodeMetaData(StartNode, FNodeMetadata::DefaultGraphNode);
UVoxelGraphMacro* Macro = Cast<UVoxelGraphMacro>(CastChecked<UVoxelEdGraph>(&Graph)->GetGenerator());
if (Macro)
{
UVoxelGraphMacroInputNode* InputNode = Macro->ConstructNewNode<UVoxelGraphMacroInputNode>(FVector2D(-100, 0));
UVoxelGraphMacroOutputNode* OutputNode = Macro->ConstructNewNode<UVoxelGraphMacroOutputNode>(FVector2D(100, 0));
Macro->InputNode = InputNode;
Macro->OutputNode = OutputNode;
SetNodeMetaData(InputNode->GraphNode, FNodeMetadata::DefaultGraphNode);
SetNodeMetaData(OutputNode->GraphNode, FNodeMetadata::DefaultGraphNode);
StartNode->NodePosX = -500;
}
}
void UVoxelGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const
{
Super::BreakNodeLinks(TargetNode);
CastChecked<UVoxelEdGraph>(TargetNode.GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes();
}
void UVoxelGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const
{
const FScopedTransaction Transaction(VOXEL_LOCTEXT("Break Pin Links"));
auto OldLinkedTo = TargetPin.LinkedTo;
Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation);
// if this would notify the node then we need to compile the generator
if (bSendsNodeNotifcation)
{
CastChecked<UVoxelEdGraph>(TargetPin.GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes();
}
auto AK = Cast<UVoxelGraphNode_Knot>(TargetPin.GetOwningNode());
if (AK)
{
AK->PropagatePinType();
}
for (auto& Pin : OldLinkedTo)
{
auto BK = Cast<UVoxelGraphNode_Knot>(Pin->GetOwningNode());
if (BK)
{
BK->PropagatePinType();
}
}
}
void UVoxelGraphSchema::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const
{
const FScopedTransaction Transaction(VOXEL_LOCTEXT("Create Reroute Node"));
const FVector2D NodeSpacerSize(42.0f, 24.0f);
const FVector2D KnotTopLeft = GraphPosition - (NodeSpacerSize * 0.5f);
// Create a new knot
UEdGraph* ParentGraph = PinA->GetOwningNode()->GetGraph();
FVoxelGraphSchemaAction_NewKnotNode Action;
UVoxelGraphNode_Knot* NewKnot = Cast<UVoxelGraphNode_Knot>(Action.PerformAction(ParentGraph, NULL, KnotTopLeft, true));
// Move the connections across (only notifying the knot, as the other two didn't really change)
PinA->BreakLinkTo(PinB);
PinA->MakeLinkTo((PinA->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin());
PinB->MakeLinkTo((PinB->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin());
NewKnot->PropagatePinType();
// Recompile
CastChecked<UVoxelEdGraph>(PinA->GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes();
}
const FPinConnectionResponse UVoxelGraphSchema::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
// Make sure the pins are not on the same node
if (PinA->GetOwningNode() == PinB->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, VOXEL_LOCTEXT("Both are on the same node"));
}
// Compare the directions
const UEdGraphPin* InputPin = NULL;
const UEdGraphPin* OutputPin = NULL;
if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, VOXEL_LOCTEXT("Directions are not compatible"));
}
check(InputPin);
check(OutputPin);
auto InputCategory = FVoxelPinCategory::FromString(InputPin->PinType.PinCategory);
auto OutputCategory = FVoxelPinCategory::FromString(OutputPin->PinType.PinCategory);
if (InputCategory != OutputCategory && InputCategory != EVoxelPinCategory::Wildcard && OutputCategory != EVoxelPinCategory::Wildcard)
{
if (InputCategory == EVoxelPinCategory::Float && OutputCategory == EVoxelPinCategory::Int)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, VOXEL_LOCTEXT("Cast to float"));
}
else if (InputCategory == EVoxelPinCategory::Int && OutputCategory == EVoxelPinCategory::Float)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, VOXEL_LOCTEXT("Round to int"));
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, VOXEL_LOCTEXT("Types are not compatible"));
}
}
if (ConnectionCausesLoop(InputPin, OutputPin))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, VOXEL_LOCTEXT("Connection would cause loop"));
}
// Break existing connections on inputs only except for exec - multiple output connections are acceptable
if (InputCategory != EVoxelPinCategory::Exec && InputPin->LinkedTo.Num() > 0)
{
ECanCreateConnectionResponse ReplyBreakOutputs;
if (InputPin == PinA)
{
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_A;
}
else
{
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_B;
}
return FPinConnectionResponse(ReplyBreakOutputs, VOXEL_LOCTEXT("Replace existing connections"));
}
if (OutputCategory == EVoxelPinCategory::Exec && OutputPin->LinkedTo.Num() > 0)
{
ECanCreateConnectionResponse ReplyBreakOutputs;
if (OutputPin == PinA)
{
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_A;
}
else
{
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_B;
}
return FPinConnectionResponse(ReplyBreakOutputs, VOXEL_LOCTEXT("Replace existing connections"));
}
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, TEXT(""));
}
void UVoxelGraphSchema::GetAllVoxelNodeActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) const
{
InitVoxelNodeClasses();
auto* FromPin = ActionMenuBuilder.FromPin;
EVoxelPinCategory Category = FromPin ? FVoxelPinCategory::FromString(FromPin->PinType.PinCategory) : EVoxelPinCategory::Wildcard;
const int32 RerouteNodePriority = 20;
const int32 LocalVariablesPriority = 10;
const int32 SetterNodesPriority = 5;
const int32 ParameterNodesPriority = 0;
const int32 MacroNodesPriority = 0;
const auto PinMatchesNode = [&](UVoxelNode* Node)
{
if (Category == EVoxelPinCategory::Vector)
{
// Make sure to check the opposite direction of FromPin
return UVoxelGraphNode::HasVectorPin(*Node, FromPin->Direction == EGPD_Input ? EGPD_Output : EGPD_Input);
}
// Make sure to check the opposite direction of FromPin
return FromPin->Direction == EGPD_Input
? Node->HasOutputPinWithCategory(Category)
: Node->HasInputPinWithCategory(Category);
};
// Macros
{
// Load the asset registry module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
// Collect a full list of assets with the specified class
TArray<FAssetData> AssetDataList;
AssetRegistryModule.Get().GetAssetsByClass(UVoxelGraphMacro::StaticClass()->GetFName(), AssetDataList);
for (const FAssetData& AssetData : AssetDataList)
{
FStreamableManager AssetLoader;
FStringAssetReference AssetRef(AssetData.ObjectPath.ToString());
UVoxelGraphMacro* Macro = Cast<UVoxelGraphMacro>(AssetLoader.LoadSynchronous(AssetRef));
if (!Macro || !Macro->InputNode || !Macro->OutputNode || !Macro->bShowInContextMenu)
{
continue;
}
const auto PinMatchesMacro = [&]()
{
// Make sure to check the opposite direction of FromPin
auto* Node = FromPin->Direction == EGPD_Input ? static_cast<UVoxelNode*>(Macro->OutputNode) : Macro->InputNode;
if (!Node)
{
return false;
}
if (Category != EVoxelPinCategory::Vector && Macro->bVectorOnlyNode)
{
// Having all the vector macros when dragging a float is annoying
return false;
}
return PinMatchesNode(Node);
};
if (Macro->bShowInContextMenu && (!FromPin || PinMatchesMacro()))
{
const FText Name = Macro->GetMacroName();
const FText AddToolTip = FText::FromString(Macro->Tooltip);
const FText Keywords = FText::FromString(Macro->Keywords);
const FText MacroCategory = Macro->GetMacroCategory();
TSharedPtr<FVoxelGraphSchemaAction_NewMacroNode> NewNodeAction(
new FVoxelGraphSchemaAction_NewMacroNode(
MacroCategory,
Name,
AddToolTip,
MacroNodesPriority,
Keywords));
NewNodeAction->Macro = Macro;
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
}
// Local variables declaration
{
TSharedPtr<FVoxelGraphSchemaAction_NewLocalVariableDeclaration> NewNodeAction(
new FVoxelGraphSchemaAction_NewLocalVariableDeclaration(
FText::GetEmpty(),
VOXEL_LOCTEXT("Create local variable"),
VOXEL_LOCTEXT("Create a new local variable here"),
RerouteNodePriority));
NewNodeAction->PinCategory = EVoxelPinCategory::Float;
bool bAdd = false;
if (FromPin)
{
if (FromPin->Direction == EGPD_Output)
{
if (Category != EVoxelPinCategory::Exec &&
Category != EVoxelPinCategory::Wildcard &&
Category != EVoxelPinCategory::Vector)
{
NewNodeAction->DefaultName = FromPin->PinName;
NewNodeAction->PinCategory = Category;
bAdd = true;
}
}
}
else
{
bAdd = true;
}
if (bAdd)
{
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
// For the palette actions CurrentGraph is null
if (CurrentGraph)
{
auto* Graph = CastChecked<UVoxelEdGraph>(CurrentGraph);
auto* Generator = Graph->GetGenerator();
// Local variables usage
if (!FromPin || FromPin->Direction == EGPD_Input)
{
for (auto& Node : Generator->AllNodes)
{
auto* Declaration = Cast<UVoxelLocalVariableDeclaration>(Node);
if (Declaration && (!FromPin || Declaration->GetCategory() == Category))
{
const FText Name = FText::FromName(Declaration->Name);
const FText AddToolTip = FText::Format(VOXEL_LOCTEXT("Use {0} here"), Name);
TSharedPtr<FVoxelGraphSchemaAction_NewLocalVariableUsage> NewNodeAction(
new FVoxelGraphSchemaAction_NewLocalVariableUsage(
VOXEL_LOCTEXT("Local variables"),
Name,
AddToolTip,
LocalVariablesPriority));
NewNodeAction->Declaration = Declaration;
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
}
// Setter nodes
{
auto Outputs = Generator->GetOutputs();
for (auto It : Outputs)
{
auto Output = It.Value;
auto Index = It.Key;
if (FVoxelGraphOutputsUtils::IsVoxelGraphOutputHidden(Index))
{
continue;
}
if (!FromPin || Category == EVoxelPinCategory::Exec || Category == FVoxelPinCategory::DataPinToPin(Output.Category))
{
const FText Name = FText::FromString("Set " + Output.Name.ToString());
const FText AddToolTip = FText::Format(VOXEL_LOCTEXT("Adds {0} node here"), Name);
TSharedPtr<FVoxelGraphSchemaAction_NewSetterNode> NewNodeAction(
new FVoxelGraphSchemaAction_NewSetterNode(
VOXEL_LOCTEXT("Setter nodes"),
Name,
AddToolTip,
SetterNodesPriority));
NewNodeAction->Index = Index;
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
}
}
for (auto& NodeClass : VoxelNodeClasses)
{
UVoxelNode* DefaultNode = NodeClass->GetDefaultObject<UVoxelNode>();
if (!FromPin || PinMatchesNode(DefaultNode))
{
const auto GetCategory = [](UClass* Class)
{
return Class->GetMetaDataText(TEXT("Category"), TEXT("UObjectCategory"), Class->GetFullGroupName(false));
};
FText ActionCategory = GetCategory(NodeClass);
if (ActionCategory.IsEmpty())
{
UClass* Class = NodeClass->GetSuperClass();
while (Class && ActionCategory.IsEmpty())
{
ActionCategory = GetCategory(Class);
Class = Class->GetSuperClass();
}
}
int32 Priority = 0;
if (NodeClass->IsChildOf(UVoxelExposedNode::StaticClass()))
{
Priority = ParameterNodesPriority;
}
if (NodeClass->IsChildOf(UVoxelSetterNode::StaticClass()))
{
Priority = SetterNodesPriority;
}
FText Name = FText::FromString(NodeClass->GetDescription());
FText AddToolTip = NodeClass->GetToolTipText();
FText Keywords = NodeClass->GetMetaDataText(TEXT("Keywords"), TEXT("UObjectKeywords"), GetClass()->GetFullGroupName(false));
TSharedPtr<FVoxelGraphSchemaAction_NewNode> NewNodeAction(
new FVoxelGraphSchemaAction_NewNode(
ActionCategory,
Name,
AddToolTip,
Priority,
Keywords));
NewNodeAction->VoxelNodeClass = NodeClass;
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
if (FromPin)
{
const FText MenuDescription = VOXEL_LOCTEXT("Add reroute node");
const FText ToolTip = VOXEL_LOCTEXT("Create a reroute node.");
TSharedPtr<FVoxelGraphSchemaAction_NewKnotNode> NewNodeAction(new FVoxelGraphSchemaAction_NewKnotNode(FText::GetEmpty(), MenuDescription, ToolTip, RerouteNodePriority));
ActionMenuBuilder.AddAction(NewNodeAction);
}
}
void UVoxelGraphSchema::GetCommentAction(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph /*= NULL*/) const
{
if (!ActionMenuBuilder.FromPin)
{
const bool bIsManyNodesSelected = CurrentGraph ? (FVoxelGraphEditorUtilities::GetNumberOfSelectedNodes(CurrentGraph) > 0) : false;
const FText MenuDescription = bIsManyNodesSelected ? VOXEL_LOCTEXT("Create Comment from Selection") : VOXEL_LOCTEXT("Add Comment");
const FText ToolTip = VOXEL_LOCTEXT("Creates a comment.");
TSharedPtr<FVoxelGraphSchemaAction_NewComment> NewAction(new FVoxelGraphSchemaAction_NewComment(FText::GetEmpty(), MenuDescription, ToolTip, 0));
ActionMenuBuilder.AddAction(NewAction);
}
}
void UVoxelGraphSchema::InitVoxelNodeClasses()
{
if (bVoxelNodeClassesInitialized)
{
return;
}
VoxelNodeClasses.Empty();
// Construct list of non-abstract voxel node classes.
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->IsChildOf(UVoxelNode::StaticClass()) && !It->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable | CLASS_Deprecated))
{
VoxelNodeClasses.Add(*It);
}
}
VoxelNodeClasses.Sort();
bVoxelNodeClassesInitialized = true;
}