// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "MaterialEditorModule.h" #include "Materials/MaterialExpressionFunctionInput.h" #include "Materials/MaterialExpressionFunctionOutput.h" #include "Materials/MaterialExpressionStaticBoolParameter.h" #include "Materials/MaterialExpressionStaticBool.h" #include "Materials/MaterialExpressionStaticSwitch.h" #include "Materials/MaterialExpressionComment.h" #include "Materials/MaterialExpressionParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionFontSampleParameter.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionStaticSwitchParameter.h" #include "Materials/MaterialFunction.h" #include "MaterialEditorUtilities.h" #include "Toolkits/ToolkitManager.h" #include "MaterialExpressionClasses.h" #define LOCTEXT_NAMESPACE "MaterialEditorUtilities" DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditorUtilities, Log, All); UMaterialExpression* FMaterialEditorUtilities::CreateNewMaterialExpression(const class UEdGraph* Graph, UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { return MaterialEditor->CreateNewMaterialExpression(NewExpressionClass, NodePos, bAutoSelect, bAutoAssignResource); } return NULL; } UMaterialExpressionComment* FMaterialEditorUtilities::CreateNewMaterialExpressionComment(const class UEdGraph* Graph, const FVector2D& NodePos) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { return MaterialEditor->CreateNewMaterialExpressionComment(NodePos); } return NULL; } void FMaterialEditorUtilities::ForceRefreshExpressionPreviews(const class UEdGraph* Graph) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { MaterialEditor->ForceRefreshExpressionPreviews(); } } void FMaterialEditorUtilities::AddToSelection(const class UEdGraph* Graph, UMaterialExpression* Expression) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { MaterialEditor->AddToSelection(Expression); } } void FMaterialEditorUtilities::DeleteSelectedNodes(const class UEdGraph* Graph) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { MaterialEditor->DeleteSelectedNodes(); } } FString FMaterialEditorUtilities::GetOriginalObjectName(const class UEdGraph* Graph) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { return MaterialEditor->GetOriginalObjectName(); } return TEXT(""); } void FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(const class UEdGraph* Graph) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if (MaterialEditor.IsValid()) { MaterialEditor->UpdateMaterialAfterGraphChange(); } } bool FMaterialEditorUtilities::CanPasteNodes(const class UEdGraph* Graph) { bool bCanPaste = false; TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if(MaterialEditor.IsValid()) { bCanPaste = MaterialEditor->CanPasteNodes(); } return bCanPaste; } void FMaterialEditorUtilities::PasteNodesHere(class UEdGraph* Graph, const FVector2D& Location) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if(MaterialEditor.IsValid()) { MaterialEditor->PasteNodesHere(Location); } } int32 FMaterialEditorUtilities::GetNumberOfSelectedNodes(const class UEdGraph* Graph) { int32 SelectedNodes = 0; TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if(MaterialEditor.IsValid()) { SelectedNodes = MaterialEditor->GetNumberOfSelectedNodes(); } return SelectedNodes; } void FMaterialEditorUtilities::GetMaterialExpressionActions(FGraphActionMenuBuilder& ActionMenuBuilder, bool bMaterialFunction) { // TODO: Not sure if this is necessary/usable anymore // Get all menu extenders for this context menu from the material editor module /*IMaterialEditorModule& MaterialEditor = FModuleManager::GetModuleChecked( TEXT("MaterialEditor") ); TArray MenuExtenderDelegates = MaterialEditor.GetAllMaterialCanvasMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(MaterialEditorPtr.Pin()->GetToolkitCommands())); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders);*/ bool bUseUnsortedMenus = false; MaterialExpressionClasses* ExpressionClasses = MaterialExpressionClasses::Get(); if (bUseUnsortedMenus) { AddMaterialExpressionCategory(ActionMenuBuilder, TEXT(""), &ExpressionClasses->AllExpressionClasses, bMaterialFunction); } else { // Add Favourite expressions as a category const FText FavouritesCategory = LOCTEXT("FavoritesMenu", "Favorites"); AddMaterialExpressionCategory(ActionMenuBuilder, FavouritesCategory.ToString(), &ExpressionClasses->FavoriteExpressionClasses, bMaterialFunction); // Add each category to the menu for (int32 CategoryIndex = 0; CategoryIndex < ExpressionClasses->CategorizedExpressionClasses.Num(); ++CategoryIndex) { FCategorizedMaterialExpressionNode* CategoryNode = &(ExpressionClasses->CategorizedExpressionClasses[CategoryIndex]); AddMaterialExpressionCategory(ActionMenuBuilder, CategoryNode->CategoryName, &CategoryNode->MaterialExpressions, bMaterialFunction); } if (ExpressionClasses->UnassignedExpressionClasses.Num() > 0) { AddMaterialExpressionCategory(ActionMenuBuilder, TEXT(""), &ExpressionClasses->UnassignedExpressionClasses, bMaterialFunction); } } } bool FMaterialEditorUtilities::IsMaterialExpressionInFavorites(UMaterialExpression* InExpression) { return MaterialExpressionClasses::Get()->IsMaterialExpressionInFavorites(InExpression); } FMaterialRenderProxy* FMaterialEditorUtilities::GetExpressionPreview(const class UEdGraph* Graph, UMaterialExpression* InExpression) { FMaterialRenderProxy* ExpressionPreview = NULL; TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if(MaterialEditor.IsValid()) { ExpressionPreview = MaterialEditor->GetExpressionPreview(InExpression); } return ExpressionPreview; } void FMaterialEditorUtilities::UpdateSearchResults(const class UEdGraph* Graph) { TSharedPtr MaterialEditor = GetIMaterialEditorForObject(Graph); if(MaterialEditor.IsValid()) { MaterialEditor->UpdateSearch(false); } } ///////////////////////////////////////////////////// // Static functions moved from SMaterialEditorCanvas void FMaterialEditorUtilities::GetVisibleMaterialParameters(const UMaterial *Material, UMaterialInstance *MaterialInstance, TArray &VisibleExpressions) { VisibleExpressions.Empty(); TArray ProcessedExpressions; for(uint32 i = 0; i < MP_MAX; ++i) { FExpressionInput* ExpressionInput = ((UMaterial *)Material)->GetExpressionInputForProperty((EMaterialProperty)i); if(ExpressionInput) { GetVisibleMaterialParametersFromExpression(ExpressionInput->Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions); } } } bool FMaterialEditorUtilities::GetStaticSwitchExpressionValue(UMaterialInstance* MaterialInstance, UMaterialExpression *SwitchValueExpression, bool& OutValue, FGuid& OutExpressionID, const TArray* FunctionInputs) { // If switch value is a function input expression then we must recursively find the associated input expressions from the parent function/material to evaluate the value. UMaterialExpressionFunctionInput* FunctionInputExpression = Cast(SwitchValueExpression); if(FunctionInputExpression && FunctionInputExpression->InputType == FunctionInput_StaticBool) { // Get the FFunctionExpressionInput which stores information about the input node from the parent that this is linked to. const FFunctionExpressionInput* MatchingInput = FindInputById(FunctionInputExpression, *FunctionInputs); if (MatchingInput && (MatchingInput->Input.Expression || !FunctionInputExpression->bUsePreviewValueAsDefault)) { GetStaticSwitchExpressionValue(MaterialInstance, MatchingInput->Input.Expression, OutValue, OutExpressionID, FunctionInputs); } else { GetStaticSwitchExpressionValue(MaterialInstance, FunctionInputExpression->Preview.Expression, OutValue, OutExpressionID, FunctionInputs); } } // The expression can only be a static bool parameter when the current scope is not within a function if(SwitchValueExpression && SwitchValueExpression->Material != NULL) { UMaterialExpressionStaticBoolParameter* SwitchParamValue = Cast(SwitchValueExpression); if(SwitchParamValue) { MaterialInstance->GetStaticSwitchParameterValue(SwitchParamValue->ParameterName, OutValue, OutExpressionID); return true; } } UMaterialExpressionStaticBool* StaticSwitchValue = Cast(SwitchValueExpression); if(StaticSwitchValue) { OutValue = StaticSwitchValue->Value; return true; } return false; } bool FMaterialEditorUtilities::IsFunctionContainingSwitchExpressions(UMaterialFunction* MaterialFunction) { if (MaterialFunction) { TArray DependentFunctions; MaterialFunction->GetDependentFunctions(DependentFunctions); DependentFunctions.AddUnique(MaterialFunction); for (int32 FunctionIndex = 0; FunctionIndex < DependentFunctions.Num(); ++FunctionIndex) { UMaterialFunction* CurrentFunction = DependentFunctions[FunctionIndex]; for(int32 ExpressionIndex = 0; ExpressionIndex < CurrentFunction->FunctionExpressions.Num(); ++ExpressionIndex ) { UMaterialExpressionStaticSwitch* StaticSwitchExpression = Cast(CurrentFunction->FunctionExpressions[ExpressionIndex]); if (StaticSwitchExpression) { return true; } } } } return false; } const FFunctionExpressionInput* FMaterialEditorUtilities::FindInputById(const UMaterialExpressionFunctionInput* InputExpression, const TArray& Inputs) { for (int32 InputIndex = 0; InputIndex < Inputs.Num(); InputIndex++) { const FFunctionExpressionInput& CurrentInput = Inputs[InputIndex]; if (CurrentInput.ExpressionInputId == InputExpression->Id && CurrentInput.ExpressionInput->GetOuter() == InputExpression->GetOuter()) { return &CurrentInput; } } return NULL; } void FMaterialEditorUtilities::InitExpressions(UMaterial* Material) { FString ParmName; Material->EditorComments.Empty(); Material->Expressions.Empty(); TArray ChildObjects; GetObjectsWithOuter(Material, ChildObjects, /*bIncludeNestedObjects=*/false); for ( int32 ChildIdx = 0; ChildIdx < ChildObjects.Num(); ++ChildIdx ) { UMaterialExpression* MaterialExpression = Cast(ChildObjects[ChildIdx]); if( MaterialExpression != NULL && !MaterialExpression->IsPendingKill() ) { // Comment expressions are stored in a separate list. if ( MaterialExpression->IsA( UMaterialExpressionComment::StaticClass() ) ) { Material->EditorComments.Add( static_cast(MaterialExpression) ); } else { Material->Expressions.Add( MaterialExpression ); } } } Material->BuildEditorParameterList(); // Propagate RF_Transactional to all referenced material expressions. Material->SetFlags( RF_Transactional ); for( int32 MaterialExpressionIndex = 0 ; MaterialExpressionIndex < Material->Expressions.Num() ; ++MaterialExpressionIndex ) { UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ]; if(MaterialExpression) { MaterialExpression->SetFlags( RF_Transactional ); } } for( int32 MaterialExpressionIndex = 0 ; MaterialExpressionIndex < Material->EditorComments.Num() ; ++MaterialExpressionIndex ) { UMaterialExpressionComment* Comment = Material->EditorComments[ MaterialExpressionIndex ]; Comment->SetFlags( RF_Transactional ); } } /////////// // private void FMaterialEditorUtilities::GetVisibleMaterialParametersFromExpression(UMaterialExpression *MaterialExpression, UMaterialInstance *MaterialInstance, TArray &VisibleExpressions, TArray &ProcessedExpressions, const TArray* FunctionInputs, TArray* VisibleFunctionInputExpressions) { if(!MaterialExpression) { return; } check(MaterialInstance); //don't allow re-entrant expressions to continue if (ProcessedExpressions.Contains(MaterialExpression)) { return; } ProcessedExpressions.Push(MaterialExpression); if(MaterialExpression->Material != NULL) { // if it's a material parameter it must be visible so add it to the map UMaterialExpressionParameter *Param = Cast( MaterialExpression ); UMaterialExpressionTextureSampleParameter *TexParam = Cast( MaterialExpression ); UMaterialExpressionFontSampleParameter *FontParam = Cast( MaterialExpression ); if( Param ) { VisibleExpressions.AddUnique(Param->ExpressionGUID); UMaterialExpressionScalarParameter *ScalarParam = Cast( MaterialExpression ); UMaterialExpressionVectorParameter *VectorParam = Cast( MaterialExpression ); TArray Names; TArray Ids; if( ScalarParam ) { MaterialInstance->GetMaterial()->GetAllScalarParameterNames( Names, Ids ); for( int32 i = 0; i < Names.Num(); i++ ) { if( Names[i] == ScalarParam->ParameterName ) { VisibleExpressions.AddUnique( Ids[ i ] ); } } } else if ( VectorParam ) { MaterialInstance->GetMaterial()->GetAllVectorParameterNames( Names, Ids ); for( int32 i = 0; i < Names.Num(); i++ ) { if( Names[i] == VectorParam->ParameterName ) { VisibleExpressions.AddUnique( Ids[ i ] ); } } } } else if(TexParam) { VisibleExpressions.AddUnique( TexParam->ExpressionGUID ); TArray Names; TArray Ids; MaterialInstance->GetMaterial()->GetAllTextureParameterNames( Names, Ids ); for( int32 i = 0; i < Names.Num(); i++ ) { if( Names[i] == TexParam->ParameterName ) { VisibleExpressions.AddUnique( Ids[ i ] ); } } } else if(FontParam) { VisibleExpressions.AddUnique( FontParam->ExpressionGUID ); TArray Names; TArray Ids; MaterialInstance->GetMaterial()->GetAllFontParameterNames( Names, Ids ); for( int32 i = 0; i < Names.Num(); i++ ) { if( Names[i] == FontParam->ParameterName ) { VisibleExpressions.AddUnique( Ids[ i ] ); } } } } UMaterialExpressionFunctionInput* InputExpression = Cast(MaterialExpression); if(InputExpression) { VisibleFunctionInputExpressions->AddUnique(InputExpression->Id); } // check if it's a switch expression and branch according to its value UMaterialExpressionStaticSwitchParameter* StaticSwitchParamExpression = Cast(MaterialExpression); UMaterialExpressionStaticSwitch* StaticSwitchExpression = Cast(MaterialExpression); UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast(MaterialExpression); if(StaticSwitchParamExpression) { bool Value = false; FGuid ExpressionID; MaterialInstance->GetStaticSwitchParameterValue(StaticSwitchParamExpression->ParameterName, Value, ExpressionID); VisibleExpressions.AddUnique(ExpressionID); if(Value) { GetVisibleMaterialParametersFromExpression(StaticSwitchParamExpression->A.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions); } else { GetVisibleMaterialParametersFromExpression(StaticSwitchParamExpression->B.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions); } } else if(StaticSwitchExpression) { bool bValue = StaticSwitchExpression->DefaultValue; FGuid ExpressionID; if(StaticSwitchExpression->Value.Expression) { GetStaticSwitchExpressionValue(MaterialInstance, StaticSwitchExpression->Value.Expression, bValue, ExpressionID, FunctionInputs ); // Flag the switch value as a visible expression UMaterialExpressionFunctionInput* FunctionInputExpression = Cast(StaticSwitchExpression->Value.Expression); if(FunctionInputExpression) { VisibleFunctionInputExpressions->AddUnique(FunctionInputExpression->Id); } else if (StaticSwitchExpression && StaticSwitchExpression->Material != NULL) { if(ExpressionID.IsValid()) { VisibleExpressions.AddUnique(ExpressionID); } } } if(bValue) { GetVisibleMaterialParametersFromExpression(StaticSwitchExpression->A.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, FunctionInputs, VisibleFunctionInputExpressions); } else { GetVisibleMaterialParametersFromExpression(StaticSwitchExpression->B.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, FunctionInputs, VisibleFunctionInputExpressions); } } else if(FunctionCallExpression) { // Only process functions if any of the dependent functions contain static switches that could potentially cull the material parameters. if(IsFunctionContainingSwitchExpressions(FunctionCallExpression->MaterialFunction)) { // Pass an accumalated array of FFunctionExpressionInput so that input expressions can be matched to the input from the parent. TArray VisibleFunctionInputs; TArray FunctionCallInputs = FunctionCallExpression->FunctionInputs; if(FunctionInputs) { FunctionCallInputs.Append(*FunctionInputs); } for(int32 FunctionOutputIndex = 0; FunctionOutputIndex < FunctionCallExpression->FunctionOutputs.Num(); FunctionOutputIndex++) { // Recurse material functions. Returns an array of input ids that are visible and have not been culled by static switches. GetVisibleMaterialParametersFromExpression( FunctionCallExpression->FunctionOutputs[FunctionOutputIndex].ExpressionOutput->A.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, &FunctionCallInputs, &VisibleFunctionInputs); } // Parse children of function call inputs that have not been culled for(int32 ExpressionInputIndex = 0; ExpressionInputIndex < FunctionCallExpression->FunctionInputs.Num(); ExpressionInputIndex++) { const FFunctionExpressionInput& Input = FunctionCallExpression->FunctionInputs[ExpressionInputIndex]; if(VisibleFunctionInputs.Contains(Input.ExpressionInputId)) { // Retrieve the expression input and then start parsing its children GetVisibleMaterialParametersFromExpression(Input.Input.Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, FunctionInputs, VisibleFunctionInputExpressions ); } } } else { const TArray& ExpressionInputs = MaterialExpression->GetInputs(); for(int32 ExpressionInputIndex = 0; ExpressionInputIndex < ExpressionInputs.Num(); ExpressionInputIndex++) { //retrieve the expression input and then start parsing its children FExpressionInput* Input = ExpressionInputs[ExpressionInputIndex]; GetVisibleMaterialParametersFromExpression(Input->Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, FunctionInputs, VisibleFunctionInputExpressions); } } } else { const TArray& ExpressionInputs = MaterialExpression->GetInputs(); for(int32 ExpressionInputIndex = 0; ExpressionInputIndex < ExpressionInputs.Num(); ExpressionInputIndex++) { //retrieve the expression input and then start parsing its children FExpressionInput* Input = ExpressionInputs[ExpressionInputIndex]; GetVisibleMaterialParametersFromExpression(Input->Expression, MaterialInstance, VisibleExpressions, ProcessedExpressions, FunctionInputs, VisibleFunctionInputExpressions); } } UMaterialExpression* TopExpression = ProcessedExpressions.Pop(); //ensure that the top of the stack matches what we expect (the same as MaterialExpression) check(MaterialExpression == TopExpression); } TSharedPtr FMaterialEditorUtilities::GetIMaterialEditorForObject(const UObject* ObjectToFocusOn) { check(ObjectToFocusOn); // Find the associated Material UMaterial* Material = Cast(ObjectToFocusOn->GetOuter()); TSharedPtr MaterialEditor; if (Material != NULL) { TSharedPtr< IToolkit > FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(Material); if (FoundAssetEditor.IsValid()) { MaterialEditor = StaticCastSharedPtr(FoundAssetEditor); } } return MaterialEditor; } void FMaterialEditorUtilities::AddMaterialExpressionCategory(FGraphActionMenuBuilder& ActionMenuBuilder, FString CategoryName, TArray* MaterialExpressions, bool bMaterialFunction) { // Get type of dragged pin uint32 FromPinType = 0; if (ActionMenuBuilder.FromPin) { FromPinType = UMaterialGraphSchema::GetMaterialValueType(ActionMenuBuilder.FromPin); } for (int32 Index = 0; Index < MaterialExpressions->Num(); ++Index) { const FMaterialExpression& MaterialExpression = (*MaterialExpressions)[Index]; if (IsAllowedExpressionType(MaterialExpression.MaterialClass, bMaterialFunction)) { if (!ActionMenuBuilder.FromPin || HasCompatibleConnection(MaterialExpression.MaterialClass, FromPinType, ActionMenuBuilder.FromPin->Direction, bMaterialFunction)) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("Name"), FText::FromString( *MaterialExpression.Name )); const FText ToolTip = FText::Format( LOCTEXT( "NewMaterialExpressionTooltip", "Adds a {Name} node here" ), Arguments ); TSharedPtr NewNodeAction(new FMaterialGraphSchemaAction_NewNode( CategoryName, FText::FromString(MaterialExpression.Name), ToolTip.ToString(), 0)); ActionMenuBuilder.AddAction(NewNodeAction); NewNodeAction->MaterialExpressionClass = MaterialExpression.MaterialClass; NewNodeAction->Keywords = CastChecked(MaterialExpression.MaterialClass->GetDefaultObject())->GetKeywords(); } } } } bool FMaterialEditorUtilities::HasCompatibleConnection(UClass* ExpressionClass, uint32 TestType, EEdGraphPinDirection TestDirection, bool bMaterialFunction) { if (TestType != 0) { UMaterialExpression* DefaultExpression = CastChecked(ExpressionClass->GetDefaultObject()); if (TestDirection == EGPD_Output) { int32 NumInputs = DefaultExpression->GetInputs().Num(); for (int32 Index = 0; Index < NumInputs; ++Index) { uint32 InputType = DefaultExpression->GetInputType(Index); if (CanConnectMaterialValueTypes(InputType, TestType)) { return true; } } } else { int32 NumOutputs = DefaultExpression->GetOutputs().Num(); for (int32 Index = 0; Index < NumOutputs; ++Index) { uint32 OutputType = DefaultExpression->GetOutputType(Index); if (CanConnectMaterialValueTypes(TestType, OutputType)) { return true; } } } if (bMaterialFunction) { // Specific test as Default object won't have texture input if (ExpressionClass == UMaterialExpressionTextureSample::StaticClass() && TestType & MCT_Texture && TestDirection == EGPD_Output) { return true; } // Always allow creation of new inputs as they can take any type else if (ExpressionClass == UMaterialExpressionFunctionInput::StaticClass()) { return true; } // Allow creation of outputs for floats and material attributes else if (ExpressionClass == UMaterialExpressionFunctionOutput::StaticClass() && TestType & (MCT_Float|MCT_MaterialAttributes)) { return true; } } } return false; } #undef LOCTEXT_NAMESPACE