// Copyright 2020 Phyronnaz #include "VoxelGraphNode.h" #include "VoxelGraphGenerator.h" #include "VoxelNode.h" #include "VoxelNodes/VoxelLocalVariables.h" #include "VoxelNodes/VoxelGraphMacro.h" #include "VoxelGraphNodes/VoxelGraphNode_Knot.h" #include "VoxelGraphEditorUtilities.h" #include "IVoxelGraphEditorToolkit.h" #include "VoxelGraphEditorCommands.h" #include "VoxelEdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphNode.h" #include "GraphEditorActions.h" #include "Engine/Font.h" #include "ScopedTransaction.h" #include "Editor/EditorEngine.h" #include "Framework/Commands/GenericCommands.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Launch/Resources/Version.h" void UVoxelGraphNode::SetVoxelNode(UVoxelNode* InNode) { check(InNode); VoxelNode = InNode; bCanRenameNode = VoxelNode->CanRenameNode(); } void UVoxelGraphNode::PostCopyNode() { // Make sure the VoxelNode goes back to being owned by the generator after copying. ResetVoxelNodeOwner(); } void UVoxelGraphNode::CreateInputPin() { const int32 PinIndex = GetInputCount(); UEdGraphPin* NewPin = CreatePin(EGPD_Input, FVoxelPinCategory::GetName(VoxelNode->GetInputPinCategory(PinIndex)), FName(), nullptr, VoxelNode->GetInputPinName(PinIndex)); if (NewPin->PinName.IsNone()) { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Input")); NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); } NewPin->DefaultValue = VoxelNode->GetInputPinDefaultValue(PinIndex); if (NewPin->DefaultValue.IsEmpty()) { NewPin->DefaultValue = FVoxelPinCategory::GetDefaultValue(VoxelNode->GetInputPinCategory(PinIndex)); } } void UVoxelGraphNode::CreateOutputPin() { const int32 PinIndex = GetOutputCount(); UEdGraphPin* NewPin = CreatePin(EGPD_Output, FVoxelPinCategory::GetName(VoxelNode->GetOutputPinCategory(PinIndex)), FName(), nullptr, VoxelNode->GetOutputPinName(PinIndex)); if (NewPin->PinName.IsNone()) { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Output")); NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); } } void UVoxelGraphNode::AddInputPin() { const FScopedTransaction Transaction(VOXEL_LOCTEXT("Add Input Pin")); Modify(); const int32 Increment = VoxelNode->GetInputPinsIncrement(); VoxelNode->InputPinCount += Increment; ensure(VoxelNode->InputPinCount <= VoxelNode->GetMaxInputPins()); for (int32 Index = 0; Index < Increment; ++Index) { CreateInputPin(); } VoxelNode->OnInputPinCountModified(); UVoxelGraphGenerator* Generator = CastChecked(GetGraph())->GetGenerator(); Generator->CompileVoxelNodesFromGraphNodes(); // Refresh the current graph, so the pins can be updated GetGraph()->NotifyGraphChanged(); } void UVoxelGraphNode::RemoveInputPin(UEdGraphPin* InGraphPin) { const FScopedTransaction Transaction(VOXEL_LOCTEXT("Delete Input Pin")); Modify(); for (auto* InputPin : GetInputPins()) { if (InGraphPin == InputPin) { InGraphPin->MarkAsGarbage(); Pins.Remove(InGraphPin); const int32 Increment = VoxelNode->GetInputPinsIncrement(); if (Increment > 1) { const int32 PinIndex = VoxelNode->GetInputPinIndex(InGraphPin->PinId); if (ensure(PinIndex != -1)) { // Below = higher index! const int32 PinsBelow = (VoxelNode->InputPinCount - 1 - PinIndex) % Increment; const int32 PinsAbove = Increment - 1 - PinsBelow; for (int32 Index = PinIndex - PinsAbove; Index <= PinIndex + PinsBelow; Index++) { if (ensure(VoxelNode->InputPins.IsValidIndex(Index))) { const auto PinId = VoxelNode->InputPins[Index].PinId; Pins.RemoveAll([&](auto& ArrayPin) { return ArrayPin->PinId == PinId; }); } } } } // also remove the VoxelNode child node so ordering matches VoxelNode->Modify(); VoxelNode->InputPinCount -= Increment; ensure(VoxelNode->InputPinCount >= VoxelNode->GetMinInputPins()); break; } } VoxelNode->OnInputPinCountModified(); UVoxelGraphGenerator* Generator = CastChecked(GetGraph())->GetGenerator(); Generator->CompileVoxelNodesFromGraphNodes(); // Refresh the current graph, so the pins can be updated GetGraph()->NotifyGraphChanged(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool UVoxelGraphNode::CanSplitPin_Voxel(const UEdGraphPin& Pin) const { return const_cast(this)->TrySplitPin(const_cast(Pin), true); } bool UVoxelGraphNode::CanCombinePin(const UEdGraphPin& Pin) const { return const_cast(this)->TryCombinePin(const_cast(Pin), true); } bool UVoxelGraphNode::TrySplitPin(UEdGraphPin& Pin, bool bOnlyCheck) { ensure(!Pin.bHidden); if (Pin.SubPins.Num() == 0 || Pin.LinkedTo.Num() > 0) { return false; } if (bOnlyCheck) { return true; } TArray SubDefaultValues; Pin.DefaultValue.ParseIntoArray(SubDefaultValues, TEXT(",")); for (int32 Index = 0; Index < Pin.SubPins.Num(); Index++) { auto* SubPin = Pin.SubPins[Index]; ensure(SubPin->bHidden); ensure(SubPin->ParentPin == &Pin); SubPin->bHidden = false; SubPin->ParentPin = nullptr; SubPin->DefaultValue = SubDefaultValues.IsValidIndex(Index) ? SubDefaultValues[Index] : ""; } Pin.SubPins.Empty(); ensure(RemovePin(&Pin)); GetGraph()->NotifyGraphChanged(); return true; } bool UVoxelGraphNode::TryCombinePin(UEdGraphPin& Pin, bool bOnlyCheck) { ensure(!Pin.bHidden); const auto NeighborPins = Pin.Direction == EGPD_Input ? GetInputPins() : GetOutputPins(); const int32 PinIndex = NeighborPins.Find(&Pin); if (!ensure(PinIndex != -1)) { return false; } const auto CheckStart = [&](int32 Index) { if (!NeighborPins.IsValidIndex(Index) || !NeighborPins.IsValidIndex(Index + 2)) { return false; } FString Name = NeighborPins[Index]->GetName(); if (!Name.RemoveFromStart("X")) { return false; } return NeighborPins[Index + 1]->GetName() == "Y" + Name && NeighborPins[Index + 2]->GetName() == "Z" + Name; }; const auto CheckEnd = [&](int32 Index) { if (!NeighborPins.IsValidIndex(Index) || !NeighborPins.IsValidIndex(Index + 2)) { return false; } FString Name = NeighborPins[Index]->GetName(); if (!Name.RemoveFromEnd("X")) { return false; } return NeighborPins[Index + 1]->GetName() == Name + "Y" && NeighborPins[Index + 2]->GetName() == Name + "Z"; }; int32 IndexX = -1; bool bIsStart = false; for (int32 Index = PinIndex - 2; Index <= PinIndex; Index++) { if (CheckStart(Index)) { bIsStart = true; IndexX = Index; break; } if (CheckEnd(Index)) { bIsStart = false; IndexX = Index; break; } } if (IndexX == -1) { return false; } for (int32 Index = 0; Index < 3; Index++) { if (NeighborPins[IndexX + Index]->LinkedTo.Num() > 0) { return false; } } if (bOnlyCheck) { return true; } FString ParentPinName = NeighborPins[IndexX]->GetName(); if (bIsStart) { ensure(ParentPinName.RemoveFromStart("X")); ParentPinName.RemoveFromStart("."); } else { ensure(ParentPinName.RemoveFromEnd("X")); ParentPinName.RemoveFromEnd("."); } auto* ParentPin = CreatePin(Pin.Direction, FVoxelPinCategory::GetName(EVoxelPinCategory::Vector), FName(), nullptr, *ParentPinName); Pins.Pop(false); FVector DefaultValue; for (int32 Index = 0; Index < 3; Index++) { auto* SubPin = NeighborPins[IndexX + Index]; SubPin->bHidden = true; SubPin->ParentPin = ParentPin; DefaultValue[Index] = FCString::Atof(*SubPin->DefaultValue); ParentPin->SubPins.Add(SubPin); } ParentPin->DefaultValue = FString::Printf(TEXT("%f,%f,%f"), DefaultValue.X, DefaultValue.Y, DefaultValue.Z); // Add the parent before the sub pins const int32 InsertIndex = Pins.Find(NeighborPins[IndexX]); check(InsertIndex != -1); Pins.Insert(ParentPin, InsertIndex); GetGraph()->NotifyGraphChanged(); return true; } void UVoxelGraphNode::CombineAll() { const auto Copy = Pins; for (auto& Pin : Copy) { if (!Pin->bHidden) { TryCombinePin(*Pin, false); } } } bool UVoxelGraphNode::HasVectorPin(UVoxelNode& Node, EEdGraphPinDirection Direction) { TArray Names; if (Direction == EGPD_Input) { const int32 InputCount = Node.GetMinInputPins(); for (int32 Index = 0; Index < InputCount; Index++) { Names.Add(Node.GetInputPinName(Index).ToString()); } } else { const int32 OutputCount = Node.GetOutputPinsCount(); for (int32 Index = 0; Index < OutputCount; Index++) { Names.Add(Node.GetOutputPinName(Index).ToString()); } } const auto CheckStart = [&](int32 Index) { FString Name = Names[Index]; if (!Name.RemoveFromStart("X")) { return false; } return Names[Index + 1] == "Y" + Name && Names[Index + 2] == "Z" + Name; }; const auto CheckEnd = [&](int32 Index) { FString Name = Names[Index]; if (!Name.RemoveFromEnd("X")) { return false; } return Names[Index + 1] == Name + "Y" && Names[Index + 2] == Name + "Z"; }; for (int32 Index = 0; Index < Names.Num() - 2; Index++) { if (CheckStart(Index) || CheckEnd(Index)) { return true; } } return false; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool UVoxelGraphNode::CanAddInputPin() const { if (VoxelNode) { const int32 MinPins = VoxelNode->GetMinInputPins(); const int32 MaxPins = VoxelNode->GetMaxInputPins(); if (MinPins == MaxPins) { return false; } else { return GetInputCount() < MaxPins; } } else { return false; } } bool UVoxelGraphNode::IsCompact() const { return VoxelNode && VoxelNode->IsCompact(); } FLinearColor UVoxelGraphNode::GetNodeBodyColor() const { if (!IsNodeEnabled()) { return FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } if (VoxelNode) { for (auto& Pin : Pins) { if (Pin->bIsDiffing) { return FLinearColor(0.f, 0.f, 1.0f, 1.f); } } return VoxelNode->GetNodeBodyColor(); } return FLinearColor::White; } bool UVoxelGraphNode::IsOutdated() const { int32 InputIndex = 0; int32 OutputIndex = 0; for (auto* Pin : Pins) { if (Pin->SubPins.Num() > 0) { continue; } if (Pin->Direction == EGPD_Input) { if (FVoxelPinCategory::GetName(VoxelNode->GetInputPinCategory(InputIndex)) != Pin->PinType.PinCategory) { return true; } const FName PinName = VoxelNode->GetInputPinName(InputIndex); if (!PinName.IsNone() && PinName != Pin->PinName) { return true; } InputIndex++; } else { check(Pin->Direction == EGPD_Output); if (FVoxelPinCategory::GetName(VoxelNode->GetOutputPinCategory(OutputIndex)) != Pin->PinType.PinCategory) { return true; } const FName PinName = VoxelNode->GetOutputPinName(OutputIndex); if (!PinName.IsNone() && PinName != Pin->PinName) { return true; } OutputIndex++; } } return false; } void UVoxelGraphNode::CreateInputPins() { if (!ensure(VoxelNode)) return; VoxelNode->InputPinCount = FMath::Clamp(VoxelNode->InputPinCount, VoxelNode->GetMinInputPins(), VoxelNode->GetMaxInputPins()); while (GetInputCount() < VoxelNode->InputPinCount) { CreateInputPin(); } } void UVoxelGraphNode::CreateOutputPins() { if (!ensure(VoxelNode)) return; while (GetOutputCount() < VoxelNode->GetOutputPinsCount()) { CreateOutputPin(); } } void UVoxelGraphNode::RestoreVectorPins(const TArray& OldInputPins, const TArray& OldOutputPins) { const auto NewInputPins = GetInputPins(); const auto NewOutputPins = GetOutputPins(); const auto Restore = [&](const TArray& OldPins, const TArray& NewPins) { int32 NewIndex = 0; for (int32 Index = 0; Index < OldPins.Num() && NewIndex < NewPins.Num(); Index++) { auto* OldPin = OldPins[Index]; if (OldPin->SubPins.Num() == 0) { // Not a parent pin auto* NewPin = NewPins[NewIndex++]; if (OldPin->ParentPin && !NewPin->ParentPin) { TryCombinePin(*NewPin, false); } } } }; Restore(OldInputPins, NewInputPins); Restore(OldOutputPins, NewOutputPins); } FText UVoxelGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (VoxelNode) { if (TitleType == ENodeTitleType::EditableTitle) { return FText::FromString(VoxelNode->GetEditableName()); } else { return VoxelNode->GetTitle(); } } else { return Super::GetNodeTitle(TitleType); } } FLinearColor UVoxelGraphNode::GetNodeTitleColor() const { if (VoxelNode) { return VoxelNode->GetColor(); } else { return FLinearColor::Gray; } } void UVoxelGraphNode::PrepareForCopying() { if (VoxelNode) { // Temporarily take ownership of the VoxelNode, so that it is not deleted when cutting VoxelNode->Rename(NULL, this, REN_DontCreateRedirectors); } } FText UVoxelGraphNode::GetTooltipText() const { if (VoxelNode) { return VoxelNode->GetTooltip(); } else { return GetNodeTitle(ENodeTitleType::ListView); } } FString UVoxelGraphNode::GetDocumentationExcerptName() const { // Default the node to searching for an excerpt named for the C++ node class name, including the U prefix. // This is done so that the excerpt name in the doc file can be found by find-in-files when searching for the full class name. UClass* MyClass = (VoxelNode != NULL) ? VoxelNode->GetClass() : this->GetClass(); return FString::Printf(TEXT("%s%s"), MyClass->GetPrefixCPP(), *MyClass->GetName()); } bool UVoxelGraphNode::CanUserDeleteNode() const { return !VoxelNode || VoxelNode->CanUserDeleteNode(); } bool UVoxelGraphNode::CanDuplicateNode() const { return !VoxelNode || VoxelNode->CanDuplicateNode(); } bool UVoxelGraphNode::CanJumpToDefinition() const { return VoxelNode && ((VoxelNode->IsA(UVoxelGraphMacroNode::StaticClass()) && CastChecked(VoxelNode)->Macro) || VoxelNode->IsA()); } void UVoxelGraphNode::JumpToDefinition() const { if (auto* Macro = Cast(VoxelNode)) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(Macro->Macro); } else if (auto* Usage = Cast(VoxelNode)) { if (Usage->Declaration) { FVoxelGraphEditorUtilities::GetIVoxelEditorForGraph(VoxelNode->Graph->VoxelGraph)->SelectNodesAndZoomToFit({ Usage->Declaration->GraphNode }); } } } void UVoxelGraphNode::OnRenameNode(const FString& NewName) { if (VoxelNode) { VoxelNode->Modify(); VoxelNode->SetEditableName(NewName); VoxelNode->MarkPackageDirty(); } } void UVoxelGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const { if (!VoxelNode) { return; } TArray PinIds; PinIds.Add(Pin.PinId); for (auto& SubPin : Pin.SubPins) { PinIds.Add(SubPin->PinId); } for (auto& PinId : PinIds) { int32 Index = VoxelNode->GetInputPinIndex(PinId); if (Index != -1) { if (!HoverTextOut.IsEmpty()) HoverTextOut += "\n"; HoverTextOut += VoxelNode->GetInputPinToolTip(Index); } else { Index = VoxelNode->GetOutputPinIndex(PinId); if (Index != -1) { if (!HoverTextOut.IsEmpty()) HoverTextOut += "\n"; HoverTextOut += VoxelNode->GetOutputPinToolTip(Index); } } } } void UVoxelGraphNode::PostLoad() { Super::PostLoad(); // Fixup any VoxelNode back pointers that may be out of date if (VoxelNode) { VoxelNode->GraphNode = this; } for (int32 Index = 0; Index < Pins.Num(); ++Index) { UEdGraphPin* Pin = Pins[Index]; Pin->PinType.bIsConst = false; Pin->PinType.ContainerType = EPinContainerType::None; // Remove preview if (Pin->PinName.IsNone()) { // Makes sure pin has a name for lookup purposes but user will never see it if (Pin->Direction == EGPD_Input) { Pin->PinName = CreateUniquePinName(TEXT("Input")); } else { Pin->PinName = CreateUniquePinName(TEXT("Output")); } Pin->PinFriendlyName = FText::FromString(TEXT(" ")); } } } void UVoxelGraphNode::PostEditImport() { // Make sure this VoxelNode is owned by the generator it's being pasted into. ResetVoxelNodeOwner(); } void UVoxelGraphNode::PostDuplicate(bool bDuplicateForPIE) { Super::PostDuplicate(bDuplicateForPIE); if (!bDuplicateForPIE) { CreateNewGuid(); } } void UVoxelGraphNode::ResetVoxelNodeOwner() { if (VoxelNode) { UVoxelGraphGenerator* Generator = CastChecked(GetGraph())->GetGenerator(); if (VoxelNode->GetOuter() != Generator) { // Ensures VoxelNode is owned by the generator VoxelNode->Rename(NULL, Generator, REN_DontCreateRedirectors); } // Set up the back pointer for newly created voxel nodes VoxelNode->GraphNode = this; } }