// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraNodeOp.h" #include "NiagaraCompileHashVisitor.h" #include "NiagaraHlslTranslator.h" #include "GraphEditorSettings.h" #include "EdGraphSchema_Niagara.h" #include "NiagaraCustomVersion.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraNodeOp) #define LOCTEXT_NAMESPACE "NiagaraNodeOp" UNiagaraNodeOp::UNiagaraNodeOp(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), bAllStatic(false) { } void UNiagaraNodeOp::AllocateDefaultPins() { const UEdGraphSchema_Niagara* Schema = GetDefault(); const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (!OpInfo) { return; } // Create input pins from the op for (int32 SrcIndex = 0; SrcIndex < OpInfo->Inputs.Num(); ++SrcIndex) { const FNiagaraOpInOutInfo& InOutInfo = OpInfo->Inputs[SrcIndex]; UEdGraphPin* Pin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(InOutInfo.DataType), *InOutInfo.Name.ToString()); check(Pin); if(InOutInfo.FriendlyName.IsEmpty() == false) { Pin->PinFriendlyName = InOutInfo.FriendlyName; } Pin->bDefaultValueIsIgnored = false; Pin->bDefaultValueIsReadOnly = false; Pin->bNotConnectable = false; Pin->DefaultValue = InOutInfo.Default; Pin->AutogeneratedDefaultValue = InOutInfo.Default; Pin->PinToolTip = InOutInfo.Description.ToString(); } // Restore pins added by the user for (int32 SrcIndex = 0; SrcIndex < AddedPins.Num(); ++SrcIndex) { FAddedPinData& OldPinData = AddedPins[SrcIndex]; UEdGraphPin* Pin = CreatePin(EGPD_Input, OldPinData.PinType, OldPinData.PinName); check(Pin); Pin->bDefaultValueIsIgnored = false; Pin->bDefaultValueIsReadOnly = false; Pin->bNotConnectable = false; } // Create output pins from the op for (int32 OutIdx = 0; OutIdx < OpInfo->Outputs.Num(); ++OutIdx) { const FNiagaraOpInOutInfo& InOutInfo = OpInfo->Outputs[OutIdx]; UEdGraphPin* Pin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(InOutInfo.DataType), *InOutInfo.Name.ToString()); check(Pin); Pin->PinToolTip = InOutInfo.Description.ToString(); } if (OpInfo->bSupportsAddedInputs) { CreateAddPin(EGPD_Input); } } FText UNiagaraNodeOp::GetNodeTitle(ENodeTitleType::Type TitleType) const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (!OpInfo) { FString Strn = "Unknown"; return FText::FromString(Strn); } if (OpInfo && OpInfo->StaticVariableResolveFunction.IsBound() && OpInfo->bSupportsStaticResolution && bAllStatic) { FFormatNamedArguments Args; Args.Add(TEXT("OpName"), OpInfo->FriendlyName); FText Format = LOCTEXT("OpNodeNameStatic", "{OpName} (Static)"); return FText::Format(Format, Args); } return OpInfo->FriendlyName; } FText UNiagaraNodeOp::GetTooltipText()const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (!OpInfo) { FString Strn = "Unknown"; return FText::FromString(Strn); } if (OpInfo && OpInfo->StaticVariableResolveFunction.IsBound() && OpInfo->bSupportsStaticResolution && bAllStatic) { FFormatNamedArguments Args; Args.Add(TEXT("OpDesc"), OpInfo->Description); FText Format = LOCTEXT("OpNodeDescStatic", "{OpDesc}\r\n(To convert into a static version, connect all input pins to other static pins.)"); return FText::Format(Format, Args); } return OpInfo->Description; } FLinearColor UNiagaraNodeOp::GetNodeTitleColor() const { return GetDefault()->FunctionCallNodeTitleColor; } void UNiagaraNodeOp::PinTypeChanged(UEdGraphPin* Pin) { FName PinName = Pin->PinName; for(FAddedPinData& AddedPinData : AddedPins) { if(AddedPinData.PinName == PinName) { AddedPinData.PinType = Pin->PinType; break; } } Super::PinTypeChanged(Pin); } void UNiagaraNodeOp::OnPostSynchronizationInReallocatePins() { FPinCollectorArray InputPins; GetInputPins(InputPins); bool bOutputPinsNeedUpdate = false; for (int32 i = 0; i < InputPins.Num(); ++i) { UEdGraphPin* Pin = InputPins[i]; FEdGraphPinType StartType = Pin->PinType; HandleStaticInputPinUpgrade(Pin); // Type was changed... now make sure that we can fix up the output pin. if (StartType != Pin->PinType) { bOutputPinsNeedUpdate = true; } } HandleStaticOutputPinUpgrade(); } FNiagaraTypeDefinition UNiagaraNodeOp::ResolveCustomNumericType(const TArray& NonNumericInputs) const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (OpInfo && OpInfo->CustomNumericResolveFunction.IsBound()) { return OpInfo->CustomNumericResolveFunction.Execute(NonNumericInputs); } return FNiagaraTypeDefinition::GetGenericNumericDef(); } void UNiagaraNodeOp::Compile(FTranslator* Translator, TArray& Outputs) const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (!OpInfo) { FFormatNamedArguments Args; Args.Add(TEXT("OpName"), FText::FromName(OpName)); FText Format = LOCTEXT("Unknown opcode", "Unknown opcode on {OpName} node."); Translator->Error(FText::Format(Format, Args), this, nullptr); return; } int32 NumInputs = OpInfo->bSupportsAddedInputs ? Pins.Num() : OpInfo->Inputs.Num(); int32 NumOutputs = OpInfo->Outputs.Num(); TArray Inputs; bool bError = false; for (int32 i = 0; i < NumInputs; ++i) { UEdGraphPin *Pin = Pins[i]; if (Pin->Direction != EGPD_Input || IsAddPin(Pin)) { continue; } int32 CompiledInput = Translator->CompileInputPin(Pin); if (CompiledInput == INDEX_NONE) { bError = true; FFormatNamedArguments Args; Args.Add(TEXT("OpName"), GetNodeTitle(ENodeTitleType::FullTitle)); FText Format = LOCTEXT("InputErrorFormat", "Error compiling input on {OpName} node."); Translator->Error(FText::Format(Format, Args), this, Pin); } else if (i < OpInfo->Inputs.Num() && OpInfo->Inputs[i].DataType == FNiagaraTypeDefinition::GetGenericNumericDef()) { // Some nodes disallow integer or floating numeric input pins, so we guard against them here. // This will catch both implicitly and explicitly set pin types. // Currently this is for the Random Float/Integer and Seeded Random Float/Integer ops, but might be useful for others in the future. const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); FNiagaraTypeDefinition TypeDef = Schema->PinToTypeDefinition(Pin); if (TypeDef.IsFloatPrimitive() && !OpInfo->bNumericsCanBeFloats) { bError = true; FFormatNamedArguments Args; Args.Add(TEXT("OpName"), GetNodeTitle(ENodeTitleType::FullTitle)); FText Format = LOCTEXT("InputTypeErrorFormatFloat", "The {OpName} node cannot have float based numeric input pins."); Translator->Error(FText::Format(Format, Args), this, Pin); } else if (!TypeDef.IsFloatPrimitive() && !OpInfo->bNumericsCanBeIntegers) { bError = true; FFormatNamedArguments Args; Args.Add(TEXT("OpName"), GetNodeTitle(ENodeTitleType::FullTitle)); FText Format = LOCTEXT("InputTypeErrorFormatInt", "The {OpName} node cannot have integer based numeric input pins."); Translator->Error(FText::Format(Format, Args), this, Pin); } } Inputs.Add(CompiledInput); } Translator->Operation(this, Inputs, Outputs); } void UNiagaraNodeOp::PostLoad() { Super::PostLoad(); FName OriginalOpName = OpName; if (OpName == TEXT("Numeric::Cos")) { OpName = TEXT("Numeric::Cosine(Radians)"); } else if (OpName == TEXT("Numeric::Sin")) { OpName = TEXT("Numeric::Sine(Radians)"); } else if (OpName == TEXT("Numeric::Tan")) { OpName = TEXT("Numeric::Tangent(Radians)"); } else if (OpName == TEXT("Numeric::ASin")) { OpName = TEXT("Numeric::ArcSine(Radians)"); } else if (OpName == TEXT("Numeric::ACos")) { OpName = TEXT("Numeric::ArcCosine(Radians)"); } else if (OpName == TEXT("Numeric::ATan")) { OpName = TEXT("Numeric::ArcTangent(Radians)"); } else if (OpName == TEXT("Numeric::ATan2")) { OpName = TEXT("Numeric::ArcTangent2(Radians)"); } if (OpName != OriginalOpName) { UE_LOG(LogNiagaraEditor, Log, TEXT("OpNode: Converted %s to %s, Package: %s"), *OriginalOpName.ToString(), *OpName.ToString(), *GetOutermost()->GetName()); } const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); if (NiagaraVer < FNiagaraCustomVersion::ImproveLoadTimeFixupOfOpAddPins && AllowDynamicPins() && Pins.FindByPredicate([this](UEdGraphPin* Pin) { return IsAddPin(Pin); }) == nullptr) { // Add the pin directly here rather than calling allocate default pins to prevent the graph id from being invalidated // since adding the add pin doesn't change the compile behavior. We do modify here so that when running the resave // commandlet the package will be marked dirty, it will be ignored during regular post load. Modify(); CreateAddPin(EGPD_Input); } } bool UNiagaraNodeOp::RefreshFromExternalChanges() { // TODO - Leverage code in reallocate pins to determine if any pins have changed... ReallocatePins(); return true; } ENiagaraNumericOutputTypeSelectionMode UNiagaraNodeOp::GetNumericOutputTypeSelectionMode() const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (OpInfo) { return OpInfo->NumericOuputTypeSelectionMode; } else { return ENiagaraNumericOutputTypeSelectionMode::Largest; } } bool UNiagaraNodeOp::GenerateCompileHashForClassMembers(const UClass* InClass, FNiagaraCompileHashVisitor* InVisitor) const { if (InClass == UNiagaraNodeOp::StaticClass()) { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if(OpInfo) { InVisitor->UpdateReference(TEXT("OpInfo"), OpInfo); } return true; } else { return Super::GenerateCompileHashForClassMembers(InClass, InVisitor); } } bool UNiagaraNodeOp::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (!OpInfo) { return false; } if (OpInfo->AddedInputTypeRestrictions.Num() == 0) { return true; } auto FindPredicate = [this, InType](const FNiagaraTypeDefinition& PinType) { if (bAllStatic) return PinType.ToStaticDef() == InType; else return PinType == InType; }; return OpInfo->AddedInputTypeRestrictions.IndexOfByPredicate(FindPredicate) != INDEX_NONE; } void UNiagaraNodeOp::OnPinRemoved(UEdGraphPin* PinToRemove) { auto FindPredicate = [=](const FAddedPinData& PinData) { return PinData.PinName == PinToRemove->PinName && PinData.PinType == PinToRemove->PinType; }; AddedPins.RemoveAll(FindPredicate); ReallocatePins(); } bool UNiagaraNodeOp::AllowDynamicPins() const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); return OpInfo && OpInfo->bSupportsAddedInputs; } void UNiagaraNodeOp::OnNewTypedPinAdded(UEdGraphPin*& NewPin) { FName UniqueName = GetUniqueAdditionalPinName(); FAddedPinData PinData; PinData.PinType = NewPin->PinType; PinData.PinName = UniqueName; NewPin->PinName = UniqueName; AddedPins.Add(PinData); if (bAllStatic) { const UEdGraphSchema_Niagara* Schema = GetDefault(); FNiagaraTypeDefinition PinType = Schema->PinToTypeDefinition(NewPin); if (PinType.IsStatic() == false) NewPin->PinType = Schema->TypeDefinitionToPinType(PinType.ToStaticDef()); } } void UNiagaraNodeOp::OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) { TSet ExistingNames; for (const FAddedPinData& PinData : AddedPins) { ExistingNames.Add(PinData.PinName); } FName UniqueName = FNiagaraUtilities::GetUniqueName(*RenamedPin->PinName.ToString(), ExistingNames); FName OldPinName(*OldName); for (FAddedPinData& PinData : AddedPins) { if (PinData.PinName == OldPinName && PinData.PinType == RenamedPin->PinType) { PinData.PinName = UniqueName; RenamedPin->PinName = UniqueName; break; } } } bool UNiagaraNodeOp::CanRemovePin(const UEdGraphPin* Pin) const { if (!Pin || Pin->Direction != EGPD_Input || !Super::CanRemovePin(Pin)) { return false; } // check if the pin was added by the user, only those can be removed for (int32 SrcIndex = 0; SrcIndex < AddedPins.Num(); ++SrcIndex) { const FAddedPinData& PinData = AddedPins[SrcIndex]; if (Pin->PinType == PinData.PinType && Pin->PinName == PinData.PinName) { return true; } } return false; } FName UNiagaraNodeOp::GetUniqueAdditionalPinName() const { // count existing input pins FString Name; TSet ExistingNames; for (UEdGraphPin* Pin : Pins) { if (Pin->Direction == EEdGraphPinDirection::EGPD_Input && !IsAddPin(Pin)) { ExistingNames.Add(Pin->PinName); } } // create a new name based on the existing pins (A, B, ... AA, AB, ...) static FString Alphabet = TEXT("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); for (int32 Remaining = ExistingNames.Num(); Remaining > 0; Remaining = Remaining / 26) { int32 CharIndex = Remaining < 26 ? Remaining - 1 : Remaining % 26; Name = FString() + (*Alphabet)[CharIndex] + Name; } return FNiagaraUtilities::GetUniqueName(*Name, ExistingNames); } void UNiagaraNodeOp::BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive /*= true*/, bool bFilterForCompilation /*= true*/) const { if (bRecursive) { OutHistory.VisitInputPins(this, bFilterForCompilation); } if (!IsNodeEnabled() && OutHistory.GetIgnoreDisabled()) { RouteParameterMapAroundMe(OutHistory, bRecursive); return; } else { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (OpInfo && OpInfo->StaticVariableResolveFunction.IsBound()) { const UEdGraphSchema_Niagara* Schema = GetDefault(); TArray InputPins; GetInputPins(InputPins); bool bAllPinsStatic = true; UEdGraphPin* OutputPin = nullptr; { for (int32 PinIdx = 0; PinIdx < Pins.Num(); PinIdx++) { if (IsAddPin(Pins[PinIdx])) continue; FNiagaraTypeDefinition InputType = Schema->PinToTypeDefinition(Pins[PinIdx]); if (!InputType.IsStatic()) bAllPinsStatic = false; if (OutputPin == nullptr && Pins[PinIdx]->Direction == EEdGraphPinDirection::EGPD_Output) { OutputPin = Pins[PinIdx]; } } } if (bAllPinsStatic) { TArray Vars; for (int32 InputIdx = 0; InputIdx < InputPins.Num(); InputIdx++) { if (IsAddPin(InputPins[InputIdx])) continue; FNiagaraTypeDefinition InputType = Schema->PinToTypeDefinition(InputPins[InputIdx]); if (!InputType.IsStatic()) { return; } int32 Value = 0; OutHistory.SetConstantByStaticVariable(Value, InputPins[InputIdx]); Vars.Add(Value); } if (Vars.Num() > 0) { int32 Result = OpInfo->StaticVariableResolveFunction.Execute(Vars); int32 ConstantIdx = OutHistory.AddOrGetConstantFromValue(FString::FromInt(Result)); if (UNiagaraScript::LogCompileStaticVars > 0) { UE_LOG(LogNiagaraEditor, Log, TEXT("Inputs Static Node Op: %s"), *GetNodeTitle(ENodeTitleType::FullTitle).ToString()); for (int32 i = 0; i < Vars.Num(); i++) { UE_LOG(LogNiagaraEditor, Log, TEXT("[%d] %d"), i, Vars[i]); } UE_LOG(LogNiagaraEditor, Log, TEXT("Result: %d"), Result); } OutHistory.RegisterConstantPin(ConstantIdx, OutputPin); } } } } } void UNiagaraNodeOp::PinConnectionListChanged(UEdGraphPin* Pin) { Super::PinConnectionListChanged(Pin); FEdGraphPinType StartType = Pin->PinType; HandleStaticInputPinUpgrade(Pin); // Type was changed... now make sure that we can fix up the output pin. if (StartType != Pin->PinType) { HandleStaticOutputPinUpgrade(); } } FText UNiagaraNodeOp::GetCompactTitle() const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if(OpInfo == nullptr) { // this will effectively turn off compact mode return FText::GetEmpty(); } return OpInfo->CompactName; } bool UNiagaraNodeOp::ShouldShowPinNamesInCompactMode() { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if(OpInfo == nullptr) { return false; } return OpInfo->bShowPinNamesInCompactMode; } TOptional UNiagaraNodeOp::GetCompactModeFontSizeOverride() const { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if(OpInfo == nullptr) { return {}; } return OpInfo->CompactNameFontSizeOverride; } void UNiagaraNodeOp::HandleStaticInputPinUpgrade(UEdGraphPin* Pin) { const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (Pin && OpInfo && OpInfo->StaticVariableResolveFunction.IsBound() && OpInfo->bSupportsStaticResolution) { const UEdGraphSchema_Niagara* Schema = GetDefault(); FNiagaraVariable PinVar = Schema->PinToNiagaraVariable(Pin); if (Pin->Direction == EEdGraphPinDirection::EGPD_Input && !IsAddPin(Pin)) { // Handle linkage... bool bHandled = false; // this shouldn't specifically check for add pins as there can be other special pins like the wildcard pin // this will generally attempt to set the current input pin to the same type as the other's output pin. Ok for now, but might break in the future if (Pin->LinkedTo.Num() > 0 && !IsAddPin(Pin->LinkedTo[0])) { FNiagaraTypeDefinition LinkedPinType = Schema->PinToTypeDefinition(Pin->LinkedTo[0]); if(LinkedPinType.IsStatic()) { Pin->PinType = Schema->TypeDefinitionToPinType(LinkedPinType); } } else // Handle unlinkage... { // Downcast if the types don't match... if (OpInfo->bSupportsAddedInputs && OpInfo->Inputs.Num() > 0) { if (PinVar.GetType() != OpInfo->Inputs[0].DataType && OpInfo->Inputs[0].DataType != FNiagaraTypeDefinition::GetGenericNumericDef()) { Pin->PinType = Schema->TypeDefinitionToPinType(OpInfo->Inputs[0].DataType); } } else { for (int32 i = 0; i < OpInfo->Inputs.Num(); i++) { if (OpInfo->Inputs[i].Name == PinVar.GetName()) { if (PinVar.GetType() != OpInfo->Inputs[i].DataType && OpInfo->Inputs[0].DataType != FNiagaraTypeDefinition::GetGenericNumericDef()) { Pin->PinType = Schema->TypeDefinitionToPinType(OpInfo->Inputs[i].DataType); break; } break; } } } } } } } void UNiagaraNodeOp::HandleStaticOutputPinUpgrade() { const UEdGraphSchema_Niagara* Schema = GetDefault(); const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); if (OpInfo) { bAllStatic = false; bool bAllInputStatic = true; bool bAnyInputStatic = false; for (int32 i = 0; i < Pins.Num(); i++) { if (IsAddPin(Pins[i]) || Pins[i]->Direction != EEdGraphPinDirection::EGPD_Input) continue; FNiagaraTypeDefinition PinType = Schema->PinToTypeDefinition(Pins[i]); if (PinType.IsStatic()) { bAnyInputStatic = true; } else { bAllInputStatic = false; continue; } } if (OpInfo->StaticVariableResolveFunction.IsBound() && OpInfo->bSupportsStaticResolution) { // Synchronize the output pin either way... bool bAllOutputStatic = true; bool bAnyOutputStatic = false; for (int32 PinIdx = 0; PinIdx < Pins.Num(); PinIdx++) { if (Pins[PinIdx]->Direction == EEdGraphPinDirection::EGPD_Output) { FNiagaraVariable PinVar = Schema->PinToNiagaraVariable(Pins[PinIdx]); if (IsAddPin(Pins[PinIdx])) continue; if (!PinVar.GetType().IsStatic()) { bAllOutputStatic = false; } else { bAnyOutputStatic = true; } } } bool bCanAutoConvertOutputs = bAllInputStatic; for (int32 OutputIdx = 0; OutputIdx < OpInfo->Outputs.Num(); OutputIdx++) { if (!FNiagaraTypeRegistry::IsStaticPossible(OpInfo->Outputs[OutputIdx].DataType)) { bCanAutoConvertOutputs = false; } } if (bCanAutoConvertOutputs) { for (int32 PinIdx = 0; PinIdx < Pins.Num(); PinIdx++) { if (IsAddPin(Pins[PinIdx])) continue; if (Pins[PinIdx]->Direction == EEdGraphPinDirection::EGPD_Output) { FNiagaraTypeDefinition PinStaticType = Schema->PinToTypeDefinition(Pins[PinIdx]).ToStaticDef(); Pins[PinIdx]->PinType = Schema->TypeDefinitionToPinType(PinStaticType); } } bAllOutputStatic = true; } if (bAllOutputStatic && bAllInputStatic) { bAllStatic = true; NodeUpgradeMessage = FText(); } else if (bAnyOutputStatic && !bAllInputStatic) { NodeUpgradeMessage = LOCTEXT("AllStaticInvalidInput", "All inputs must be static to have static outputs. Static will not be preserved."); MarkNodeRequiresSynchronization(__FUNCTION__, true); } else if (bAnyInputStatic && !bAllOutputStatic) { NodeUpgradeMessage = LOCTEXT("AllStaticInvalidOutput", "All outputs must be static to have static inputs. Static will not be preserved."); MarkNodeRequiresSynchronization(__FUNCTION__, true); } } else if (bAllStatic) { NodeUpgradeMessage = LOCTEXT("AllStaticNotSupported", "This op doesn't fully support static outputs. Static will not be preserved."); MarkNodeRequiresSynchronization(__FUNCTION__, true); } } } #undef LOCTEXT_NAMESPACE