// 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(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(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(ParentGraph)->GetGenerator(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("New macro node")); UVoxelGraphMacroNode* NewNode = Generator->ConstructNewNode(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(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(ParentGraph)->GetGenerator(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("New local variable declaration")); UVoxelLocalVariableDeclaration* Declaration = Generator->ConstructNewNode(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(ParentGraph)->GetGenerator(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("New local variable usage")); UVoxelLocalVariableUsage* Usage = Generator->ConstructNewNode(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(ParentGraph)->GetGenerator(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("New setter node")); UVoxelNode_SetNode* NewNode = Generator->ConstructNewNode(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 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(); 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(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 ProcessedNodes; TArray 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(Node)) { if (auto* Declaration = Cast(PortalInputGraphNode->VoxelNode)) { if (!ensure(Declaration->Graph)) { continue; } for (UVoxelNode* OtherNode : Declaration->Graph->AllNodes) { auto* Usage = Cast(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 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(A->GetOwningNode()); auto BK = Cast(B->GetOwningNode()); if (AK) { AK->PropagatePinType(); } if (BK) { BK->PropagatePinType(); } if (bModified) { CastChecked(A->GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes(); } return bModified; } void UVoxelGraphSchema::TrySetDefaultValue( UEdGraphPin& Pin, const FString& NewDefaultValue, bool bMarkAsModified ) const { FString DefaultValue = NewDefaultValue; auto Node = Cast(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(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(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(Position, false); } else { ConvertNode = Generator->ConstructNewNode(Position, false); } UVoxelGraphNode* ConvertGraphNode = CastChecked(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 UVoxelGraphSchema::VoxelNodeClasses; bool UVoxelGraphSchema::bVoxelNodeClassesInitialized = false; FLinearColor UVoxelGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { EVoxelPinCategory Category = FVoxelPinCategory::FromString(PinType.PinCategory); const UGraphEditorSettings* Settings = GetDefault(); 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 UVoxelGraphSchema::GetCreateCommentAction() const { return TSharedPtr(static_cast(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(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(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(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& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const { auto* Generator = CastChecked(Graph)->GetGenerator(); FStreamableManager AssetLoader; for(auto& AssetData : Assets) { FStringAssetReference AssetRef(AssetData.ObjectPath.ToString()); UObject* Asset = AssetLoader.LoadSynchronous(AssetRef); if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); if (Asset->IsA()) { Node->bFloatHeightmap = true; Node->HeightmapFloat = CastChecked(Asset); } else if (ensure(Asset->IsA())) { Node->bFloatHeightmap = false; Node->HeightmapUINT16 = CastChecked(Asset); } Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Asset = CastChecked(Asset); Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Texture = CastChecked(Asset); Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Curve = CastChecked(Asset); Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Curve = CastChecked(Asset); Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Macro = CastChecked(Asset); Node->GraphNode->ReconstructNode(); Node->SetEditableName(Asset->GetName()); } else if (Asset->IsA()) { auto* Node = Generator->ConstructNewNode(GraphPosition); Node->Generator = CastChecked(Asset); Node->SetEditableName(Asset->GetName()); } } Generator->CompileVoxelNodesFromGraphNodes(); } void UVoxelGraphSchema::GetAssetsGraphHoverMessage(const TArray& 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()) { OutOkIcon = true; OutTooltipText = "Add Heightmap Sampler node"; } else if (Asset->IsA()) { OutOkIcon = true; OutTooltipText = "Add Texture Sampler node"; } else if (Asset->IsA()) { OutOkIcon = true; OutTooltipText = "Add Curve Sampler node"; } else if (Asset->IsA()) { OutOkIcon = true; OutTooltipText = "Add Color Curve Sampler node"; } else if (Asset->IsA()) { OutOkIcon = true; OutTooltipText = "Add Macro node"; } else if (Asset->IsA()) { OutOkIcon = true; OutTooltipText = "Add Generator Sampler node"; } } } void UVoxelGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const { FGraphNodeCreator StartNodeCreator(Graph); UVoxelGraphNode_Root* StartNode = StartNodeCreator.CreateNode(); StartNodeCreator.Finalize(); SetNodeMetaData(StartNode, FNodeMetadata::DefaultGraphNode); UVoxelGraphMacro* Macro = Cast(CastChecked(&Graph)->GetGenerator()); if (Macro) { UVoxelGraphMacroInputNode* InputNode = Macro->ConstructNewNode(FVector2D(-100, 0)); UVoxelGraphMacroOutputNode* OutputNode = Macro->ConstructNewNode(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(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(TargetPin.GetOwningNode()->GetGraph())->GetGenerator()->CompileVoxelNodesFromGraphNodes(); } auto AK = Cast(TargetPin.GetOwningNode()); if (AK) { AK->PropagatePinType(); } for (auto& Pin : OldLinkedTo) { auto BK = Cast(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(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(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("AssetRegistry"); // Collect a full list of assets with the specified class TArray AssetDataList; AssetRegistryModule.Get().GetAssetsByClass(UVoxelGraphMacro::StaticClass()->GetFName(), AssetDataList); for (const FAssetData& AssetData : AssetDataList) { FStreamableManager AssetLoader; FStringAssetReference AssetRef(AssetData.ObjectPath.ToString()); UVoxelGraphMacro* Macro = Cast(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(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 NewNodeAction( new FVoxelGraphSchemaAction_NewMacroNode( MacroCategory, Name, AddToolTip, MacroNodesPriority, Keywords)); NewNodeAction->Macro = Macro; ActionMenuBuilder.AddAction(NewNodeAction); } } } // Local variables declaration { TSharedPtr 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(CurrentGraph); auto* Generator = Graph->GetGenerator(); // Local variables usage if (!FromPin || FromPin->Direction == EGPD_Input) { for (auto& Node : Generator->AllNodes) { auto* Declaration = Cast(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 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 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(); 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 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 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 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 It; It; ++It) { if (It->IsChildOf(UVoxelNode::StaticClass()) && !It->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable | CLASS_Deprecated)) { VoxelNodeClasses.Add(*It); } } VoxelNodeClasses.Sort(); bVoxelNodeClassesInitialized = true; }