// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "MaterialEditorModule.h" #include "MaterialGraph/MaterialGraphNode_Comment.h" #include "Materials/MaterialExpressionBreakMaterialAttributes.h" #include "Materials/MaterialExpressionCollectionParameter.h" #include "Materials/MaterialExpressionComment.h" #include "Materials/MaterialExpressionComponentMask.h" #include "Materials/MaterialExpressionConstant.h" #include "Materials/MaterialExpressionConstant2Vector.h" #include "Materials/MaterialExpressionConstant3Vector.h" #include "Materials/MaterialExpressionConstant4Vector.h" #include "Materials/MaterialExpressionDynamicParameter.h" #include "Materials/MaterialExpressionFontSampleParameter.h" #include "Materials/MaterialExpressionFunctionInput.h" #include "Materials/MaterialExpressionFunctionOutput.h" #include "Materials/MaterialExpressionParameter.h" #include "Materials/MaterialExpressionParticleSubUV.h" #include "Materials/MaterialExpressionRotateAboutAxis.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionStaticComponentMaskParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionTextureObject.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Materials/MaterialExpressionTextureSampleParameterCube.h" #include "Materials/MaterialExpressionTextureSampleParameterSubUV.h" #include "Materials/MaterialExpressionTransformPosition.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialFunction.h" #include "Materials/MaterialParameterCollection.h" #include "MaterialEditorActions.h" #include "MaterialExpressionClasses.h" #include "MaterialCompiler.h" #include "EditorSupportDelegates.h" #include "Toolkits/IToolkitHost.h" #include "Editor/EditorWidgets/Public/EditorWidgets.h" #include "AssetRegistryModule.h" #include "AssetToolsModule.h" #include "SMaterialEditorViewport.h" #include "SMaterialEditorTitleBar.h" #include "PreviewScene.h" #include "ScopedTransaction.h" #include "BusyCursor.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "Editor/PropertyEditor/Public/IDetailsView.h" #include "MaterialEditorDetailCustomization.h" #include "MaterialInstanceEditor.h" #include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h" #include "EditorViewportCommands.h" #include "GraphEditor.h" #include "GraphEditorActions.h" #include "BlueprintEditorUtils.h" #include "EdGraphUtilities.h" #include "SNodePanel.h" #include "MaterialEditorUtilities.h" #include "SMaterialPalette.h" #include "FindInMaterial.h" #include "SColorPicker.h" #include "EditorClassUtils.h" #include "IDocumentation.h" #include "SDockTab.h" #include "Developer/MessageLog/Public/MessageLogModule.h" #include "Particles/ParticleSystemComponent.h" #include "GenericCommands.h" #include "CanvasTypes.h" #include "Engine/Selection.h" #include "Engine/TextureCube.h" #define LOCTEXT_NAMESPACE "MaterialEditor" DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditor, Log, All); static TAutoConsoleVariable CVarMaterialEdUseDevShaders( TEXT("r.MaterialEditor.UseDevShaders"), 0, TEXT("Toggles whether the material editor will use shaders that include extra overhead incurred by the editor. Material editor must be re-opened if changed at runtime."), ECVF_RenderThreadSafe); const FName FMaterialEditor::PreviewTabId( TEXT( "MaterialEditor_Preview" ) ); const FName FMaterialEditor::GraphCanvasTabId( TEXT( "MaterialEditor_GraphCanvas" ) ); const FName FMaterialEditor::PropertiesTabId( TEXT( "MaterialEditor_MaterialProperties" ) ); const FName FMaterialEditor::HLSLCodeTabId( TEXT( "MaterialEditor_HLSLCode" ) ); const FName FMaterialEditor::PaletteTabId( TEXT( "MaterialEditor_Palette" ) ); const FName FMaterialEditor::StatsTabId( TEXT( "MaterialEditor_Stats" ) ); const FName FMaterialEditor::FindTabId( TEXT( "MaterialEditor_Find" ) ); /////////////////////////// // FMatExpressionPreview // /////////////////////////// bool FMatExpressionPreview::ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const { if(VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLocalVertexFactory"), FNAME_Find))) { // we only need the non-light-mapped, base pass, local vertex factory shaders for drawing an opaque Material Tile // @todo: Added a FindShaderType by fname or something" if (IsMobilePlatform(Platform)) { if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingVSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingPSFNoLightMapPolicy"))) { return true; } } else { if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassVSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassHSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassDSFNoLightMapPolicy"))) { return true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSFNoLightMapPolicy"))) { return true; } } } return false; } int32 FMatExpressionPreview::CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const { // needs to be called in this function!! Compiler->SetMaterialProperty(Property, OverrideShaderFrequency, bUsePreviousFrameTime); int32 Ret = INDEX_NONE; if( Property == MP_EmissiveColor && Expression.IsValid()) { // Hardcoding output 0 as we don't have the UI to specify any other output const int32 OutputIndex = 0; // Get back into gamma corrected space, as DrawTile does not do this adjustment. Ret = Compiler->Power(Compiler->Max(Expression->CompilePreview(Compiler, OutputIndex, -1), Compiler->Constant(0)), Compiler->Constant(1.f / 2.2f)); } else if (Property == MP_WorldPositionOffset) { //set to 0 to prevent off by 1 pixel errors Ret = Compiler->Constant(0.0f); } else if (Property >= MP_CustomizedUVs0 && Property <= MP_CustomizedUVs7) { const int32 TextureCoordinateIndex = Property - MP_CustomizedUVs0; Ret = Compiler->TextureCoordinate(TextureCoordinateIndex, false, false); } else { Ret = Compiler->Constant(1.0f); } // output should always be the right type for this property return Compiler->ForceCast(Ret, GetMaterialPropertyType(Property)); } void FMatExpressionPreview::NotifyCompilationFinished() { if (Expression.IsValid() && Expression->GraphNode) { CastChecked(Expression->GraphNode)->bPreviewNeedsUpdate = true; } } ///////////////////// // FMaterialEditor // ///////////////////// void FMaterialEditor::RegisterTabSpawners(const TSharedRef& TabManager) { WorkspaceMenuCategory = TabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); FAssetEditorToolkit::RegisterTabSpawners(TabManager); TabManager->RegisterTabSpawner( PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) ) .SetDisplayName( LOCTEXT("ViewportTab", "Viewport") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports")); TabManager->RegisterTabSpawner( GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_GraphCanvas) ) .SetDisplayName( LOCTEXT("GraphCanvasTab", "Graph") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x")); TabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) ) .SetDisplayName( LOCTEXT("DetailsTab", "Details") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); TabManager->RegisterTabSpawner( PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) ) .SetDisplayName( LOCTEXT("PaletteTab", "Palette") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Palette")); TabManager->RegisterTabSpawner( StatsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Stats) ) .SetDisplayName( LOCTEXT("StatsTab", "Stats") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.StatsViewer")); TabManager->RegisterTabSpawner(FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find)) .SetDisplayName(LOCTEXT("FindTab", "Find Results")) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.FindResults")); TabManager->RegisterTabSpawner( HLSLCodeTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_HLSLCode) ) .SetDisplayName( LOCTEXT("HLSLCodeTab", "HLSL Code") ) .SetGroup( WorkspaceMenuCategoryRef ); } void FMaterialEditor::UnregisterTabSpawners(const TSharedRef& TabManager) { FAssetEditorToolkit::UnregisterTabSpawners(TabManager); TabManager->UnregisterTabSpawner( PreviewTabId ); TabManager->UnregisterTabSpawner( GraphCanvasTabId ); TabManager->UnregisterTabSpawner( PropertiesTabId ); TabManager->UnregisterTabSpawner( PaletteTabId ); TabManager->UnregisterTabSpawner( StatsTabId ); TabManager->UnregisterTabSpawner( FindTabId ); TabManager->UnregisterTabSpawner( HLSLCodeTabId ); } void FMaterialEditor::InitEditorForMaterial(UMaterial* InMaterial) { check(InMaterial); OriginalMaterial = InMaterial; MaterialFunction = NULL; OriginalMaterialObject = InMaterial; ExpressionPreviewMaterial = NULL; // Create a copy of the material for preview usage (duplicating to a different class than original!) // Propagate all object flags except for RF_Standalone, otherwise the preview material won't GC once // the material editor releases the reference. Material = (UMaterial*)StaticDuplicateObject(OriginalMaterial, GetTransientPackage(), NULL, ~RF_Standalone, UPreviewMaterial::StaticClass()); Material->CancelOutstandingCompilation(); //The material is compiled later on anyway so no need to do it in Duplication/PostLoad. //I'm hackily canceling the jobs here but we should really not add the jobs in the first place. <<--- TODO Material->bAllowDevelopmentShaderCompile = CVarMaterialEdUseDevShaders.GetValueOnGameThread(); // Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid // This can happen if an expression class was removed for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { if (!Material->Expressions[ExpressionIndex]) { Material->Expressions.RemoveAt(ExpressionIndex); } } } void FMaterialEditor::InitEditorForMaterialFunction(UMaterialFunction* InMaterialFunction) { check(InMaterialFunction); Material = NULL; MaterialFunction = InMaterialFunction; OriginalMaterialObject = InMaterialFunction; ExpressionPreviewMaterial = NULL; // Create a temporary material to preview the material function Material = NewObject(); { FArchiveUObject DummyArchive; // Hack: serialize the new material with an archive that does nothing so that its material resources are created Material->Serialize(DummyArchive); } Material->SetShadingModel(MSM_Unlit); // Propagate all object flags except for RF_Standalone, otherwise the preview material function won't GC once // the material editor releases the reference. MaterialFunction = (UMaterialFunction*)StaticDuplicateObject(InMaterialFunction, GetTransientPackage(), NULL, ~RF_Standalone, UMaterialFunction::StaticClass()); MaterialFunction->ParentFunction = InMaterialFunction; OriginalMaterial = Material; } void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit ) { EditorOptions = NULL; bMaterialDirty = false; bStatsFromPreviewMaterial = false; ColorPickerObject = NULL; // Support undo/redo Material->SetFlags(RF_Transactional); GEditor->RegisterForUndo(this); if (!Material->MaterialGraph) { Material->MaterialGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(Material, NAME_None, UMaterialGraph::StaticClass(), UMaterialGraphSchema::StaticClass())); } Material->MaterialGraph->Material = Material; Material->MaterialGraph->MaterialFunction = MaterialFunction; Material->MaterialGraph->RealtimeDelegate.BindSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked); Material->MaterialGraph->MaterialDirtyDelegate.BindSP(this, &FMaterialEditor::SetMaterialDirty); Material->MaterialGraph->ToggleCollapsedDelegate.BindSP(this, &FMaterialEditor::ToggleCollapsed); // copy material usage for( int32 Usage=0; Usage < MATUSAGE_MAX; Usage++ ) { const EMaterialUsage UsageEnum = (EMaterialUsage)Usage; if( OriginalMaterial->GetUsageByFlag(UsageEnum) ) { bool bNeedsRecompile=false; Material->SetMaterialUsage(bNeedsRecompile,UsageEnum); } } // Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials Material->bUsedAsSpecialEngineMaterial = OriginalMaterial->bUsedAsSpecialEngineMaterial; // Register our commands. This will only register them if not previously registered FGraphEditorCommands::Register(); FMaterialEditorCommands::Register(); FMaterialEditorSpawnNodeCommands::Register(); FEditorSupportDelegates::MaterialUsageFlagsChanged.AddRaw(this, &FMaterialEditor::OnMaterialUsageFlagsChanged); FEditorSupportDelegates::VectorParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnVectorParameterDefaultChanged); FEditorSupportDelegates::ScalarParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnScalarParameterDefaultChanged); FEditorDelegates::OnAssetPostImport.AddRaw(this, &FMaterialEditor::OnAssetPostImport); FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnAssetRenamed().AddSP( this, &FMaterialEditor::RenameAssetFromRegistry ); CreateInternalWidgets(); // Do setup previously done in SMaterialEditorCanvas SetPreviewMaterial(Material); Material->bIsPreviewMaterial = true; FMaterialEditorUtilities::InitExpressions(Material); BindCommands(); const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_MaterialEditor_Layout_v5") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.1f) ->SetHideTabWell( true ) ->AddTab(GetToolbarTabId(), ETabState::OpenedTab) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.9f) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.2f) ->Split ( FTabManager::NewStack() ->SetHideTabWell( true ) ->AddTab( PreviewTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->AddTab( PropertiesTabId, ETabState::OpenedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation( Orient_Vertical ) ->SetSizeCoefficient(0.80f) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.8f) ->SetHideTabWell( true ) ->AddTab( GraphCanvasTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient( 0.20f ) ->AddTab( StatsTabId, ETabState::ClosedTab ) ->AddTab( FindTabId, ETabState::ClosedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.2f) ->Split ( FTabManager::NewStack() ->AddTab( PaletteTabId, ETabState::OpenedTab ) ) ) ) ); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; // Add the preview material to the objects being edited, so that we can find this editor from the temporary material graph TArray< UObject* > ObjectsToEdit; ObjectsToEdit.Add(ObjectToEdit); ObjectsToEdit.Add(Material); FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, MaterialEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, false ); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddMenuExtender(MaterialEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); ExtendToolbar(); RegenerateMenusAndToolbars(); // @todo toolkit world centric editing /*if( IsWorldCentricAssetEditor() ) { SpawnToolkitTab(GetToolbarTabId(), FString(), EToolkitTabSpot::ToolBar); SpawnToolkitTab(PreviewTabId, FString(), EToolkitTabSpot::Viewport); SpawnToolkitTab(GraphCanvasTabId, FString(), EToolkitTabSpot::Document); SpawnToolkitTab(PropertiesTabId, FString(), EToolkitTabSpot::Details); }*/ // Load editor settings from disk. LoadEditorSettings(); // Set the preview mesh for the material. This call must occur after the toolbar is initialized. if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString())) { // The material preview mesh couldn't be found or isn't loaded. Default to the one of the primitive types. SetPreviewAsset( GUnrealEd->GetThumbnailManager()->EditorSphere ); } // Initialize expression previews. if (MaterialFunction) { Material->Expressions = MaterialFunction->FunctionExpressions; Material->EditorComments = MaterialFunction->FunctionEditorComments; // Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid // This can happen if an expression class was removed for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { if (!Material->Expressions[ExpressionIndex]) { Material->Expressions.RemoveAt(ExpressionIndex); } } if (Material->Expressions.Num() == 0) { // If this is an empty functions, create an output by default and start previewing it if (GraphEditor.IsValid()) { UMaterialExpression* Expression = CreateNewMaterialExpression(UMaterialExpressionFunctionOutput::StaticClass(), FVector2D(200, 300), false, true); SetPreviewExpression(Expression); } } else { bool bSetPreviewExpression = false; UMaterialExpressionFunctionOutput* FirstOutput = NULL; for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->Expressions[ExpressionIndex]; // Setup the expression to be used with the preview material instead of the function Expression->Function = NULL; Expression->Material = Material; UMaterialExpressionFunctionOutput* FunctionOutput = Cast(Expression); if (FunctionOutput) { FirstOutput = FunctionOutput; if (FunctionOutput->bLastPreviewed) { bSetPreviewExpression = true; // Preview the last output previewed SetPreviewExpression(FunctionOutput); } } } if (!bSetPreviewExpression && FirstOutput) { SetPreviewExpression(FirstOutput); } } } // Store the name of this material (for the tutorial widget meta) Material->MaterialGraph->OriginalMaterialFullName = OriginalMaterial->GetName(); Material->MaterialGraph->RebuildGraph(); RecenterEditor(); //Make sure the preview material is initialized. UpdatePreviewMaterial(true); RegenerateCodeView(true); ForceRefreshExpressionPreviews(); } FMaterialEditor::FMaterialEditor() : bMaterialDirty(false) , bStatsFromPreviewMaterial(false) , Material(NULL) , OriginalMaterial(NULL) , ExpressionPreviewMaterial(NULL) , EmptyMaterial(NULL) , PreviewExpression(NULL) , MaterialFunction(NULL) , OriginalMaterialObject(NULL) , EditorOptions(NULL) , ScopedTransaction(NULL) , bAlwaysRefreshAllPreviews(false) , bHideUnusedConnectors(false) , bLivePreview(true) , bIsRealtime(false) , bShowStats(true) , bShowBuiltinStats(false) , bShowMobileStats(false) { } FMaterialEditor::~FMaterialEditor() { for (int32 ParameterIndex = 0; ParameterIndex < OverriddenVectorParametersToRevert.Num(); ParameterIndex++) { SetVectorParameterDefaultOnDependentMaterials(OverriddenVectorParametersToRevert[ParameterIndex], FLinearColor::Black, false); } for (int32 ParameterIndex = 0; ParameterIndex < OverriddenScalarParametersToRevert.Num(); ParameterIndex++) { SetScalarParameterDefaultOnDependentMaterials(OverriddenScalarParametersToRevert[ParameterIndex], 0, false); } // Unregister this delegate FEditorSupportDelegates::MaterialUsageFlagsChanged.RemoveAll(this); FEditorSupportDelegates::VectorParameterDefaultChanged.RemoveAll(this); FEditorSupportDelegates::ScalarParameterDefaultChanged.RemoveAll(this); FEditorDelegates::OnAssetPostImport.RemoveAll(this); // Null out the expression preview material so they can be GC'ed ExpressionPreviewMaterial = NULL; // Save editor settings to disk. SaveEditorSettings(); MaterialDetailsView.Reset(); { SCOPED_SUSPEND_RENDERING_THREAD(true); ExpressionPreviews.Empty(); } check( !ScopedTransaction ); GEditor->UnregisterForUndo( this ); } void FMaterialEditor::GetAllMaterialExpressionGroups(TArray* OutGroups) { for (int32 MaterialExpressionIndex = 0; MaterialExpressionIndex < Material->Expressions.Num(); ++MaterialExpressionIndex) { UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ]; UMaterialExpressionParameter *Switch = Cast(MaterialExpression); UMaterialExpressionTextureSampleParameter *TextureS = Cast(MaterialExpression); UMaterialExpressionFontSampleParameter *FontS = Cast(MaterialExpression); if(Switch) { OutGroups->AddUnique(Switch->Group.ToString()); } if(TextureS) { OutGroups->AddUnique(TextureS->Group.ToString()); } if(FontS) { OutGroups->AddUnique(FontS->Group.ToString()); } } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMaterialEditor::CreateInternalWidgets() { Viewport = SNew(SMaterialEditorViewport) .MaterialEditor(SharedThis(this)); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked( "PropertyEditor" ); GraphEditor = CreateGraphEditorWidget(); // Manually set zoom level to avoid deferred zooming GraphEditor->SetViewLocation(FVector2D::ZeroVector, 1); const FDetailsViewArgs DetailsViewArgs( false, false, true, FDetailsViewArgs::HideNameArea, true, this ); MaterialDetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs ); FOnGetDetailCustomizationInstance LayoutExpressionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic( &FMaterialExpressionParameterDetails::MakeInstance, FOnCollectParameterGroups::CreateSP(this, &FMaterialEditor::GetAllMaterialExpressionGroups) ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionParameter::StaticClass(), LayoutExpressionParameterDetails ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionFontSampleParameter::StaticClass(), LayoutExpressionParameterDetails ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionTextureSampleParameter::StaticClass(), LayoutExpressionParameterDetails ); FOnGetDetailCustomizationInstance LayoutCollectionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCollectionParameterDetails::MakeInstance); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionCollectionParameter::StaticClass(), LayoutCollectionParameterDetails ); Palette = SNew(SMaterialPalette, SharedThis(this)); FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); FMessageLogInitializationOptions LogOptions; // Show Pages so that user is never allowed to clear log messages LogOptions.bShowPages = false; LogOptions.bShowFilters = false; //TODO - Provide custom filters? E.g. "Critical Errors" vs "Errors" needed for materials? LogOptions.bAllowClear = false; LogOptions.MaxPageCount = 1; StatsListing = MessageLogModule.CreateLogListing( "MaterialEditorStats", LogOptions ); Stats = MessageLogModule.CreateLogListingWidget( StatsListing.ToSharedRef() ); FindResults = SNew(SFindInMaterial, SharedThis(this)); CodeViewUtility = SNew(SVerticalBox) // Copy Button +SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .AutoWidth() .Padding( 2.0f, 0.0f ) .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ SNew(SButton) .Text( LOCTEXT("CopyHLSLButton", "Copy") ) .ToolTipText( LOCTEXT("CopyHLSLButtonToolTip", "Copies all HLSL code to the clipboard.") ) .ContentPadding(3) .OnClicked(this, &FMaterialEditor::CopyCodeViewTextToClipboard) ] ] // Separator +SVerticalBox::Slot() .FillHeight(1) [ SNew(SSeparator) ]; CodeView = SNew(SScrollBox) +SScrollBox::Slot() .Padding(5) [ SNew(STextBlock) .Text(this, &FMaterialEditor::GetCodeViewText) ]; RegenerateCodeView(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FName FMaterialEditor::GetToolkitFName() const { return FName("MaterialEditor"); } FText FMaterialEditor::GetBaseToolkitName() const { return LOCTEXT("AppLabel", "Material Editor"); } FText FMaterialEditor::GetToolkitName() const { const UObject* EditingObject = GetEditingObjects()[0]; const bool bDirtyState = EditingObject->GetOutermost()->IsDirty(); // Overridden to accommodate editing of multiple objects (original and preview materials) FFormatNamedArguments Args; Args.Add( TEXT("ObjectName"), FText::FromString( EditingObject->GetName() ) ); Args.Add( TEXT("DirtyState"), bDirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() ); return FText::Format( LOCTEXT("MaterialEditorAppLabel", "{ObjectName}{DirtyState}"), Args ); } FString FMaterialEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Material ").ToString(); } FLinearColor FMaterialEditor::GetWorldCentricTabColorScale() const { return FLinearColor( 0.3f, 0.2f, 0.5f, 0.5f ); } void FMaterialEditor::Tick( float InDeltaTime ) { UpdateMaterialInfoList(); UpdateGraphNodeStates(); } TStatId FMaterialEditor::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FMaterialEditor, STATGROUP_Tickables); } void FMaterialEditor::UpdateThumbnailInfoPreviewMesh(UMaterialInterface* MatInterface) { if ( MatInterface ) { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( MatInterface->GetClass() ); if ( AssetTypeActions.IsValid() ) { USceneThumbnailInfoWithPrimitive* OriginalThumbnailInfo = Cast(AssetTypeActions.Pin()->GetThumbnailInfo(MatInterface)); if ( OriginalThumbnailInfo ) { OriginalThumbnailInfo->PreviewMesh = MatInterface->PreviewMesh; MatInterface->PostEditChange(); } } } } void FMaterialEditor::ExtendToolbar() { struct Local { static void FillToolbar(FToolBarBuilder& ToolbarBuilder) { ToolbarBuilder.BeginSection("Apply"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().Apply); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("Search"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().FindInMaterial); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("Graph"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CameraHome); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CleanUnusedExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ShowHideConnectors); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleLivePreview); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleRealtimeExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMobileStats); } ToolbarBuilder.EndSection(); } }; TSharedPtr ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar ) ); AddToolbarExtender(ToolbarExtender); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } UMaterialInterface* FMaterialEditor::GetMaterialInterface() const { return Material; } bool FMaterialEditor::ApproveSetPreviewAsset(UObject* InAsset) { bool bApproved = true; // Only permit the use of a skeletal mesh if the material has bUsedWithSkeltalMesh. if (USkeletalMesh* SkeletalMesh = Cast(InAsset)) { if (!Material->bUsedWithSkeletalMesh) { FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_MaterialEditor_CantPreviewOnSkelMesh", "Can't preview on the specified skeletal mesh because the material has not been compiled with bUsedWithSkeletalMesh.")); bApproved = false; } } return bApproved; } void FMaterialEditor::SaveAsset_Execute() { UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName()); if (bMaterialDirty) { UpdateOriginalMaterial(); } UPackage* Package = OriginalMaterial->GetOutermost(); if (MaterialFunction != NULL && MaterialFunction->ParentFunction) { Package = MaterialFunction->ParentFunction->GetOutermost(); } if (Package) { TArray PackagesToSave; PackagesToSave.Add(Package); FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, false, false); } } bool FMaterialEditor::OnRequestClose() { DestroyColorPicker(); // @todo DB: Store off the viewport camera position/orientation to the material. //AnimTree->PreviewCamPos = PreviewVC->ViewLocation; //AnimTree->PreviewCamRot = PreviewVC->ViewRotation; if (bMaterialDirty) { // find out the user wants to do with this dirty material EAppReturnType::Type YesNoCancelReply = FMessageDialog::Open(EAppMsgType::YesNoCancel, FText::Format( NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorClose", "Would you like to apply changes to this material to the original material?\n{0}\n(No will lose all changes!)"), FText::FromString(OriginalMaterialObject->GetPathName()) )); // act on it switch (YesNoCancelReply) { case EAppReturnType::Yes: // update material and exit UpdateOriginalMaterial(); break; case EAppReturnType::No: // exit break; case EAppReturnType::Cancel: // don't exit return false; } } return true; } void FMaterialEditor::DrawMaterialInfoStrings( FCanvas* Canvas, const UMaterial* Material, const FMaterialResource* MaterialResource, const TArray& CompileErrors, int32 &DrawPositionY, bool bDrawInstructions) { check(Material && MaterialResource); ERHIFeatureLevel::Type FeatureLevel = MaterialResource->GetFeatureLevel(); FString FeatureLevelName; GetFeatureLevelName(FeatureLevel,FeatureLevelName); // The font to use when displaying info strings UFont* FontToUse = GEngine->GetTinyFont(); const int32 SpacingBetweenLines = 13; if (bDrawInstructions) { // Display any errors and messages in the upper left corner of the viewport. TArray Descriptions; TArray InstructionCounts; MaterialResource->GetRepresentativeInstructionCounts(Descriptions, InstructionCounts); for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++) { FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"),*Descriptions[InstructionIndex],InstructionCounts[InstructionIndex]); Canvas->DrawShadowedString(5, DrawPositionY, *InstructionCountString, FontToUse, FLinearColor(1, 1, 0)); DrawPositionY += SpacingBetweenLines; } // Display the number of samplers used by the material. const int32 SamplersUsed = MaterialResource->GetSamplerUsage(); if (SamplersUsed >= 0) { int32 MaxSamplers = GetFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel()); Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers), FontToUse, SamplersUsed > MaxSamplers ? FLinearColor(1,0,0) : FLinearColor(1,1,0) ); DrawPositionY += SpacingBetweenLines; } } for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { Canvas->DrawShadowedString(5, DrawPositionY, *FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]), FontToUse, FLinearColor(1, 0, 0)); DrawPositionY += SpacingBetweenLines; } } void FMaterialEditor::DrawMessages( FViewport* InViewport, FCanvas* Canvas ) { if( PreviewExpression != NULL ) { Canvas->PushAbsoluteTransform( FMatrix::Identity ); // The message to display in the viewport. FString Name = FString::Printf( TEXT("Previewing: %s"), *PreviewExpression->GetName() ); // Size of the tile we are about to draw. Should extend the length of the view in X. const FIntPoint TileSize( InViewport->GetSizeXY().X, 25); const FColor PreviewColor( 70,100,200 ); const FColor FontColor( 255,255,128 ); UFont* FontToUse = GEditor->EditorFont; Canvas->DrawTile( 0.0f, 0.0f, TileSize.X, TileSize.Y, 0.0f, 0.0f, 0.0f, 0.0f, PreviewColor ); int32 XL, YL; StringSize( FontToUse, XL, YL, *Name ); if( XL > TileSize.X ) { // There isn't enough room to show the preview expression name Name = TEXT("Previewing"); StringSize( FontToUse, XL, YL, *Name ); } // Center the string in the middle of the tile. const FIntPoint StringPos( (TileSize.X-XL)/2, ((TileSize.Y-YL)/2)+1 ); // Draw the preview message Canvas->DrawShadowedString( StringPos.X, StringPos.Y, *Name, FontToUse, FontColor ); Canvas->PopTransform(); } } void FMaterialEditor::RecenterEditor() { UEdGraphNode* FocusNode = NULL; if (MaterialFunction) { bool bSetPreviewExpression = false; UMaterialExpressionFunctionOutput* FirstOutput = NULL; for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->Expressions[ExpressionIndex]; UMaterialExpressionFunctionOutput* FunctionOutput = Cast(Expression); if (FunctionOutput) { FirstOutput = FunctionOutput; if (FunctionOutput->bLastPreviewed) { bSetPreviewExpression = true; FocusNode = FunctionOutput->GraphNode; } } } if (!bSetPreviewExpression && FirstOutput) { FocusNode = FirstOutput->GraphNode; } } else { FocusNode = Material->MaterialGraph->RootNode; } if (FocusNode) { JumpToNode(FocusNode); } else { // Get current view location so that we don't change the zoom amount FVector2D CurrLocation; float CurrZoomLevel; GraphEditor->GetViewLocation(CurrLocation, CurrZoomLevel); GraphEditor->SetViewLocation(FVector2D::ZeroVector, CurrZoomLevel); } } bool FMaterialEditor::SetPreviewAsset(UObject* InAsset) { if (Viewport.IsValid()) { return Viewport->SetPreviewAsset(InAsset); } return false; } bool FMaterialEditor::SetPreviewAssetByName(const TCHAR* InAssetName) { if (Viewport.IsValid()) { return Viewport->SetPreviewAssetByName(InAssetName); } return false; } void FMaterialEditor::SetPreviewMaterial(UMaterialInterface* InMaterialInterface) { if (Viewport.IsValid()) { Viewport->SetPreviewMaterial(InMaterialInterface); } } void FMaterialEditor::RefreshPreviewViewport() { if (Viewport.IsValid()) { Viewport->RefreshViewport(); } } void FMaterialEditor::LoadEditorSettings() { EditorOptions = NewObject(); if (EditorOptions->bHideUnusedConnectors) {OnShowConnectors();} if (bLivePreview != EditorOptions->bLivePreviewUpdate) { ToggleLivePreview(); } if (EditorOptions->bAlwaysRefreshAllPreviews) {OnAlwaysRefreshAllPreviews();} if (EditorOptions->bRealtimeExpressionViewport) {ToggleRealTimeExpressions();} if ( Viewport.IsValid() ) { if (EditorOptions->bShowGrid) {Viewport->TogglePreviewGrid();} if (EditorOptions->bShowBackground) {Viewport->TogglePreviewBackground();} if (EditorOptions->bRealtimeMaterialViewport) {Viewport->OnToggleRealtime();} // Load the preview scene Viewport->PreviewScene.LoadSettings(TEXT("MaterialEditor")); } if (EditorOptions->bShowMobileStats) { ToggleMobileStats(); } // Primitive type int32 PrimType; if(GConfig->GetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PrimType, GEditorPerProjectIni)) { Viewport->OnSetPreviewPrimitive((EThumbnailPrimType)PrimType); } } void FMaterialEditor::SaveEditorSettings() { // Save the preview scene check( Viewport.IsValid() ); Viewport->PreviewScene.SaveSettings(TEXT("MaterialEditor")); if ( EditorOptions ) { EditorOptions->bShowGrid = Viewport->IsTogglePreviewGridChecked(); EditorOptions->bShowBackground = Viewport->IsTogglePreviewBackgroundChecked(); EditorOptions->bRealtimeMaterialViewport = Viewport->IsRealtime(); EditorOptions->bShowMobileStats = bShowMobileStats; EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked(); EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews(); EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked(); EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); EditorOptions->SaveConfig(); } GConfig->SetInt(TEXT("MaterialEditor"), TEXT("PrimType"), Viewport->PreviewPrimType, GEditorPerProjectIni); } FText FMaterialEditor::GetCodeViewText() const { return FText::FromString(HLSLCode); } FReply FMaterialEditor::CopyCodeViewTextToClipboard() { FPlatformMisc::ClipboardCopy(*HLSLCode); return FReply::Handled(); } void FMaterialEditor::RegenerateCodeView(bool bForce) { #define MARKTAG TEXT("/*MARK_") #define MARKTAGLEN 7 HLSLCode = TEXT(""); if (!CodeTab.IsValid() || (!bLivePreview && !bForce)) { //When bLivePreview is false then the source can be out of date. return; } FString MarkupSource; if (Material->GetMaterialResource(GMaxRHIFeatureLevel)->GetMaterialExpressionSource(MarkupSource)) { // Remove line-feeds and leave just CRs so the character counts match the selection ranges. MarkupSource.ReplaceInline(TEXT("\r"), TEXT("")); // Improve formatting: Convert tab to 4 spaces since STextBlock (currently) doesn't show tab characters MarkupSource.ReplaceInline(TEXT("\t"), TEXT(" ")); // Extract highlight ranges from markup tags // Make a copy so we can insert null terminators. TCHAR* MarkupSourceCopy = new TCHAR[MarkupSource.Len()+1]; FCString::Strcpy(MarkupSourceCopy, MarkupSource.Len()+1, *MarkupSource); TCHAR* Ptr = MarkupSourceCopy; while( Ptr && *Ptr != '\0' ) { TCHAR* NextTag = FCString::Strstr( Ptr, MARKTAG ); if( !NextTag ) { // No more tags, so we're done! HLSLCode += Ptr; break; } // Copy the text up to the tag. *NextTag = '\0'; HLSLCode += Ptr; // Advance past the markup tag to see what type it is (beginning or end) NextTag += MARKTAGLEN; int32 TagNumber = FCString::Atoi(NextTag+1); Ptr = FCString::Strstr(NextTag, TEXT("*/")) + 2; } delete[] MarkupSourceCopy; } } void FMaterialEditor::UpdatePreviewMaterial( bool bForce ) { if (!bLivePreview && !bForce) { //Don't update the preview material return; } bStatsFromPreviewMaterial = true; if( PreviewExpression && ExpressionPreviewMaterial ) { PreviewExpression->ConnectToPreviewMaterial(ExpressionPreviewMaterial,0); } if(PreviewExpression) { // The preview material's expressions array must stay up to date before recompiling // So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated ExpressionPreviewMaterial->Expressions = Material->Expressions; FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread); UpdateContext.AddMaterial(ExpressionPreviewMaterial); // If we are previewing an expression, update the expression preview material ExpressionPreviewMaterial->PreEditChange( NULL ); ExpressionPreviewMaterial->PostEditChange(); } else { FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread); UpdateContext.AddMaterial(Material); // Update the regular preview material when not previewing an expression. Material->PreEditChange( NULL ); Material->PostEditChange(); UpdateStatsMaterials(); // Null out the expression preview material so they can be GC'ed ExpressionPreviewMaterial = NULL; } // Reregister all components that use the preview material, since UMaterial::PEC does not reregister components using a bIsPreviewMaterial=true material RefreshPreviewViewport(); } void FMaterialEditor::RebuildMaterialInstanceEditors(UMaterialInstance * MatInst) { FAssetEditorManager& AssetEditorManager = FAssetEditorManager::Get(); TArray EditedAssets = AssetEditorManager.GetAllEditedAssets(); for (int32 AssetIdx = 0; AssetIdx < EditedAssets.Num(); AssetIdx++) { UObject* EditedAsset = EditedAssets[AssetIdx]; UMaterialInstance* SourceInstance = Cast(EditedAsset); if(!SourceInstance) { // Check to see if the EditedAssets are from material instance editor UMaterialEditorInstanceConstant* EditorInstance = Cast(EditedAsset); if(EditorInstance && EditorInstance->SourceInstance) { SourceInstance = Cast(EditorInstance->SourceInstance); } } // Ensure the material instance is valid and not a UMaterialInstanceDynamic, as that doesn't use FMaterialInstanceEditor as its editor if ( SourceInstance != NULL && !SourceInstance->IsA(UMaterialInstanceDynamic::StaticClass())) { UMaterial * MICOriginalMaterial = SourceInstance->GetMaterial(); if (MICOriginalMaterial == OriginalMaterial) { IAssetEditorInstance* EditorInstance = AssetEditorManager.FindEditorForAsset(EditedAsset, false); if ( EditorInstance != NULL ) { FMaterialInstanceEditor* OtherEditor = static_cast(EditorInstance); OtherEditor->RebuildMaterialInstanceEditor(); } } } } } void FMaterialEditor::UpdateOriginalMaterial() { // If the Material has compilation errors, warn the user for (int32 i = ERHIFeatureLevel::SM5; i >= 0; --i) { ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)i; if( Material->GetMaterialResource(FeatureLevel)->GetCompileErrors().Num() > 0 ) { FString FeatureLevelName; GetFeatureLevelName(FeatureLevel, FeatureLevelName); FSuppressableWarningDialog::FSetupInfo Info( FText::Format(NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial", "The current material has compilation errors, so it will not render correctly in feature level {0}.\nAre you sure you wish to continue?"),FText::FromString(*FeatureLevelName)), NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial_Title", "Warning: Compilation errors in this Material" ), "Warning_CompileErrorsInMaterial"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialConfirm", "Continue"); Info.CancelText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialCancel", "Abort"); FSuppressableWarningDialog CompileErrorsWarning( Info ); if( CompileErrorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel ) { return; } } } // Make sure any graph position changes that might not have been copied are taken into account Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); //remove any memory copies of shader files, so they will be reloaded from disk //this way the material editor can be used for quick shader iteration FlushShaderFileCache(); //recompile and refresh the preview material so it will be updated if there was a shader change //Force it even if bLivePreview is false. UpdatePreviewMaterial(true); RegenerateCodeView(true); const FScopedBusyCursor BusyCursor; const FText LocalizedMaterialEditorApply = NSLOCTEXT("UnrealEd", "ToolTip_MaterialEditorApply", "Apply changes to original material and its use in the world."); GWarn->BeginSlowTask( LocalizedMaterialEditorApply, true ); GWarn->StatusUpdate( 1, 1, LocalizedMaterialEditorApply ); // Handle propagation of the material function being edited if (MaterialFunction) { // Copy the expressions back from the preview material MaterialFunction->FunctionExpressions = Material->Expressions; MaterialFunction->FunctionEditorComments = Material->EditorComments; // Preserve the thumbnail info UThumbnailInfo* OriginalThumbnailInfo = MaterialFunction->ParentFunction->ThumbnailInfo; UThumbnailInfo* ThumbnailInfo = MaterialFunction->ThumbnailInfo; MaterialFunction->ParentFunction->ThumbnailInfo = NULL; MaterialFunction->ThumbnailInfo = NULL; // overwrite the original material function in place by constructing a new one with the same name MaterialFunction->ParentFunction = (UMaterialFunction*)StaticDuplicateObject( MaterialFunction, MaterialFunction->ParentFunction->GetOuter(), *MaterialFunction->ParentFunction->GetName(), RF_AllFlags, MaterialFunction->ParentFunction->GetClass()); // Restore the thumbnail info MaterialFunction->ParentFunction->ThumbnailInfo = OriginalThumbnailInfo; MaterialFunction->ThumbnailInfo = ThumbnailInfo; // Restore RF_Standalone on the original material function, as it had been removed from the preview material so that it could be GC'd. MaterialFunction->ParentFunction->SetFlags( RF_Standalone ); for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionExpressions.Num(); ExpressionIndex++) { UMaterialExpression* CurrentExpression = MaterialFunction->ParentFunction->FunctionExpressions[ExpressionIndex]; // Link the expressions back to their function CurrentExpression->Material = NULL; CurrentExpression->Function = MaterialFunction->ParentFunction; } for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionEditorComments.Num(); ExpressionIndex++) { UMaterialExpressionComment* CurrentExpression = MaterialFunction->ParentFunction->FunctionEditorComments[ExpressionIndex]; // Link the expressions back to their function CurrentExpression->Material = NULL; CurrentExpression->Function = MaterialFunction->ParentFunction; } // mark the parent function as changed MaterialFunction->ParentFunction->PreEditChange(NULL); MaterialFunction->ParentFunction->PostEditChange(); MaterialFunction->ParentFunction->MarkPackageDirty(); // clear the dirty flag bMaterialDirty = false; bStatsFromPreviewMaterial = false; // Create a material update context so we can safely update materials using this function. { FMaterialUpdateContext UpdateContext; // Go through all materials in memory and recompile them if they use this material function for (TObjectIterator It; It; ++It) { UMaterial* CurrentMaterial = *It; if (CurrentMaterial != Material) { bool bRecompile = false; // Preview materials often use expressions for rendering that are not in their Expressions array, // And therefore their MaterialFunctionInfos are not up to date. // However we don't want to trigger this if the Material is a preview material itself. This can now be the case with thumbnail preview materials for material functions. if (CurrentMaterial->bIsPreviewMaterial && !Material->bIsPreviewMaterial) { bRecompile = true; } else { for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bRecompile = true; break; } } } if (bRecompile) { UpdateContext.AddMaterial(CurrentMaterial); // Propagate the function change to this material CurrentMaterial->PreEditChange(NULL); CurrentMaterial->PostEditChange(); CurrentMaterial->MarkPackageDirty(); if (CurrentMaterial->MaterialGraph) { CurrentMaterial->MaterialGraph->RebuildGraph(); } } } } } // update the world's viewports FEditorDelegates::RefreshEditor.Broadcast(); FEditorSupportDelegates::RedrawAllViewports.Broadcast(); } // Handle propagation of the material being edited else { FNavigationLockContext NavUpdateLock(ENavigationLockReason::MaterialUpdate); // Create a material update context so we can safely update materials. { FMaterialUpdateContext UpdateContext; UpdateContext.AddMaterial(OriginalMaterial); // ensure the original copy of the material is removed from the editor's selection set // or it will end up containing a stale, invalid entry if ( OriginalMaterial->IsSelected() ) { GEditor->GetSelectedObjects()->Deselect( OriginalMaterial ); } // Preserve the thumbnail info UThumbnailInfo* OriginalThumbnailInfo = OriginalMaterial->ThumbnailInfo; UThumbnailInfo* ThumbnailInfo = Material->ThumbnailInfo; OriginalMaterial->ThumbnailInfo = NULL; Material->ThumbnailInfo = NULL; // A bit hacky, but disable material compilation in post load when we duplicate the material. UMaterial::ForceNoCompilationInPostLoad(true); // overwrite the original material in place by constructing a new one with the same name OriginalMaterial = (UMaterial*)StaticDuplicateObject( Material, OriginalMaterial->GetOuter(), *OriginalMaterial->GetName(), RF_AllFlags, OriginalMaterial->GetClass()); // Post load has been called, allow materials to be compiled in PostLoad. UMaterial::ForceNoCompilationInPostLoad(false); // Restore the thumbnail info OriginalMaterial->ThumbnailInfo = OriginalThumbnailInfo; Material->ThumbnailInfo = ThumbnailInfo; // Change the original material object to the new original material OriginalMaterialObject = OriginalMaterial; // Restore RF_Standalone on the original material, as it had been removed from the preview material so that it could be GC'd. OriginalMaterial->SetFlags( RF_Standalone ); // Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials OriginalMaterial->bUsedAsSpecialEngineMaterial = Material->bUsedAsSpecialEngineMaterial; // If we are showing stats for mobile materials, compile the full material for ES2 here. That way we can see if permutations // not used for preview materials fail to compile. if (bShowMobileStats) { OriginalMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,true); } // let the material update itself if necessary OriginalMaterial->PreEditChange(NULL); OriginalMaterial->PostEditChange(); OriginalMaterial->MarkPackageDirty(); // clear the dirty flag bMaterialDirty = false; bStatsFromPreviewMaterial = false; // update the world's viewports FEditorDelegates::RefreshEditor.Broadcast(); FEditorSupportDelegates::RedrawAllViewports.Broadcast(); // Force particle components to update their view relevance. for (TObjectIterator It; It; ++It) { It->bIsViewRelevanceDirty = true; } // Leaving this scope will update all dependent material instances. } RebuildMaterialInstanceEditors(NULL); } GWarn->EndSlowTask(); } void FMaterialEditor::UpdateMaterialInfoList(bool bForceDisplay) { TArray< TSharedRef > Messages; TArray> TempMaterialInfoList; ERHIFeatureLevel::Type FeatureLevelsToDisplay[2]; int32 NumFeatureLevels = 0; // Always show basic features so that errors aren't hidden FeatureLevelsToDisplay[NumFeatureLevels++] = GMaxRHIFeatureLevel; if (bShowMobileStats) { FeatureLevelsToDisplay[NumFeatureLevels++] = ERHIFeatureLevel::ES2; } if (NumFeatureLevels > 0) { UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial; for (int32 i = 0; i < NumFeatureLevels; ++i) { TArray CompileErrors; ERHIFeatureLevel::Type FeatureLevel = FeatureLevelsToDisplay[i]; const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(FeatureLevel); if (MaterialFunction && ExpressionPreviewMaterial) { // Add a compile error message for functions missing an output CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel)->GetCompileErrors(); bool bFoundFunctionOutput = false; for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++) { if (Material->Expressions[ExpressionIndex]->IsA(UMaterialExpressionFunctionOutput::StaticClass())) { bFoundFunctionOutput = true; break; } } if (!bFoundFunctionOutput) { CompileErrors.Add(TEXT("Missing a function output")); } } else { CompileErrors = MaterialResource->GetCompileErrors(); } // Only show general info if stats enabled if (!MaterialFunction && bShowStats) { // Display any errors and messages in the upper left corner of the viewport. TArray Descriptions; TArray InstructionCounts; TArray EmptyDescriptions; TArray EmptyInstructionCounts; MaterialResource->GetRepresentativeInstructionCounts(Descriptions, InstructionCounts); //Built in stats is no longer exposed to the UI but may still be useful so they're still in the code. bool bBuiltinStats = false; const FMaterialResource* EmptyMaterialResource = EmptyMaterial ? EmptyMaterial->GetMaterialResource(FeatureLevel) : NULL; if (bShowBuiltinStats && bStatsFromPreviewMaterial && EmptyMaterialResource && InstructionCounts.Num() > 0) { EmptyMaterialResource->GetRepresentativeInstructionCounts(EmptyDescriptions, EmptyInstructionCounts); if (EmptyInstructionCounts.Num() > 0) { //The instruction counts should match. If not, the preview material has been changed without the EmptyMaterial being updated to match. if (ensure(InstructionCounts.Num() == EmptyInstructionCounts.Num())) { bBuiltinStats = true; } } } for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++) { FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"),*Descriptions[InstructionIndex], InstructionCounts[InstructionIndex]); if (bBuiltinStats) { InstructionCountString += FString::Printf(TEXT(" - Built-in instructions: %u"), EmptyInstructionCounts[InstructionIndex]); } TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InstructionCountString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(InstructionCountString))); Messages.Add(Line); } // Display the number of samplers used by the material. const int32 SamplersUsed = MaterialResource->GetSamplerUsage(); if (SamplersUsed >= 0) { int32 MaxSamplers = GetFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel()); FString SamplersString = FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(SamplersString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create( EMessageSeverity::Info ); Line->AddToken( FTextToken::Create( FText::FromString( SamplersString ) ) ); Messages.Add(Line); } } FString FeatureLevelName; GetFeatureLevelName(FeatureLevel,FeatureLevelName); for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red))); TSharedRef Line = FTokenizedMessage::Create( EMessageSeverity::Error ); Line->AddToken( FTextToken::Create( FText::FromString( ErrorString ) ) ); Messages.Add(Line); bForceDisplay = true; } } } bool bNeedsRefresh = false; if (TempMaterialInfoList.Num() != MaterialInfoList.Num()) { bNeedsRefresh = true; } for (int32 Index = 0; !bNeedsRefresh && Index < TempMaterialInfoList.Num(); ++Index) { if (TempMaterialInfoList[Index]->Color != MaterialInfoList[Index]->Color) { bNeedsRefresh = true; break; } if (TempMaterialInfoList[Index]->Text != MaterialInfoList[Index]->Text) { bNeedsRefresh = true; break; } } if (bNeedsRefresh) { MaterialInfoList = TempMaterialInfoList; /*TSharedPtr TitleBar = GraphEditor->GetTitleBar(); if (TitleBar.IsValid()) { StaticCastSharedPtr(TitleBar)->RequestRefresh(); }*/ StatsListing->ClearMessages(); StatsListing->AddMessages(Messages); if (bForceDisplay) { TabManager->InvokeTab(StatsTabId); } } } void FMaterialEditor::UpdateGraphNodeStates() { const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel); const FMaterialResource* ErrorMaterialResourceES2 = NULL; if (bShowMobileStats) { ErrorMaterialResourceES2 = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(ERHIFeatureLevel::ES2) : Material->GetMaterialResource(ERHIFeatureLevel::ES2); } bool bUpdatedErrorState = false; // Have to loop through everything here as there's no way to be notified when the material resource updates for (int32 Index = 0; Index < Material->MaterialGraph->Nodes.Num(); ++Index) { UMaterialGraphNode* MaterialNode = Cast(Material->MaterialGraph->Nodes[Index]); if (MaterialNode) { MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression); MaterialNode->bIsErrorExpression = (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE) || (ErrorMaterialResourceES2 && ErrorMaterialResourceES2->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE); if (MaterialNode->bIsErrorExpression && !MaterialNode->bHasCompilerMessage) { bUpdatedErrorState = true; MaterialNode->bHasCompilerMessage = true; MaterialNode->ErrorMsg = MaterialNode->MaterialExpression->LastErrorText; MaterialNode->ErrorType = EMessageSeverity::Error; } else if (!MaterialNode->bIsErrorExpression && MaterialNode->bHasCompilerMessage) { bUpdatedErrorState = true; MaterialNode->bHasCompilerMessage = false; } } } if (bUpdatedErrorState) { // Rebuild the SGraphNodes to display/hide error block GraphEditor->NotifyGraphChanged(); } } void FMaterialEditor::AddReferencedObjects( FReferenceCollector& Collector ) { Collector.AddReferencedObject( EditorOptions ); Collector.AddReferencedObject( Material ); Collector.AddReferencedObject( OriginalMaterial ); Collector.AddReferencedObject( MaterialFunction ); Collector.AddReferencedObject( ExpressionPreviewMaterial ); Collector.AddReferencedObject( EmptyMaterial ); } void FMaterialEditor::BindCommands() { const FMaterialEditorCommands& Commands = FMaterialEditorCommands::Get(); ToolkitCommands->MapAction( Commands.Apply, FExecuteAction::CreateSP( this, &FMaterialEditor::OnApply ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::OnApplyEnabled ) ); ToolkitCommands->MapAction( FEditorViewportCommands::Get().ToggleRealTime, FExecuteAction::CreateSP( Viewport.ToSharedRef(), &SMaterialEditorViewport::OnToggleRealtime ), FCanExecuteAction(), FIsActionChecked::CreateSP( Viewport.ToSharedRef(), &SMaterialEditorViewport::IsRealtime ) ); ToolkitCommands->MapAction( FGenericCommands::Get().Undo, FExecuteAction::CreateSP(this, &FMaterialEditor::UndoGraphAction)); ToolkitCommands->MapAction( FGenericCommands::Get().Redo, FExecuteAction::CreateSP(this, &FMaterialEditor::RedoGraphAction)); ToolkitCommands->MapAction( Commands.CameraHome, FExecuteAction::CreateSP(this, &FMaterialEditor::OnCameraHome), FCanExecuteAction() ); ToolkitCommands->MapAction( Commands.CleanUnusedExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::CleanUnusedExpressions), FCanExecuteAction() ); ToolkitCommands->MapAction( Commands.ShowHideConnectors, FExecuteAction::CreateSP(this, &FMaterialEditor::OnShowConnectors), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnShowConnectorsChecked)); ToolkitCommands->MapAction( Commands.ToggleLivePreview, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleLivePreview), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked)); ToolkitCommands->MapAction( Commands.ToggleRealtimeExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleRealTimeExpressions), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked)); ToolkitCommands->MapAction( Commands.AlwaysRefreshAllPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlwaysRefreshAllPreviews), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnAlwaysRefreshAllPreviews)); ToolkitCommands->MapAction( Commands.ToggleMaterialStats, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleStats), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleStatsChecked)); ToolkitCommands->MapAction( Commands.ToggleMobileStats, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleMobileStats), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleMobileStatsChecked)); ToolkitCommands->MapAction( Commands.UseCurrentTexture, FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture)); ToolkitCommands->MapAction( Commands.ConvertObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)); ToolkitCommands->MapAction( Commands.ConvertToTextureObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)); ToolkitCommands->MapAction( Commands.ConvertToTextureSamples, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)); ToolkitCommands->MapAction( Commands.ConvertToConstant, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)); ToolkitCommands->MapAction( Commands.StopPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)); ToolkitCommands->MapAction( Commands.StartPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)); ToolkitCommands->MapAction( Commands.EnableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)); ToolkitCommands->MapAction( Commands.DisableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)); ToolkitCommands->MapAction( Commands.SelectDownstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownsteamNodes)); ToolkitCommands->MapAction( Commands.SelectUpstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpsteamNodes)); ToolkitCommands->MapAction( Commands.RemoveFromFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites)); ToolkitCommands->MapAction( Commands.AddToFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites)); ToolkitCommands->MapAction( Commands.ForceRefreshPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews)); ToolkitCommands->MapAction( Commands.FindInMaterial, FExecuteAction::CreateSP(this, &FMaterialEditor::OnFindInMaterial)); } void FMaterialEditor::OnApply() { UE_LOG(LogMaterialEditor, Log, TEXT("Applying material %s"), *GetEditingObjects()[0]->GetName()); UpdateOriginalMaterial(); } bool FMaterialEditor::OnApplyEnabled() const { return bMaterialDirty == true; } void FMaterialEditor::OnCameraHome() { RecenterEditor(); } void FMaterialEditor::OnShowConnectors() { bHideUnusedConnectors = !bHideUnusedConnectors; GraphEditor->SetPinVisibility(bHideUnusedConnectors ? SGraphEditor::Pin_HideNoConnection : SGraphEditor::Pin_Show); } bool FMaterialEditor::IsOnShowConnectorsChecked() const { return bHideUnusedConnectors == false; } void FMaterialEditor::ToggleLivePreview() { bLivePreview = !bLivePreview; if (bLivePreview) { UpdatePreviewMaterial(); RegenerateCodeView(); } } bool FMaterialEditor::IsToggleLivePreviewChecked() const { return bLivePreview; } void FMaterialEditor::ToggleRealTimeExpressions() { bIsRealtime = !bIsRealtime; } bool FMaterialEditor::IsToggleRealTimeExpressionsChecked() const { return bIsRealtime == true; } void FMaterialEditor::OnAlwaysRefreshAllPreviews() { bAlwaysRefreshAllPreviews = !bAlwaysRefreshAllPreviews; if ( bAlwaysRefreshAllPreviews ) { RefreshExpressionPreviews(); } } bool FMaterialEditor::IsOnAlwaysRefreshAllPreviews() const { return bAlwaysRefreshAllPreviews == true; } void FMaterialEditor::ToggleStats() { // Toggle the showing of material stats each time the user presses the show stats button bShowStats = !bShowStats; UpdateMaterialInfoList(bShowStats); } bool FMaterialEditor::IsToggleStatsChecked() const { return bShowStats == true; } void FMaterialEditor::ToggleMobileStats() { // Toggle the showing of material stats each time the user presses the show stats button bShowMobileStats = !bShowMobileStats; UPreviewMaterial* PreviewMaterial = Cast(Material); if (PreviewMaterial) { { // Sync with the rendering thread but don't reregister components. We will manually do so. FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread); UpdateContext.AddMaterial(PreviewMaterial); PreviewMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,bShowMobileStats); PreviewMaterial->ForceRecompileForRendering(); if (!bStatsFromPreviewMaterial) { OriginalMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,bShowMobileStats); OriginalMaterial->ForceRecompileForRendering(); } } UpdateStatsMaterials(); RefreshPreviewViewport(); } UpdateMaterialInfoList(bShowMobileStats); } bool FMaterialEditor::IsToggleMobileStatsChecked() const { return bShowMobileStats == true; } void FMaterialEditor::OnUseCurrentTexture() { // Set the currently selected texture in the generic browser // as the texture to use in all selected texture sample expressions. FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop(); if ( SelectedTexture ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "UseCurrentTexture", "Use Current Texture") ); const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode && GraphNode->MaterialExpression->IsA(UMaterialExpressionTextureBase::StaticClass()) ) { UMaterialExpressionTextureBase* TextureBase = static_cast(GraphNode->MaterialExpression); TextureBase->Modify(); TextureBase->Texture = SelectedTexture; TextureBase->AutoSetSampleType(); } } // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); } } void FMaterialEditor::OnConvertObjects() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvert", "Material Editor: Convert") ); Material->Modify(); Material->MaterialGraph->Modify(); TArray NodesToDelete; TArray NodesToSelect; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { // Look for the supported classes to convert from UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionConstant* Constant1Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant2Vector* Constant2Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant3Vector* Constant3Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant4Vector* Constant4Expression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureSample* TextureSampleExpression = Cast(CurrentSelectedExpression); UMaterialExpressionComponentMask* ComponentMaskExpression = Cast(CurrentSelectedExpression); UMaterialExpressionParticleSubUV* ParticleSubUVExpression = Cast(CurrentSelectedExpression); UMaterialExpressionScalarParameter* ScalarParameterExpression = Cast(CurrentSelectedExpression); UMaterialExpressionVectorParameter* VectorParameterExpression = Cast(CurrentSelectedExpression); // Setup the class to convert to UClass* ClassToCreate = NULL; if (Constant1Expression) { ClassToCreate = UMaterialExpressionScalarParameter::StaticClass(); } else if (Constant2Expression || Constant3Expression || Constant4Expression) { ClassToCreate = UMaterialExpressionVectorParameter::StaticClass(); } else if (ParticleSubUVExpression) // Has to come before the TextureSample comparison... { ClassToCreate = UMaterialExpressionTextureSampleParameterSubUV::StaticClass(); } else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCube::StaticClass())) { ClassToCreate = UMaterialExpressionTextureSampleParameterCube::StaticClass(); } else if (TextureSampleExpression) { ClassToCreate = UMaterialExpressionTextureSampleParameter2D::StaticClass(); } else if (ComponentMaskExpression) { ClassToCreate = UMaterialExpressionStaticComponentMaskParameter::StaticClass(); } else if (ScalarParameterExpression) { ClassToCreate = UMaterialExpressionConstant::StaticClass(); } else if (VectorParameterExpression) { // Technically should be a constant 4 but UMaterialExpressionVectorParameter has an rgb pin, so using Constant3 to avoid a compile error ClassToCreate = UMaterialExpressionConstant3Vector::StaticClass(); } if (ClassToCreate) { UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), false, true ); if (NewExpression) { UMaterialGraphNode* NewGraphNode = CastChecked(NewExpression->GraphNode); NewGraphNode->ReplaceNode(GraphNode); bool bNeedsRefresh = false; // Copy over expression-specific values if (Constant1Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant1Expression->R; } else if (Constant2Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = FLinearColor(Constant2Expression->R, Constant2Expression->G, 0); } else if (Constant3Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant3Expression->Constant; CastChecked(NewExpression)->DefaultValue.A = 1.0f; } else if (Constant4Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant4Expression->Constant; } else if (TextureSampleExpression) { bNeedsRefresh = true; UMaterialExpressionTextureSampleParameter* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureSampleExpression->Texture; NewTextureExpr->Coordinates = TextureSampleExpression->Coordinates; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture; NewTextureExpr->TextureObject = TextureSampleExpression->TextureObject; NewTextureExpr->MipValue = TextureSampleExpression->MipValue; NewTextureExpr->MipValueMode = TextureSampleExpression->MipValueMode; } else if (ComponentMaskExpression) { bNeedsRefresh = true; UMaterialExpressionStaticComponentMaskParameter* ComponentMask = CastChecked(NewExpression); ComponentMask->DefaultR = ComponentMaskExpression->R; ComponentMask->DefaultG = ComponentMaskExpression->G; ComponentMask->DefaultB = ComponentMaskExpression->B; ComponentMask->DefaultA = ComponentMaskExpression->A; } else if (ParticleSubUVExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->Texture = ParticleSubUVExpression->Texture; } else if (ScalarParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->R = ScalarParameterExpression->DefaultValue; } else if (VectorParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->Constant = VectorParameterExpression->DefaultValue; } if (bNeedsRefresh) { // Refresh the expression preview if we changed its properties after it was created NewExpression->bNeedToUpdatePreview = true; RefreshExpressionPreview( NewExpression, true ); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { GraphEditor->SetNodeSelection(*NodeIter, true); } } } void FMaterialEditor::OnConvertTextures() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvertTexture", "Material Editor: Convert to Texture") ); Material->Modify(); Material->MaterialGraph->Modify(); TArray NodesToDelete; TArray NodesToSelect; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { // Look for the supported classes to convert from UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionTextureSample* TextureSampleExpression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureObject* TextureObjectExpression = Cast(CurrentSelectedExpression); // Setup the class to convert to UClass* ClassToCreate = NULL; if (TextureSampleExpression) { ClassToCreate = UMaterialExpressionTextureObject::StaticClass(); } else if (TextureObjectExpression) { ClassToCreate = UMaterialExpressionTextureSample::StaticClass(); } if (ClassToCreate) { UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), false, true); if (NewExpression) { UMaterialGraphNode* NewGraphNode = CastChecked(NewExpression->GraphNode); NewGraphNode->ReplaceNode(GraphNode); bool bNeedsRefresh = false; // Copy over expression-specific values if (TextureSampleExpression) { bNeedsRefresh = true; UMaterialExpressionTextureObject* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureSampleExpression->Texture; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture; } else if (TextureObjectExpression) { bNeedsRefresh = true; UMaterialExpressionTextureSample* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureObjectExpression->Texture; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureObjectExpression->IsDefaultMeshpaintTexture; NewTextureExpr->MipValueMode = TMVM_None; } if (bNeedsRefresh) { // Refresh the expression preview if we changed its properties after it was created NewExpression->bNeedToUpdatePreview = true; RefreshExpressionPreview( NewExpression, true ); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { GraphEditor->SetNodeSelection(*NodeIter, true); } } } void FMaterialEditor::OnPreviewNode() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { SetPreviewExpression(GraphNode->MaterialExpression); } } } } void FMaterialEditor::OnToggleRealtimePreview() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UMaterialExpression* SelectedExpression = GraphNode->MaterialExpression; SelectedExpression->bRealtimePreview = !SelectedExpression->bRealtimePreview; if (SelectedExpression->bRealtimePreview) { SelectedExpression->bCollapsed = false; } RefreshExpressionPreviews(); SetMaterialDirty(); } } } } void FMaterialEditor::OnSelectDownsteamNodes() { TArray NodesToCheck; TArray CheckedNodes; TArray NodesToSelect; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { NodesToCheck.Add(GraphNode); } } while (NodesToCheck.Num() > 0) { UMaterialGraphNode* CurrentNode = NodesToCheck.Last(); TArray OutputPins; CurrentNode->GetOutputPins(OutputPins); for (int32 Index = 0; Index < OutputPins.Num(); ++Index) { for (int32 LinkIndex = 0; LinkIndex < OutputPins[Index]->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(OutputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode()); if (LinkedNode) { int32 FoundIndex = -1; CheckedNodes.Find(LinkedNode, FoundIndex); if (FoundIndex < 0) { NodesToSelect.Add(LinkedNode); NodesToCheck.Add(LinkedNode); } } } } // This graph node has now been examined CheckedNodes.Add(CurrentNode); NodesToCheck.Remove(CurrentNode); } for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { GraphEditor->SetNodeSelection(NodesToSelect[Index], true); } } void FMaterialEditor::OnSelectUpsteamNodes() { TArray NodesToCheck; TArray CheckedNodes; TArray NodesToSelect; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { NodesToCheck.Add(GraphNode); } } while (NodesToCheck.Num() > 0) { UMaterialGraphNode* CurrentNode = NodesToCheck.Last(); TArray InputPins; CurrentNode->GetInputPins(InputPins); for (int32 Index = 0; Index < InputPins.Num(); ++Index) { for (int32 LinkIndex = 0; LinkIndex < InputPins[Index]->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(InputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode()); if (LinkedNode) { int32 FoundIndex = -1; CheckedNodes.Find(LinkedNode, FoundIndex); if (FoundIndex < 0) { NodesToSelect.Add(LinkedNode); NodesToCheck.Add(LinkedNode); } } } } // This graph node has now been examined CheckedNodes.Add(CurrentNode); NodesToCheck.Remove(CurrentNode); } for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { GraphEditor->SetNodeSelection(NodesToSelect[Index], true); } } void FMaterialEditor::OnForceRefreshPreviews() { ForceRefreshExpressionPreviews(); RefreshPreviewViewport(); } void FMaterialEditor::OnCreateComment() { CreateNewMaterialExpressionComment(GraphEditor->GetPasteLocation()); } void FMaterialEditor::OnCreateComponentMaskNode() { CreateNewMaterialExpression(UMaterialExpressionComponentMask::StaticClass(), GraphEditor->GetPasteLocation(), true, false); } void FMaterialEditor::OnFindInMaterial() { TabManager->InvokeTab(FindTabId); FindResults->FocusForUse(); } FString FMaterialEditor::GetDocLinkForSelectedNode() { FString DocumentationLink; TArray SelectedNodes = GraphEditor->GetSelectedNodes().Array(); if (SelectedNodes.Num() == 1) { UMaterialGraphNode* SelectedGraphNode = Cast(SelectedNodes[0]); if (SelectedGraphNode != NULL) { FString DocLink = SelectedGraphNode->GetDocumentationLink(); FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName(); DocumentationLink = FEditorClassUtils::GetDocumentationLinkFromExcerpt(DocLink, DocExcerpt); } } return DocumentationLink; } void FMaterialEditor::OnGoToDocumentation() { FString DocumentationLink = GetDocLinkForSelectedNode(); if (!DocumentationLink.IsEmpty()) { IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("rightclick_matnode"))); } } bool FMaterialEditor::CanGoToDocumentation() { FString DocumentationLink = GetDocLinkForSelectedNode(); return !DocumentationLink.IsEmpty(); } void FMaterialEditor::RenameAssetFromRegistry(const FAssetData& InAddedAssetData, const FString& InNewName) { // Grab the asset class, it will be checked for being a material function. UClass* Asset = FindObject(ANY_PACKAGE, *InAddedAssetData.AssetClass.ToString()); if(Asset->IsChildOf(UMaterialFunction::StaticClass())) { ForceRefreshExpressionPreviews(); } } void FMaterialEditor::OnAssetPostImport(UFactory* InFactory, UObject* InObject) { UTexture* Texture = Cast(InObject); if (InFactory->IsA(UTextureFactory::StaticClass()) && Texture != nullptr) { // When a texture which is referenced in the material is imported, update the preview material for (UMaterialExpression* Expression : Material->Expressions) { if (UMaterialExpressionTextureBase* ExpressionTexture = Cast(Expression)) { if (ExpressionTexture->Texture == Texture) { UpdateOriginalMaterial(); break; } } else if (UMaterialExpressionMaterialFunctionCall* ExpressionFunctionCall = Cast(Expression)) { struct Local { static bool ReferencesImportedObject(UMaterialFunction* Function, UTexture* InTexture) { for (UMaterialExpression* MaterialExpression : Function->FunctionExpressions) { if (UMaterialExpressionTextureBase* MaterialExpressionTexture = Cast(MaterialExpression)) { if (MaterialExpressionTexture->Texture == InTexture) { return true; } } else if (UMaterialExpressionMaterialFunctionCall* MaterialExpressionFunctionCall = Cast(MaterialExpression)) { if (MaterialExpressionFunctionCall->MaterialFunction != nullptr && ReferencesImportedObject(MaterialExpressionFunctionCall->MaterialFunction, InTexture)) { return true; } } } return false; } }; if (ExpressionFunctionCall->MaterialFunction != nullptr && Local::ReferencesImportedObject(ExpressionFunctionCall->MaterialFunction, Texture)) { UpdateOriginalMaterial(); break; } } } } } void FMaterialEditor::OnMaterialUsageFlagsChanged(UMaterial* MaterialThatChanged, int32 FlagThatChanged) { EMaterialUsage Flag = static_cast(FlagThatChanged); if(MaterialThatChanged == OriginalMaterial) { bool bNeedsRecompile = false; Material->SetMaterialUsage(bNeedsRecompile, Flag, MaterialThatChanged->GetUsageByFlag(Flag)); UpdateStatsMaterials(); } } void FMaterialEditor::SetVectorParameterDefaultOnDependentMaterials(FName ParameterName, const FLinearColor& Value, bool bOverride) { TArray MaterialsToOverride; if (MaterialFunction) { // Find all materials that reference this function for (TObjectIterator It; It; ++It) { UMaterial* CurrentMaterial = *It; if (CurrentMaterial != Material) { bool bUpdate = false; for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bUpdate = true; break; } } if (bUpdate) { MaterialsToOverride.Add(CurrentMaterial); } } } } else { MaterialsToOverride.Add(OriginalMaterial); } const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel; for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++) { UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex]; CurrentMaterial->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } // Update MI's that reference any of the materials affected for (TObjectIterator It; It; ++It) { UMaterialInstance* CurrentMaterialInstance = *It; // Only care about MI's with static parameters, because we are overriding parameter defaults, // And only MI's with static parameters contain uniform expressions, which contain parameter defaults if (CurrentMaterialInstance->bHasStaticPermutationResource) { UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial(); if (MaterialsToOverride.Contains(BaseMaterial)) { CurrentMaterialInstance->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } } } } void FMaterialEditor::OnVectorParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, const FLinearColor& Value) { check(Expression); if (Expression->Material == Material && OriginalMaterial) { SetVectorParameterDefaultOnDependentMaterials(ParameterName, Value, true); OverriddenVectorParametersToRevert.AddUnique(ParameterName); } } void FMaterialEditor::SetScalarParameterDefaultOnDependentMaterials(FName ParameterName, float Value, bool bOverride) { TArray MaterialsToOverride; if (MaterialFunction) { // Find all materials that reference this function for (TObjectIterator It; It; ++It) { UMaterial* CurrentMaterial = *It; if (CurrentMaterial != Material) { bool bUpdate = false; for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bUpdate = true; break; } } if (bUpdate) { MaterialsToOverride.Add(CurrentMaterial); } } } } else { MaterialsToOverride.Add(OriginalMaterial); } const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel; for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++) { UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex]; CurrentMaterial->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } // Update MI's that reference any of the materials affected for (TObjectIterator It; It; ++It) { UMaterialInstance* CurrentMaterialInstance = *It; // Only care about MI's with static parameters, because we are overriding parameter defaults, // And only MI's with static parameters contain uniform expressions, which contain parameter defaults if (CurrentMaterialInstance->bHasStaticPermutationResource) { UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial(); if (MaterialsToOverride.Contains(BaseMaterial)) { CurrentMaterialInstance->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } } } } void FMaterialEditor::OnScalarParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, float Value) { check(Expression); if (Expression->Material == Material && OriginalMaterial) { SetScalarParameterDefaultOnDependentMaterials(ParameterName, Value, true); OverriddenScalarParametersToRevert.AddUnique(ParameterName); } } TSharedRef FMaterialEditor::SpawnTab_Preview(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("ViewportTabTitle", "Viewport")) [ Viewport.ToSharedRef() ]; Viewport->OnAddedToTab( SpawnedTab ); return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("GraphCanvasTitle", "Graph")); if (GraphEditor.IsValid()) { SpawnedTab->SetContent(GraphEditor.ToSharedRef()); } return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_MaterialProperties(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Icon( FEditorStyle::GetBrush("LevelEditor.Tabs.Details") ) .Label( LOCTEXT("MaterialDetailsTitle", "Details") ) [ MaterialDetailsView.ToSharedRef() ]; if (GraphEditor.IsValid()) { // Since we're initialising, make sure nothing is selected GraphEditor->ClearSelectionSet(); } return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_HLSLCode(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("HLSLCodeTitle", "HLSL Code")) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ CodeViewUtility.ToSharedRef() ] +SVerticalBox::Slot() .FillHeight(1) [ CodeView.ToSharedRef() ] ]; CodeTab = SpawnedTab; RegenerateCodeView(); return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args) { check( Args.GetTabId() == PaletteTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.Palette")) .Label(LOCTEXT("MaterialPaletteTitle", "Palette")) [ SNew( SBox ) .AddMetaData(FTagMetaData(TEXT("MaterialPalette"))) [ Palette.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Stats(const FSpawnTabArgs& Args) { check( Args.GetTabId() == StatsTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.CompilerResults")) .Label(LOCTEXT("MaterialStatsTitle", "Stats")) [ SNew( SBox ) .AddMetaData(FTagMetaData(TEXT("MaterialStats"))) [ Stats.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Find(const FSpawnTabArgs& Args) { check(Args.GetTabId() == FindTabId); TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.FindResults")) .Label(LOCTEXT("MaterialFindTitle", "Find Results")) [ SNew(SBox) .AddMetaData(FTagMetaData(TEXT("MaterialFind"))) [ FindResults.ToSharedRef() ] ]; return SpawnedTab; } void FMaterialEditor::SetPreviewExpression(UMaterialExpression* NewPreviewExpression) { UMaterialExpressionFunctionOutput* FunctionOutput = Cast(NewPreviewExpression); if (!NewPreviewExpression || PreviewExpression == NewPreviewExpression) { if (FunctionOutput) { FunctionOutput->bLastPreviewed = false; } // If we are already previewing the selected expression toggle previewing off PreviewExpression = NULL; ExpressionPreviewMaterial->Expressions.Empty(); SetPreviewMaterial( Material ); // Recompile the preview material to get changes that might have been made during previewing UpdatePreviewMaterial(); } else if (NewPreviewExpression) { if( ExpressionPreviewMaterial == NULL ) { // Create the expression preview material if it hasnt already been created ExpressionPreviewMaterial = NewObject(GetTransientPackage(), NAME_None, RF_Public); ExpressionPreviewMaterial->bIsPreviewMaterial = true; } if (FunctionOutput) { FunctionOutput->bLastPreviewed = true; } else { //Hooking up the output of the break expression doesn't make much sense, preview the expression feeding it instead. UMaterialExpressionBreakMaterialAttributes* BreakExpr = Cast(NewPreviewExpression); if( BreakExpr && BreakExpr->GetInput(0) && BreakExpr->GetInput(0)->Expression ) { NewPreviewExpression = BreakExpr->GetInput(0)->Expression; } } // The expression preview material's expressions array must stay up to date before recompiling // So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated ExpressionPreviewMaterial->Expressions = Material->Expressions; // The preview window should now show the expression preview material SetPreviewMaterial( ExpressionPreviewMaterial ); // Set the preview expression PreviewExpression = NewPreviewExpression; // Recompile the preview material UpdatePreviewMaterial(); } } void FMaterialEditor::JumpToNode(const UEdGraphNode* Node) { GraphEditor->JumpToNode(Node, false); } UMaterialExpression* FMaterialEditor::CreateNewMaterialExpression(UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource) { check( NewExpressionClass->IsChildOf(UMaterialExpression::StaticClass()) ); if (!IsAllowedExpressionType(NewExpressionClass, MaterialFunction != NULL)) { // Disallowed types should not be visible to the ui to be placed, so we don't need a warning here return NULL; } // Clear the selection if ( bAutoSelect ) { GraphEditor->ClearSelectionSet(); } // Create the new expression. UMaterialExpression* NewExpression = NULL; { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorNewExpression", "Material Editor: New Expression") ); Material->Modify(); UObject* ExpressionOuter = Material; if (MaterialFunction) { ExpressionOuter = MaterialFunction; } NewExpression = NewObject(ExpressionOuter, NewExpressionClass, NAME_None, RF_Transactional); Material->Expressions.Add( NewExpression ); NewExpression->Material = Material; // Set the expression location. NewExpression->MaterialExpressionEditorX = NodePos.X; NewExpression->MaterialExpressionEditorY = NodePos.Y; // Create a GUID for the node NewExpression->UpdateMaterialExpressionGuid(true, true); if (bAutoAssignResource) { // If the user is adding a texture, automatically assign the currently selected texture to it. UMaterialExpressionTextureBase* METextureBase = Cast( NewExpression ); if( METextureBase ) { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); if( UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop() ) { METextureBase->Texture = SelectedTexture; } METextureBase->AutoSetSampleType(); } UMaterialExpressionMaterialFunctionCall* MEMaterialFunction = Cast( NewExpression ); if( MEMaterialFunction ) { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); MEMaterialFunction->SetMaterialFunction(MaterialFunction, NULL, GEditor->GetSelectedObjects()->GetTop()); } UMaterialExpressionCollectionParameter* MECollectionParameter = Cast( NewExpression ); if( MECollectionParameter ) { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); MECollectionParameter->Collection = GEditor->GetSelectedObjects()->GetTop(); } } UMaterialExpressionFunctionInput* FunctionInput = Cast( NewExpression ); if( FunctionInput ) { FunctionInput->ConditionallyGenerateId(true); FunctionInput->ValidateName(); } UMaterialExpressionFunctionOutput* FunctionOutput = Cast( NewExpression ); if( FunctionOutput ) { FunctionOutput->ConditionallyGenerateId(true); FunctionOutput->ValidateName(); } NewExpression->UpdateParameterGuid(true, true); UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast( NewExpression ); if( (TextureParameterExpression != nullptr) && TextureParameterExpression->CanRenameNode() ) { // Change the parameter's name on creation to mirror the object's name; this avoids issues of having colliding parameter // names and having the name left as "None" TextureParameterExpression->ParameterName = TextureParameterExpression->GetFName(); } UMaterialExpressionComponentMask* ComponentMaskExpression = Cast( NewExpression ); // Setup defaults for the most likely use case // Can't change default properties as that will affect existing content if( ComponentMaskExpression ) { ComponentMaskExpression->R = true; ComponentMaskExpression->G = true; } UMaterialExpressionStaticComponentMaskParameter* StaticComponentMaskExpression = Cast( NewExpression ); // Setup defaults for the most likely use case // Can't change default properties as that will affect existing content if( StaticComponentMaskExpression ) { StaticComponentMaskExpression->DefaultR = true; } UMaterialExpressionRotateAboutAxis* RotateAboutAxisExpression = Cast( NewExpression ); if( RotateAboutAxisExpression ) { // Create a default expression for the Position input UMaterialExpressionWorldPosition* WorldPositionExpression = NewObject(ExpressionOuter, NAME_None, RF_Transactional); Material->Expressions.Add( WorldPositionExpression ); WorldPositionExpression->Material = Material; RotateAboutAxisExpression->Position.Expression = WorldPositionExpression; WorldPositionExpression->MaterialExpressionEditorX = RotateAboutAxisExpression->MaterialExpressionEditorX - 250; WorldPositionExpression->MaterialExpressionEditorY = RotateAboutAxisExpression->MaterialExpressionEditorY + 73; Material->MaterialGraph->AddExpression(WorldPositionExpression); if ( bAutoSelect ) { GraphEditor->SetNodeSelection(WorldPositionExpression->GraphNode, true); } } // Setup defaults for the most likely use case // Can't change default properties as that will affect existing content UMaterialExpressionTransformPosition* PositionTransform = Cast(NewExpression); if (PositionTransform) { PositionTransform->TransformSourceType = TRANSFORMPOSSOURCE_Local; PositionTransform->TransformType = TRANSFORMPOSSOURCE_World; } // Make sure the dynamic parameters are named based on existing ones UMaterialExpressionDynamicParameter* DynamicExpression = Cast(NewExpression); if (DynamicExpression) { DynamicExpression->UpdateDynamicParameterNames(); } Material->AddExpressionParameter(NewExpression, Material->EditorParameters); if (NewExpression) { Material->MaterialGraph->AddExpression(NewExpression); // Select the new node. if ( bAutoSelect ) { GraphEditor->SetNodeSelection(NewExpression->GraphNode, true); } } } RegenerateCodeView(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RefreshExpressionPreviews(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); return NewExpression; } UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos) { UMaterialExpressionComment* NewComment = NULL; { Material->Modify(); UObject* ExpressionOuter = Material; if (MaterialFunction) { ExpressionOuter = MaterialFunction; } NewComment = NewObject(ExpressionOuter, NAME_None, RF_Transactional); // Add to the list of comments associated with this material. Material->EditorComments.Add( NewComment ); FSlateRect Bounds; if (GraphEditor->GetBoundsForSelectedNodes(Bounds, 50.0f)) { NewComment->MaterialExpressionEditorX = Bounds.Left; NewComment->MaterialExpressionEditorY = Bounds.Top; FVector2D Size = Bounds.GetSize(); NewComment->SizeX = Size.X; NewComment->SizeY = Size.Y; } else { NewComment->MaterialExpressionEditorX = NodePos.X; NewComment->MaterialExpressionEditorY = NodePos.Y; NewComment->SizeX = 400; NewComment->SizeY = 100; } NewComment->Text = NSLOCTEXT("K2Node", "CommentBlock_NewEmptyComment", "Comment").ToString(); } if (NewComment) { Material->MaterialGraph->AddComment(NewComment); // Select the new comment. GraphEditor->ClearSelectionSet(); GraphEditor->SetNodeSelection(NewComment->GraphNode, true); } Material->MarkPackageDirty(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); return NewComment; } void FMaterialEditor::ForceRefreshExpressionPreviews() { // Initialize expression previews. const bool bOldAlwaysRefreshAllPreviews = bAlwaysRefreshAllPreviews; bAlwaysRefreshAllPreviews = true; RefreshExpressionPreviews(); bAlwaysRefreshAllPreviews = bOldAlwaysRefreshAllPreviews; } void FMaterialEditor::AddToSelection(UMaterialExpression* Expression) { GraphEditor->SetNodeSelection(Expression->GraphNode, true); } void FMaterialEditor::SelectAllNodes() { GraphEditor->SelectAllNodes(); } bool FMaterialEditor::CanSelectAllNodes() const { return GraphEditor.IsValid(); } void FMaterialEditor::DeleteSelectedNodes() { TArray NodesToDelete; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { NodesToDelete.Add(CastChecked(*NodeIt)); } DeleteNodes(NodesToDelete); } void FMaterialEditor::DeleteNodes(const TArray& NodesToDelete) { if (NodesToDelete.Num() > 0) { if (!CheckExpressionRemovalWarnings(NodesToDelete)) { return; } // If we are previewing an expression and the expression being previewed was deleted bool bHaveExpressionsToDelete = false; bool bPreviewExpressionDeleted = false; { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorDelete", "Material Editor: Delete") ); Material->Modify(); for (int32 Index = 0; Index < NodesToDelete.Num(); ++Index) { if (NodesToDelete[Index]->CanUserDeleteNode()) { // Break all node links first so that we don't update the material before deleting NodesToDelete[Index]->BreakAllNodeLinks(); FBlueprintEditorUtils::RemoveNode(NULL, NodesToDelete[Index], true); if (UMaterialGraphNode* GraphNode = Cast(NodesToDelete[Index])) { UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression; bHaveExpressionsToDelete = true; DestroyColorPicker(); if( PreviewExpression == MaterialExpression ) { // The expression being previewed is also being deleted bPreviewExpressionDeleted = true; } MaterialExpression->Modify(); Material->Expressions.Remove( MaterialExpression ); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkPendingKill(); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(NodesToDelete[Index])) { CommentNode->MaterialExpressionComment->Modify(); Material->EditorComments.Remove( CommentNode->MaterialExpressionComment ); } } } Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction // Deselect all expressions and comments. GraphEditor->ClearSelectionSet(); GraphEditor->NotifyGraphChanged(); if ( bHaveExpressionsToDelete ) { if( bPreviewExpressionDeleted ) { // The preview expression was deleted. Null out our reference to it and reset to the normal preview material PreviewExpression = NULL; SetPreviewMaterial( Material ); } RegenerateCodeView(); } UpdatePreviewMaterial(); Material->MarkPackageDirty(); SetMaterialDirty(); if ( bHaveExpressionsToDelete ) { RefreshExpressionPreviews(); } } } bool FMaterialEditor::CanDeleteNodes() const { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (Cast(*NodeIt)) { // Return false if only root node is selected, as it can't be deleted return false; } } } return SelectedNodes.Num() > 0; } void FMaterialEditor::DeleteSelectedDuplicatableNodes() { // Cache off the old selection const FGraphPanelSelectionSet OldSelectedNodes = GraphEditor->GetSelectedNodes(); // Clear the selection and only select the nodes that can be duplicated FGraphPanelSelectionSet RemainingNodes; GraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { GraphEditor->SetNodeSelection(Node, true); } else { RemainingNodes.Add(Node); } } // Delete the duplicatable nodes DeleteSelectedNodes(); // Reselect whatever's left from the original selection after the deletion GraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter) { if (UEdGraphNode* Node = Cast(*SelectedIter)) { GraphEditor->SetNodeSelection(Node, true); } } } void FMaterialEditor::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); FString ExportedText; for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { if(UEdGraphNode* Node = Cast(*SelectedIter)) { Node->PrepareForCopying(); } } FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText); FPlatformMisc::ClipboardCopy(*ExportedText); // Make sure Material remains the owner of the copied nodes for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { if (UMaterialGraphNode* Node = Cast(*SelectedIter)) { Node->PostCopyNode(); } else if (UMaterialGraphNode_Comment* Comment = Cast(*SelectedIter)) { Comment->PostCopyNode(); } } } bool FMaterialEditor::CanCopyNodes() const { // If any of the nodes can be duplicated then we should allow copying const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { return true; } } return false; } void FMaterialEditor::PasteNodes() { PasteNodesHere(GraphEditor->GetPasteLocation()); } void FMaterialEditor::PasteNodesHere(const FVector2D& Location) { // Undo/Redo support const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorPaste", "Material Editor: Paste") ); Material->MaterialGraph->Modify(); Material->Modify(); // Clear the selection set (newly pasted stuff will be selected) GraphEditor->ClearSelectionSet(); // Grab the text to paste from the clipboard. FString TextToImport; FPlatformMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(Material->MaterialGraph, TextToImport, /*out*/ PastedNodes); //Average position of nodes so we can move them while still maintaining relative distances to each other FVector2D AvgNodePosition(0.0f,0.0f); for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* Node = *It; AvgNodePosition.X += Node->NodePosX; AvgNodePosition.Y += Node->NodePosY; } if ( PastedNodes.Num() > 0 ) { float InvNumNodes = 1.0f/float(PastedNodes.Num()); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; } for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* Node = *It; if (UMaterialGraphNode* GraphNode = Cast(Node)) { // These are not copied and we must account for expressions pasted between different materials anyway GraphNode->RealtimeDelegate = Material->MaterialGraph->RealtimeDelegate; GraphNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate; GraphNode->bPreviewNeedsUpdate = false; UMaterialExpression* NewExpression = GraphNode->MaterialExpression; NewExpression->Material = Material; NewExpression->Function = NULL; Material->Expressions.Add(NewExpression); // There can be only one default mesh paint texture. UMaterialExpressionTextureBase* TextureSample = Cast( NewExpression ); if( TextureSample ) { TextureSample->IsDefaultMeshpaintTexture = false; } NewExpression->UpdateParameterGuid(true, true); UMaterialExpressionFunctionInput* FunctionInput = Cast( NewExpression ); if( FunctionInput ) { FunctionInput->ConditionallyGenerateId(true); FunctionInput->ValidateName(); } UMaterialExpressionFunctionOutput* FunctionOutput = Cast( NewExpression ); if( FunctionOutput ) { FunctionOutput->ConditionallyGenerateId(true); FunctionOutput->ValidateName(); } } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) { CommentNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate; CommentNode->MaterialExpressionComment->Material = Material; Material->EditorComments.Add(CommentNode->MaterialExpressionComment); } // Select the newly pasted stuff GraphEditor->SetNodeSelection(Node, true); Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X ; Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y ; Node->SnapToGrid(SNodePanel::GetSnapGridSize()); // Give new node a different Guid from the old one Node->CreateNewGuid(); } UpdateMaterialAfterGraphChange(); // Update UI GraphEditor->NotifyGraphChanged(); } bool FMaterialEditor::CanPasteNodes() const { FString ClipboardContent; FPlatformMisc::ClipboardPaste(ClipboardContent); return FEdGraphUtilities::CanImportNodesFromText(Material->MaterialGraph, ClipboardContent); } void FMaterialEditor::CutSelectedNodes() { CopySelectedNodes(); // Cut should only delete nodes that can be duplicated DeleteSelectedDuplicatableNodes(); } bool FMaterialEditor::CanCutNodes() const { return CanCopyNodes() && CanDeleteNodes(); } void FMaterialEditor::DuplicateNodes() { // Copy and paste current selection CopySelectedNodes(); PasteNodes(); } bool FMaterialEditor::CanDuplicateNodes() const { return CanCopyNodes(); } FText FMaterialEditor::GetOriginalObjectName() const { return FText::FromString(GetEditingObjects()[0]->GetName()); } void FMaterialEditor::UpdateMaterialAfterGraphChange() { Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); } int32 FMaterialEditor::GetNumberOfSelectedNodes() const { return GraphEditor->GetSelectedNodes().Num(); } FMaterialRenderProxy* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) { bool bNewlyCreated; return GetExpressionPreview(InExpression, bNewlyCreated); } void FMaterialEditor::UndoGraphAction() { int32 NumExpressions = Material->Expressions.Num(); GEditor->UndoTransaction(); if(NumExpressions != Material->Expressions.Num()) { Material->BuildEditorParameterList(); } } void FMaterialEditor::RedoGraphAction() { // Clear selection, to avoid holding refs to nodes that go away GraphEditor->ClearSelectionSet(); int32 NumExpressions = Material->Expressions.Num(); GEditor->RedoTransaction(); if(NumExpressions != Material->Expressions.Num()) { Material->BuildEditorParameterList(); } } void FMaterialEditor::PostUndo(bool bSuccess) { if (bSuccess) { GraphEditor->ClearSelectionSet(); Material->BuildEditorParameterList(); // Update the current preview material. UpdatePreviewMaterial(); RefreshExpressionPreviews(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); } } void FMaterialEditor::NotifyPreChange(UProperty* PropertyAboutToChange) { check( !ScopedTransaction ); ScopedTransaction = new FScopedTransaction( NSLOCTEXT("UnrealEd", "MaterialEditorEditProperties", "Material Editor: Edit Properties") ); FlushRenderingCommands(); } void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) { check( ScopedTransaction ); if ( PropertyThatChanged ) { const FName NameOfPropertyThatChanged( *PropertyThatChanged->GetName() ); if ((NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialInterface, PreviewMesh)) || (NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, bUsedWithSkeletalMesh))) { // SetPreviewMesh will return false if the material has bUsedWithSkeletalMesh and // a skeleton was requested, in which case revert to a sphere static mesh. if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString())) { SetPreviewAsset(GUnrealEd->GetThumbnailManager()->EditorSphere); } } FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* SelectedNode = Cast(*NodeIt); if (SelectedNode && SelectedNode->MaterialExpression) { if(NameOfPropertyThatChanged == FName(TEXT("ParameterName"))) { Material->UpdateExpressionParameterName(SelectedNode->MaterialExpression); } else if (NameOfPropertyThatChanged == FName(TEXT("ParamNames"))) { Material->UpdateExpressionDynamicParameterNames(SelectedNode->MaterialExpression); } else { Material->PropagateExpressionParameterChanges(SelectedNode->MaterialExpression); } } } } // Prevent constant recompilation of materials while properties are being interacted with if( PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive ) { // Also prevent recompilation when properties have no effect on material output const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; if (PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, Text) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, CommentColor) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpression, Desc)) { // Update the current preview material. UpdatePreviewMaterial(); RefreshExpressionPreviews(); RegenerateCodeView(); } } delete ScopedTransaction; ScopedTransaction = NULL; Material->MarkPackageDirty(); SetMaterialDirty(); GetDefault()->ForceVisualizationCacheClear(); } void FMaterialEditor::ToggleCollapsed(UMaterialExpression* MaterialExpression) { check( MaterialExpression ); { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorToggleCollapsed", "Material Editor: Toggle Collapsed") ); MaterialExpression->Modify(); MaterialExpression->bCollapsed = !MaterialExpression->bCollapsed; } MaterialExpression->PreEditChange( NULL ); MaterialExpression->PostEditChange(); MaterialExpression->MarkPackageDirty(); SetMaterialDirty(); // Update the preview. RefreshExpressionPreview( MaterialExpression, true ); RefreshPreviewViewport(); } void FMaterialEditor::RefreshExpressionPreviews() { const FScopedBusyCursor BusyCursor; if ( bAlwaysRefreshAllPreviews ) { // we need to make sure the rendering thread isn't drawing these tiles SCOPED_SUSPEND_RENDERING_THREAD(true); // Refresh all expression previews. ExpressionPreviews.Empty(); } else { // Only refresh expressions that are marked for realtime update. for ( int32 ExpressionIndex = 0 ; ExpressionIndex < Material->Expressions.Num() ; ++ExpressionIndex ) { UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ]; RefreshExpressionPreview( MaterialExpression, false ); } } TArray ExpressionPreviewsBeingCompiled; ExpressionPreviewsBeingCompiled.Empty(50); // Go through all expression previews and create new ones as needed, and maintain a list of previews that are being compiled for( int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ++ExpressionIndex ) { UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ]; if (MaterialExpression && !MaterialExpression->IsA(UMaterialExpressionComment::StaticClass()) ) { bool bNewlyCreated; FMatExpressionPreview* Preview = GetExpressionPreview( MaterialExpression, bNewlyCreated ); if (bNewlyCreated && Preview) { ExpressionPreviewsBeingCompiled.Add(Preview); } } } } void FMaterialEditor::RefreshExpressionPreview(UMaterialExpression* MaterialExpression, bool bRecompile) { if ( (MaterialExpression->bRealtimePreview || MaterialExpression->bNeedToUpdatePreview) && !MaterialExpression->bCollapsed ) { for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex ) { FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex]; if( ExpressionPreview.GetExpression() == MaterialExpression ) { // we need to make sure the rendering thread isn't drawing this tile SCOPED_SUSPEND_RENDERING_THREAD(true); ExpressionPreviews.RemoveAt( PreviewIndex ); MaterialExpression->bNeedToUpdatePreview = false; if (bRecompile) { bool bNewlyCreated; GetExpressionPreview(MaterialExpression, bNewlyCreated); } break; } } } } FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* MaterialExpression, bool& bNewlyCreated) { bNewlyCreated = false; if (!MaterialExpression->bHidePreviewWindow && !MaterialExpression->bCollapsed) { FMatExpressionPreview* Preview = NULL; for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex ) { FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex]; if( ExpressionPreview.GetExpression() == MaterialExpression ) { Preview = &ExpressionPreviews[PreviewIndex]; break; } } if( !Preview ) { bNewlyCreated = true; Preview = new(ExpressionPreviews) FMatExpressionPreview(MaterialExpression); Preview->CacheShaders(GMaxRHIShaderPlatform, true); } return Preview; } return NULL; } void FMaterialEditor::PreColorPickerCommit(FLinearColor LinearColor) { // Begin a property edit transaction. if ( GEditor ) { GEditor->BeginTransaction( LOCTEXT("ModifyColorPicker", "Modify Color Picker Value") ); } NotifyPreChange(NULL); UObject* Object = ColorPickerObject.Get(false); if( Object ) { Object->PreEditChange(NULL); } } void FMaterialEditor::OnColorPickerCommitted(FLinearColor LinearColor) { UObject* Object = ColorPickerObject.Get(false); if( Object ) { Object->MarkPackageDirty(); FPropertyChangedEvent Event(ColorPickerProperty.Get(false)); Object->PostEditChangeProperty(Event); } NotifyPostChange(NULL,NULL); if ( GEditor ) { GEditor->EndTransaction(); } RefreshExpressionPreviews(); } TSharedRef FMaterialEditor::CreateGraphEditorWidget() { GraphEditorCommands = MakeShareable( new FUICommandList ); { // Editing commands GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll, FExecuteAction::CreateSP( this, &FMaterialEditor::SelectAllNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanSelectAllNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Delete, FExecuteAction::CreateSP( this, &FMaterialEditor::DeleteSelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDeleteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Copy, FExecuteAction::CreateSP( this, &FMaterialEditor::CopySelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCopyNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Paste, FExecuteAction::CreateSP( this, &FMaterialEditor::PasteNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanPasteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Cut, FExecuteAction::CreateSP( this, &FMaterialEditor::CutSelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCutNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP( this, &FMaterialEditor::DuplicateNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDuplicateNodes ) ); // Graph Editor Commands GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment, FExecuteAction::CreateSP( this, &FMaterialEditor::OnCreateComment ) ); // Material specific commands GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().UseCurrentTexture, FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureSamples, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToConstant, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StopPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StartPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().EnableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().DisableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectDownstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownsteamNodes) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectUpstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpsteamNodes) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().RemoveFromFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().AddToFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ForceRefreshPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().CreateComponentMaskNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnCreateComponentMaskNode) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().GoToDocumentation, FExecuteAction::CreateSP(this, &FMaterialEditor::OnGoToDocumentation), FCanExecuteAction::CreateSP(this, &FMaterialEditor::CanGoToDocumentation) ); } FGraphAppearanceInfo AppearanceInfo; if (MaterialFunction) { AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialFunction", "MATERIAL FUNCTION"); } else { AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Material", "MATERIAL"); } SGraphEditor::FGraphEditorEvents InEvents; InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FMaterialEditor::OnSelectedNodesChanged); InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FMaterialEditor::OnNodeDoubleClicked); InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FMaterialEditor::OnNodeTitleCommitted); InEvents.OnSpawnNodeByShortcut = SGraphEditor::FOnSpawnNodeByShortcut::CreateSP(this, &FMaterialEditor::OnSpawnGraphNodeByShortcut, CastChecked(Material->MaterialGraph)); // Create the title bar widget TSharedPtr TitleBarWidget = SNew(SMaterialEditorTitleBar) .TitleText(this, &FMaterialEditor::GetOriginalObjectName); //.MaterialInfoList(&MaterialInfoList); return SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .IsEditable(true) .TitleBar(TitleBarWidget) .Appearance(AppearanceInfo) .GraphToEdit(Material->MaterialGraph) .GraphEvents(InEvents) .ShowGraphStateOverlay(false); } void FMaterialEditor::CleanUnusedExpressions() { TArray UnusedNodes; Material->MaterialGraph->GetUnusedExpressions(UnusedNodes); if (UnusedNodes.Num() > 0 && CheckExpressionRemovalWarnings(UnusedNodes)) { { // Kill off expressions referenced by the material that aren't reachable. const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorCleanUnusedExpressions", "Material Editor: Clean Unused Expressions") ); Material->Modify(); Material->MaterialGraph->Modify(); for (int32 Index = 0; Index < UnusedNodes.Num(); ++Index) { UMaterialGraphNode* GraphNode = CastChecked(UnusedNodes[Index]); UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression; FBlueprintEditorUtils::RemoveNode(NULL, GraphNode, true); if (PreviewExpression == MaterialExpression) { SetPreviewExpression(NULL); } MaterialExpression->Modify(); Material->Expressions.Remove(MaterialExpression); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkPendingKill(); } Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction GraphEditor->ClearSelectionSet(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); } } bool FMaterialEditor::CheckExpressionRemovalWarnings(const TArray& NodesToRemove) { FString FunctionWarningString; bool bFirstExpression = true; for (int32 Index = 0; Index < NodesToRemove.Num(); ++Index) { if (UMaterialGraphNode* GraphNode = Cast(NodesToRemove[Index])) { UMaterialExpressionFunctionInput* FunctionInput = Cast(GraphNode->MaterialExpression); UMaterialExpressionFunctionOutput* FunctionOutput = Cast(GraphNode->MaterialExpression); if (FunctionInput) { if (!bFirstExpression) { FunctionWarningString += TEXT(", "); } bFirstExpression = false; FunctionWarningString += FunctionInput->InputName; } if (FunctionOutput) { if (!bFirstExpression) { FunctionWarningString += TEXT(", "); } bFirstExpression = false; FunctionWarningString += FunctionOutput->OutputName; } } } if (FunctionWarningString.Len() > 0) { if (EAppReturnType::Yes != FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorDeleteFunctionInputs", "Delete function inputs or outputs \"{0}\"?\nAny materials which use this function will lose their connections to these function inputs or outputs once deleted."), FText::FromString(FunctionWarningString) ))) { // User said don't delete return false; } } return true; } void FMaterialEditor::RemoveSelectedExpressionFromFavorites() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UMaterialGraphNode* GraphNode = Cast(*NodeIt)) { MaterialExpressionClasses::Get()->RemoveMaterialExpressionFromFavorites(GraphNode->MaterialExpression->GetClass()); EditorOptions->FavoriteExpressions.Remove(GraphNode->MaterialExpression->GetClass()->GetName()); EditorOptions->SaveConfig(); } } } } void FMaterialEditor::AddSelectedExpressionToFavorites() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UMaterialGraphNode* GraphNode = Cast(*NodeIt)) { MaterialExpressionClasses::Get()->AddMaterialExpressionToFavorites(GraphNode->MaterialExpression->GetClass()); EditorOptions->FavoriteExpressions.AddUnique(GraphNode->MaterialExpression->GetClass()->GetName()); EditorOptions->SaveConfig(); } } } } void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSelection) { TArray SelectedObjects; UObject* EditObject = Material; if (MaterialFunction) { EditObject = MaterialFunction; } if( NewSelection.Num() == 0 ) { SelectedObjects.Add(EditObject); } else { for(TSet::TConstIterator SetIt(NewSelection);SetIt;++SetIt) { if (UMaterialGraphNode* GraphNode = Cast(*SetIt)) { SelectedObjects.Add(GraphNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(*SetIt)) { SelectedObjects.Add(CommentNode->MaterialExpressionComment); } else { SelectedObjects.Add(EditObject); } } } GetDetailView()->SetObjects( SelectedObjects, true ); } void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) { UMaterialGraphNode* GraphNode = Cast(Node); if (GraphNode) { UMaterialExpressionConstant3Vector* Constant3Expression = Cast(GraphNode->MaterialExpression); UMaterialExpressionConstant4Vector* Constant4Expression = Cast(GraphNode->MaterialExpression); UMaterialExpressionFunctionInput* InputExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionVectorParameter* VectorExpression = Cast(GraphNode->MaterialExpression); FColorChannels ChannelEditStruct; // Reset to default ColorPickerProperty = NULL; if( Constant3Expression ) { ChannelEditStruct.Red = &Constant3Expression->Constant.R; ChannelEditStruct.Green = &Constant3Expression->Constant.G; ChannelEditStruct.Blue = &Constant3Expression->Constant.B; } else if( Constant4Expression ) { ChannelEditStruct.Red = &Constant4Expression->Constant.R; ChannelEditStruct.Green = &Constant4Expression->Constant.G; ChannelEditStruct.Blue = &Constant4Expression->Constant.B; ChannelEditStruct.Alpha = &Constant4Expression->Constant.A; } else if (InputExpression) { ChannelEditStruct.Red = &InputExpression->PreviewValue.X; ChannelEditStruct.Green = &InputExpression->PreviewValue.Y; ChannelEditStruct.Blue = &InputExpression->PreviewValue.Z; ChannelEditStruct.Alpha = &InputExpression->PreviewValue.W; } else if (VectorExpression) { ChannelEditStruct.Red = &VectorExpression->DefaultValue.R; ChannelEditStruct.Green = &VectorExpression->DefaultValue.G; ChannelEditStruct.Blue = &VectorExpression->DefaultValue.B; ChannelEditStruct.Alpha = &VectorExpression->DefaultValue.A; static FName DefaultValueName = FName(TEXT("DefaultValue")); // Store off the property the color picker will be manipulating, so we can construct a useful PostEditChangeProperty later ColorPickerProperty = VectorExpression->GetClass()->FindPropertyByName(DefaultValueName); } if (ChannelEditStruct.Red || ChannelEditStruct.Green || ChannelEditStruct.Blue || ChannelEditStruct.Alpha) { TArray Channels; Channels.Add(ChannelEditStruct); ColorPickerObject = GraphNode->MaterialExpression; // Open a color picker that only sends updates when Ok is clicked, // Since it is too slow to recompile preview expressions as the user is picking different colors FColorPickerArgs PickerArgs; PickerArgs.ParentWidget = GraphEditor;//AsShared(); PickerArgs.bUseAlpha = Constant4Expression != NULL || VectorExpression != NULL; PickerArgs.bOnlyRefreshOnOk = true; PickerArgs.bExpandAdvancedSection = true; PickerArgs.DisplayGamma = TAttribute::Create( TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) ); PickerArgs.ColorChannelsArray = &Channels; PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FMaterialEditor::OnColorPickerCommitted); PickerArgs.PreColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FMaterialEditor::PreColorPickerCommit); OpenColorPicker(PickerArgs); } UMaterialExpressionTextureSample* TextureExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionMaterialFunctionCall* FunctionExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionCollectionParameter* CollectionParameter = Cast(GraphNode->MaterialExpression); TArray ObjectsToView; UObject* ObjectToEdit = NULL; if (TextureExpression && TextureExpression->Texture) { ObjectsToView.Add(TextureExpression->Texture); } else if (TextureParameterExpression && TextureParameterExpression->Texture) { ObjectsToView.Add(TextureParameterExpression->Texture); } else if (FunctionExpression && FunctionExpression->MaterialFunction) { ObjectToEdit = FunctionExpression->MaterialFunction; } else if (CollectionParameter && CollectionParameter->Collection) { ObjectToEdit = CollectionParameter->Collection; } if (ObjectsToView.Num() > 0) { GEditor->SyncBrowserToObjects(ObjectsToView); } if (ObjectToEdit) { FAssetEditorManager::Get().OpenEditorForAsset(ObjectToEdit); } } } void FMaterialEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged) { if (NodeBeingChanged) { const FScopedTransaction Transaction( LOCTEXT( "RenameNode", "Rename Node" ) ); NodeBeingChanged->Modify(); NodeBeingChanged->OnRenameNode(NewText.ToString()); } } FReply FMaterialEditor::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2D& InPosition, UEdGraph* InGraph) { UEdGraph* Graph = InGraph; TSharedPtr< FEdGraphSchemaAction > Action = FMaterialEditorSpawnNodeCommands::Get().GetGraphActionByChord(InChord, InGraph); if(Action.IsValid()) { TArray DummyPins; Action->PerformAction(Graph, DummyPins, InPosition); return FReply::Handled(); } return FReply::Unhandled(); } void FMaterialEditor::UpdateStatsMaterials() { if (bShowBuiltinStats && bStatsFromPreviewMaterial) { UMaterial* StatsMaterial = Material; FString EmptyMaterialName = FString(TEXT("MEStatsMaterial_Empty_")) + Material->GetName(); EmptyMaterial = (UMaterial*)StaticDuplicateObject(Material, GetTransientPackage(), *EmptyMaterialName, ~RF_Standalone, UPreviewMaterial::StaticClass()); EmptyMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2, bShowMobileStats); EmptyMaterial->Expressions.Empty(); //Disconnect all properties from the expressions for (int32 PropIdx = 0; PropIdx < MP_MAX; ++PropIdx) { FExpressionInput* ExpInput = EmptyMaterial->GetExpressionInputForProperty((EMaterialProperty)PropIdx); if(ExpInput) { ExpInput->Expression = NULL; } } EmptyMaterial->bAllowDevelopmentShaderCompile = Material->bAllowDevelopmentShaderCompile; EmptyMaterial->PreEditChange(NULL); EmptyMaterial->PostEditChange(); } } #undef LOCTEXT_NAMESPACE