// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_EaseFunction.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNodeUtils.h" #include "EdGraphSchema_K2.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "Framework/Commands/UIAction.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "K2Node_CallFunction.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompiler.h" #include "Math/Color.h" #include "Misc/AssertionMacros.h" #include "ScopedTransaction.h" #include "Styling/AppStyle.h" #include "Templates/Casts.h" #include "ToolMenu.h" #include "ToolMenuSection.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/ReflectedTypeAccessors.h" #include "UObject/UnrealNames.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #define LOCTEXT_NAMESPACE "K2Node_EaseFunction" struct FEaseFunctionNodeHelper { static const FName EaseFuncPinName; static const FName AlphaPinName; static const FName APinFloatName; static const FName BPinFloatName; static const FName ResultPinName; static const FName BlendExpPinName; static const FName StepsPinName; static const FName ShortestPathPinName; static const FName GetEaseFuncPinName() { return EaseFuncPinName; } static const FName GetAlphaPinName() { return AlphaPinName; } static const FName GetAPinName() { return APinFloatName; } static const FName GetBPinName() { return BPinFloatName; } static const FName GetResultPinName() { return ResultPinName; } static const FName GetBlendExpPinName() { return BlendExpPinName; } static const FName GetStepsPinName() { return StepsPinName; } static const FName GetShortestPathPinName() { return ShortestPathPinName; } }; const FName FEaseFunctionNodeHelper::EaseFuncPinName(TEXT("Function")); const FName FEaseFunctionNodeHelper::AlphaPinName(TEXT("Alpha")); const FName FEaseFunctionNodeHelper::APinFloatName(TEXT("A")); const FName FEaseFunctionNodeHelper::BPinFloatName(TEXT("B")); const FName FEaseFunctionNodeHelper::ResultPinName(TEXT("Result")); const FName FEaseFunctionNodeHelper::BlendExpPinName(TEXT("BlendExp")); const FName FEaseFunctionNodeHelper::StepsPinName(TEXT("Steps")); const FName FEaseFunctionNodeHelper::ShortestPathPinName(TEXT("ShortestPath")); UK2Node_EaseFunction::UK2Node_EaseFunction(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , CachedEaseFuncPin(nullptr) { NodeTooltip = LOCTEXT("NodeTooltip", "Interpolates from value A to value B using a user specified easing function"); EaseFunctionName = NAME_None; } void UK2Node_EaseFunction::AllocateDefaultPins() { Super::AllocateDefaultPins(); const UEdGraphSchema_K2* K2Schema = GetDefault(); // Add the first pin representing all available easing functions. If EEasingFunc changes its name this will fail a runtime check! UEnum* EaseFuncEnum = StaticEnum(); check(EaseFuncEnum); CachedEaseFuncPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, EaseFuncEnum, FEaseFunctionNodeHelper::GetEaseFuncPinName()); SetPinToolTip(*CachedEaseFuncPin, LOCTEXT("EaseFunsPinDescription", "Specifies the desired ease function to be applied. If connected no customization is possible.")); // Make sure that the default value is set correctly if none has been set K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(CachedEaseFuncPin); UEdGraphPin* AlphaPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double, FEaseFunctionNodeHelper::GetAlphaPinName()); SetPinToolTip(*AlphaPin, LOCTEXT("AlphaPinTooltip", "Alpha value used to specify the easing in time.")); // Add wildcard pins for A, B and the return Pin UEdGraphPin* APin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, FEaseFunctionNodeHelper::GetAPinName()); SetPinToolTip(*APin, LOCTEXT("APinDescription", "Easing start value")); UEdGraphPin* BPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, FEaseFunctionNodeHelper::GetBPinName()); SetPinToolTip(*BPin, LOCTEXT("BPinDescription", "Easing end value")); UEdGraphPin* ResultPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, FEaseFunctionNodeHelper::GetResultPinName()); SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "Easing result value")); UEdGraphPin* ShortestPathPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, FEaseFunctionNodeHelper::GetShortestPathPinName()); SetPinToolTip(*ShortestPathPin, LOCTEXT("ShortestPathPinTooltip", "When interpolating the shortest path should be taken.")); K2Schema->SetPinAutogeneratedDefaultValue(ShortestPathPin, TEXT("true")); UEdGraphPin* BlendExpPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double, FEaseFunctionNodeHelper::GetBlendExpPinName()); SetPinToolTip(*BlendExpPin, LOCTEXT("BlendExpPinDescription", "Blend Exponent for basic ease functions")); K2Schema->SetPinAutogeneratedDefaultValue(BlendExpPin, TEXT("2.0")); UEdGraphPin* StepsPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, FEaseFunctionNodeHelper::GetStepsPinName()); SetPinToolTip(*StepsPin, LOCTEXT("StepsPinDescription", "Number of steps required to go from A to B")); K2Schema->SetPinAutogeneratedDefaultValue(StepsPin, TEXT("2")); GenerateExtraPins(); RefreshPinVisibility(); } FText UK2Node_EaseFunction::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("EaseFunction_Title", "Ease"); } FText UK2Node_EaseFunction::GetTooltipText() const { return NodeTooltip; } FSlateIcon UK2Node_EaseFunction::GetIconAndTint(FLinearColor& OutColor) const { // The function icon seams the best choice! OutColor = GetNodeTitleColor(); static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.FunctionIcon"); return Icon; } void UK2Node_EaseFunction::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const { MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString(); UEdGraphSchema_K2 const* const K2Schema = Cast(GetSchema()); if (K2Schema != nullptr) { MutatablePin.PinToolTip += TEXT(" "); MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString(); } MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString(); } void UK2Node_EaseFunction::NotifyPinConnectionListChanged(UEdGraphPin* Pin) { Super::NotifyPinConnectionListChanged(Pin); const auto EaseFuncPin = GetEaseFuncPin(); if (Pin == EaseFuncPin) { RefreshPinVisibility(); GetGraph()->NotifyGraphChanged(); } else { PinTypeChanged(Pin); } } void UK2Node_EaseFunction::PinDefaultValueChanged(UEdGraphPin* Pin) { const auto EaseFuncPin = GetEaseFuncPin(); if (Pin == EaseFuncPin ) { RefreshPinVisibility(); GetGraph()->NotifyGraphChanged(); } } void UK2Node_EaseFunction::PostReconstructNode() { Super::PostReconstructNode(); // Check in which state we are at the moment RefreshPinVisibility(); // Find a pin that has connections to use to jumpstart the wildcard process for (int32 PinIndex = 0; PinIndex < Pins.Num(); ++PinIndex) { if (Pins[PinIndex]->PinName == FEaseFunctionNodeHelper::GetAPinName() || Pins[PinIndex]->PinName == FEaseFunctionNodeHelper::GetBPinName() || Pins[PinIndex]->PinName == FEaseFunctionNodeHelper::GetResultPinName()) { // Take default pin values into account in case we can securly convert the string default value into a PinType // this is currently not the case but could be considered in the future. if (Pins[PinIndex]->LinkedTo.Num() > 0) { PinTypeChanged(Pins[PinIndex]); break; } } } GenerateExtraPins(); } bool UK2Node_EaseFunction::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { // Check the pin name and see if it matches on of our three base pins if (MyPin->PinName == FEaseFunctionNodeHelper::GetAPinName() || MyPin->PinName == FEaseFunctionNodeHelper::GetBPinName() || MyPin->PinName == FEaseFunctionNodeHelper::GetResultPinName()) { const bool bConnectionOk = ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real || ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && OtherPin->PinType.PinSubCategoryObject.IsValid() && ( OtherPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Vector") || OtherPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Rotator") || OtherPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Transform") ) ) ) && !OtherPin->PinType.IsContainer(); if (!bConnectionOk) { OutReason = LOCTEXT("PinConnectionDisallowed", "Pin type is not supported by function.").ToString(); return true; } } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } void UK2Node_EaseFunction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_EaseFunction::GetMenuCategory() const { static FNodeTextCache CachedCategory; if (CachedCategory.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Math, LOCTEXT("InterpCategory", "Interpolation")), this); } return CachedCategory; } void UK2Node_EaseFunction::ChangePinType(UEdGraphPin* Pin) { PinTypeChanged(Pin); } void UK2Node_EaseFunction::PinTypeChanged(UEdGraphPin* Pin) { bool bChanged = false; if (Pin->PinName == FEaseFunctionNodeHelper::GetAPinName() || Pin->PinName == FEaseFunctionNodeHelper::GetBPinName() || Pin->PinName == FEaseFunctionNodeHelper::GetResultPinName()) { // Get pin refs UEdGraphPin* APin = FindPin(FEaseFunctionNodeHelper::GetAPinName()); UEdGraphPin* BPin = FindPin(FEaseFunctionNodeHelper::GetBPinName()); UEdGraphPin* ResultPin = FindPin(FEaseFunctionNodeHelper::GetResultPinName()); // Propagate the type change or reset to wildcard PinType if (Pin->LinkedTo.Num() > 0) { UEdGraphPin* InstigatorPin = Pin->LinkedTo[0]; bChanged |= UpdatePin(APin, InstigatorPin); bChanged |= UpdatePin(BPin, InstigatorPin); bChanged |= UpdatePin(ResultPin, InstigatorPin); if (bChanged) { // Just in case we switch to an invalid function clean it first EaseFunctionName = NAME_None; // Generate the right function name if (InstigatorPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real) { EaseFunctionName = TEXT("Ease"); } else if (InstigatorPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) { if (InstigatorPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Vector")) { EaseFunctionName = TEXT("VEase"); } else if (InstigatorPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Rotator")) { EaseFunctionName = TEXT("REase"); } else if (InstigatorPin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Transform")) { EaseFunctionName = TEXT("TEase"); } } } } else { if (APin->GetDefaultAsString().IsEmpty() && APin->LinkedTo.Num() == 0 && BPin->GetDefaultAsString().IsEmpty() && BPin->LinkedTo.Num() == 0 && ResultPin->LinkedTo.Num() == 0) { // Restore wild card pin APin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; APin->PinType.PinSubCategory = NAME_None; APin->PinType.PinSubCategoryObject = nullptr; // Propagate change UpdatePin(BPin, APin); UpdatePin(ResultPin, APin); // Make sure the function name is nulled out EaseFunctionName = TEXT(""); bChanged = true; } } // Pin connections and data changed in some way if (bChanged) { SetPinToolTip(*APin, LOCTEXT("APinDescription", "Easing start value")); SetPinToolTip(*BPin, LOCTEXT("BPinDescription", "Easing end value")); SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "Easing result value")); // Let our subclasses generate some pins if required, this way we can add any aditional pins required by some types for examples GenerateExtraPins(); // Let the graph know to refresh GetGraph()->NotifyGraphChanged(); UBlueprint* Blueprint = GetBlueprint(); if (!Blueprint->bBeingCompiled) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); Blueprint->BroadcastChanged(); } } } Super::PinTypeChanged(Pin); } bool UK2Node_EaseFunction::UpdatePin(UEdGraphPin* MyPin, UEdGraphPin* OtherPin) { // If the data type changed break links first if (MyPin->PinType != OtherPin->PinType) { MyPin->PinType = OtherPin->PinType; return true; } return false; } void UK2Node_EaseFunction::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); /** At the end of this, the UK2Node_EaseFunction will not be a part of the Blueprint, it merely handles connecting the other nodes into the Blueprint. */ UFunction* Function = UKismetMathLibrary::StaticClass()->FindFunctionByName(EaseFunctionName); if (Function == NULL) { CompilerContext.MessageLog.Error(*LOCTEXT("InvalidFunctionName", "BaseAsyncTask: Type not supported or not initialized. @@").ToString(), this); return; } const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); // The call function does all the real work, each child class implementing easing for a given type provides // the name of the desired function UK2Node_CallFunction* CallFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallFunction->SetFromFunction(Function); CallFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFunction, this); // Move the ease function and the alpha connections from us to the call function CompilerContext.MovePinLinksToIntermediate(*FindPin(FEaseFunctionNodeHelper::GetEaseFuncPinName()), *CallFunction->FindPin(TEXT("EasingFunc"))); CompilerContext.MovePinLinksToIntermediate(*FindPin(FEaseFunctionNodeHelper::GetAlphaPinName()), *CallFunction->FindPin(TEXT("Alpha"))); // Move base connections to the call function's connections CompilerContext.MovePinLinksToIntermediate(*FindPin(FEaseFunctionNodeHelper::GetAPinName()), *CallFunction->FindPin(TEXT("A"))); CompilerContext.MovePinLinksToIntermediate(*FindPin(FEaseFunctionNodeHelper::GetBPinName()), *CallFunction->FindPin(TEXT("B"))); CompilerContext.MovePinLinksToIntermediate(*FindPin(FEaseFunctionNodeHelper::GetResultPinName()), *CallFunction->GetReturnValuePin()); // Now move the custom pins to their new locations UEdGraphPin* ShortestPathPin = FindPinChecked(FEaseFunctionNodeHelper::GetShortestPathPinName()); if (!ShortestPathPin->bHidden) { CompilerContext.MovePinLinksToIntermediate(*ShortestPathPin, *CallFunction->FindPinChecked(TEXT("bShortestPath"))); } UEdGraphPin* BlendExpPin = FindPinChecked(FEaseFunctionNodeHelper::GetBlendExpPinName()); if (!BlendExpPin->bHidden) { CompilerContext.MovePinLinksToIntermediate(*BlendExpPin, *CallFunction->FindPinChecked(FEaseFunctionNodeHelper::GetBlendExpPinName())); } UEdGraphPin* StepsPin = FindPinChecked(FEaseFunctionNodeHelper::GetStepsPinName()); if (!StepsPin->bHidden) { CompilerContext.MovePinLinksToIntermediate(*StepsPin, *CallFunction->FindPinChecked(FEaseFunctionNodeHelper::GetStepsPinName())); } // Cleanup links to ourself and we are done! BreakAllNodeLinks(); } UEdGraphPin* UK2Node_EaseFunction::GetEaseFuncPin() const { if (!CachedEaseFuncPin) { const_cast(this)->CachedEaseFuncPin = FindPinChecked(FEaseFunctionNodeHelper::GetEaseFuncPinName()); } return CachedEaseFuncPin; } void UK2Node_EaseFunction::RefreshPinVisibility() { const auto EaseFuncPin = GetEaseFuncPin(); UEnum * Enum = StaticEnum(); check(Enum != NULL); const int64 NewEasingFunc = CanCustomizeCurve() ? Enum->GetValueByName(*EaseFuncPin->DefaultValue) : -1; // Early exit in case no changes are required const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); UEdGraphPin* BlendExpPin = FindPinChecked(FEaseFunctionNodeHelper::GetBlendExpPinName()); if (NewEasingFunc == -1 || NewEasingFunc == EEasingFunc::EaseIn || NewEasingFunc == EEasingFunc::EaseOut || NewEasingFunc == EEasingFunc::EaseInOut) { // Show the BlendExpPin BlendExpPin->bHidden = false; } else { // Hide the BlendExpPin: BlendExpPin->BreakAllPinLinks(); BlendExpPin->bHidden = true; } UEdGraphPin* StepsPin = FindPinChecked(FEaseFunctionNodeHelper::GetStepsPinName()); if (NewEasingFunc == -1 || NewEasingFunc == EEasingFunc::Step) { // Show the Steps pin: StepsPin->bHidden = false; } else { // Hide the Steps pin: StepsPin->BreakAllPinLinks(); StepsPin->bHidden = true; } } void UK2Node_EaseFunction::GenerateExtraPins() { // Add pins based on the pin type const UEdGraphPin* APin = FindPinChecked(FEaseFunctionNodeHelper::GetAPinName()); const bool bIsRotator = APin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && APin->PinType.PinSubCategoryObject.Get()->GetName() == TEXT("Rotator"); UEdGraphPin* ShortestPathPin = FindPinChecked(FEaseFunctionNodeHelper::GetShortestPathPinName()); if (bIsRotator) { ShortestPathPin->bHidden = false; } else { ShortestPathPin->BreakAllPinLinks(); ShortestPathPin->bHidden = true; } } void UK2Node_EaseFunction::ResetToWildcards() { FScopedTransaction Transaction(LOCTEXT("ResetToDefaultsTx", "ResetToDefaults")); Modify(); // Get pin refs UEdGraphPin* APin = FindPin(FEaseFunctionNodeHelper::GetAPinName()); UEdGraphPin* BPin = FindPin(FEaseFunctionNodeHelper::GetBPinName()); UEdGraphPin* ResultPin = FindPin(FEaseFunctionNodeHelper::GetResultPinName()); // Set all to defaults and break links APin->DefaultValue = TEXT(""); BPin->DefaultValue = TEXT(""); APin->BreakAllPinLinks(); BPin->BreakAllPinLinks(); ResultPin->BreakAllPinLinks(); // Do the rest of the work, we will not recompile because the wildcard pins will prevent it PinTypeChanged(APin); } void UK2Node_EaseFunction::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const { Super::GetNodeContextMenuActions(Menu, Context); if (!Context->bIsDebugging) { if (Context->Pin == NULL) { { FToolMenuSection& Section = Menu->AddSection("UK2Node_EaseFunction", LOCTEXT("ContextMenuHeader", "Ease")); Section.AddMenuEntry( "AddPin", LOCTEXT("AddPin", "Reset to Wildcards"), LOCTEXT("AddPinTooltip", "Resets A, B and Results pins to its default wildcard state"), FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UK2Node_EaseFunction::ResetToWildcards) ) ); } } } } #undef LOCTEXT_NAMESPACE