You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Newly installed versions of the engine will now attempt to copy the project-agnostic config settings from a previous engine installation. This happens by way of a versioned manifest that copies old versions when the manifest does not exist, or is a different version. This code path is benign for non-installed versions of the engine (or FPaths::ShouldSaveToUserDir() is false). EditorGameAgnosticSettings and EditorUserSettings ini paths have been renamed to EditorSettings and EditorPerProjectUserSettings respectively to better convey their purpose. In general, most settings should be saved in EditorSettings (project-agnostic) so that they apply regardless of which project is open. We have some way to go migrating existing settings for this to be the case, however. Some previously per-project configuration files are now project-agnostic (such as Editor.ini, EditorKeyBindings.ini, and EditorLayout.ini) GEditor->Access...Settings and GEditor->Get...Settings have been removed in favor of direct access of the CDO through GetMutableDefault<> and GetDefault<> respectively. Global config ini filenames that are not set up are now neither loaded nor saved on build machines, to handle the problem of indeterminate state more generically. This addresses UETOOL-270 (Most editor preferences should be project-agnostic) [CL 2517558 by Andrew Rodham in Main branch]
4185 lines
138 KiB
C++
4185 lines
138 KiB
C++
// 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<int32> 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<UMaterialGraphNode>(Expression->GraphNode)->bPreviewNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/////////////////////
|
|
// FMaterialEditor //
|
|
/////////////////////
|
|
|
|
void FMaterialEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& 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<class FTabManager>& 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<UMaterial>();
|
|
{
|
|
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<UMaterialGraph>(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<FAssetRegistryModule>(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<FTabManager::FLayout> 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<IMaterialEditorModule>( "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<UMaterialExpressionFunctionOutput>(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<FString>* OutGroups)
|
|
{
|
|
for (int32 MaterialExpressionIndex = 0; MaterialExpressionIndex < Material->Expressions.Num(); ++MaterialExpressionIndex)
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ];
|
|
UMaterialExpressionParameter *Switch = Cast<UMaterialExpressionParameter>(MaterialExpression);
|
|
UMaterialExpressionTextureSampleParameter *TextureS = Cast<UMaterialExpressionTextureSampleParameter>(MaterialExpression);
|
|
UMaterialExpressionFontSampleParameter *FontS = Cast<UMaterialExpressionFontSampleParameter>(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<FPropertyEditorModule>( "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<FMessageLogModule>("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<FAssetToolsModule>("AssetTools");
|
|
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( MatInterface->GetClass() );
|
|
if ( AssetTypeActions.IsValid() )
|
|
{
|
|
USceneThumbnailInfoWithPrimitive* OriginalThumbnailInfo = Cast<USceneThumbnailInfoWithPrimitive>(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<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar )
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "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<USkeletalMesh>(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<UPackage*> 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<FString>& 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<FString> Descriptions;
|
|
TArray<int32> 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<UMaterialExpressionFunctionOutput>(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<UMaterialEditorOptions>();
|
|
|
|
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<UObject*> EditedAssets = AssetEditorManager.GetAllEditedAssets();
|
|
|
|
for (int32 AssetIdx = 0; AssetIdx < EditedAssets.Num(); AssetIdx++)
|
|
{
|
|
UObject* EditedAsset = EditedAssets[AssetIdx];
|
|
|
|
UMaterialInstance* SourceInstance = Cast<UMaterialInstance>(EditedAsset);
|
|
|
|
if(!SourceInstance)
|
|
{
|
|
// Check to see if the EditedAssets are from material instance editor
|
|
UMaterialEditorInstanceConstant* EditorInstance = Cast<UMaterialEditorInstanceConstant>(EditedAsset);
|
|
if(EditorInstance && EditorInstance->SourceInstance)
|
|
{
|
|
SourceInstance = Cast<UMaterialInstance>(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<FMaterialInstanceEditor*>(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<UMaterial> 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<UParticleSystemComponent> 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<class FTokenizedMessage> > Messages;
|
|
|
|
TArray<TSharedPtr<FMaterialInfo>> 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<FString> 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<FString> Descriptions;
|
|
TArray<int32> InstructionCounts;
|
|
TArray<FString> EmptyDescriptions;
|
|
TArray<int32> 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<FTokenizedMessage> 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<FTokenizedMessage> 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<FTokenizedMessage> 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<SWidget> TitleBar = GraphEditor->GetTitleBar();
|
|
if (TitleBar.IsValid())
|
|
{
|
|
StaticCastSharedPtr<SMaterialEditorTitleBar>(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<UMaterialGraphNode>(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<UPreviewMaterial>(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<UTexture>();
|
|
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<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode && GraphNode->MaterialExpression->IsA(UMaterialExpressionTextureBase::StaticClass()) )
|
|
{
|
|
UMaterialExpressionTextureBase* TextureBase = static_cast<UMaterialExpressionTextureBase*>(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<class UEdGraphNode*> NodesToDelete;
|
|
TArray<class UEdGraphNode*> NodesToSelect;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
// Look for the supported classes to convert from
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionConstant* Constant1Expression = Cast<UMaterialExpressionConstant>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant2Vector* Constant2Expression = Cast<UMaterialExpressionConstant2Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant3Vector* Constant3Expression = Cast<UMaterialExpressionConstant3Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant4Vector* Constant4Expression = Cast<UMaterialExpressionConstant4Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionTextureSample* TextureSampleExpression = Cast<UMaterialExpressionTextureSample>(CurrentSelectedExpression);
|
|
UMaterialExpressionComponentMask* ComponentMaskExpression = Cast<UMaterialExpressionComponentMask>(CurrentSelectedExpression);
|
|
UMaterialExpressionParticleSubUV* ParticleSubUVExpression = Cast<UMaterialExpressionParticleSubUV>(CurrentSelectedExpression);
|
|
UMaterialExpressionScalarParameter* ScalarParameterExpression = Cast<UMaterialExpressionScalarParameter>(CurrentSelectedExpression);
|
|
UMaterialExpressionVectorParameter* VectorParameterExpression = Cast<UMaterialExpressionVectorParameter>(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<UMaterialGraphNode>(NewExpression->GraphNode);
|
|
NewGraphNode->ReplaceNode(GraphNode);
|
|
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Copy over expression-specific values
|
|
if (Constant1Expression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionScalarParameter>(NewExpression)->DefaultValue = Constant1Expression->R;
|
|
}
|
|
else if (Constant2Expression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionVectorParameter>(NewExpression)->DefaultValue = FLinearColor(Constant2Expression->R, Constant2Expression->G, 0);
|
|
}
|
|
else if (Constant3Expression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionVectorParameter>(NewExpression)->DefaultValue = Constant3Expression->Constant;
|
|
CastChecked<UMaterialExpressionVectorParameter>(NewExpression)->DefaultValue.A = 1.0f;
|
|
}
|
|
else if (Constant4Expression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionVectorParameter>(NewExpression)->DefaultValue = Constant4Expression->Constant;
|
|
}
|
|
else if (TextureSampleExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureSampleParameter* NewTextureExpr = CastChecked<UMaterialExpressionTextureSampleParameter>(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<UMaterialExpressionStaticComponentMaskParameter>(NewExpression);
|
|
ComponentMask->DefaultR = ComponentMaskExpression->R;
|
|
ComponentMask->DefaultG = ComponentMaskExpression->G;
|
|
ComponentMask->DefaultB = ComponentMaskExpression->B;
|
|
ComponentMask->DefaultA = ComponentMaskExpression->A;
|
|
}
|
|
else if (ParticleSubUVExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionTextureSampleParameterSubUV>(NewExpression)->Texture = ParticleSubUVExpression->Texture;
|
|
}
|
|
else if (ScalarParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionConstant>(NewExpression)->R = ScalarParameterExpression->DefaultValue;
|
|
}
|
|
else if (VectorParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionConstant3Vector>(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<UEdGraphNode*>::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<class UEdGraphNode*> NodesToDelete;
|
|
TArray<class UEdGraphNode*> NodesToSelect;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
// Look for the supported classes to convert from
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionTextureSample* TextureSampleExpression = Cast<UMaterialExpressionTextureSample>(CurrentSelectedExpression);
|
|
UMaterialExpressionTextureObject* TextureObjectExpression = Cast<UMaterialExpressionTextureObject>(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<UMaterialGraphNode>(NewExpression->GraphNode);
|
|
NewGraphNode->ReplaceNode(GraphNode);
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Copy over expression-specific values
|
|
if (TextureSampleExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureObject* NewTextureExpr = CastChecked<UMaterialExpressionTextureObject>(NewExpression);
|
|
NewTextureExpr->Texture = TextureSampleExpression->Texture;
|
|
NewTextureExpr->AutoSetSampleType();
|
|
NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture;
|
|
}
|
|
else if (TextureObjectExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureSample* NewTextureExpr = CastChecked<UMaterialExpressionTextureSample>(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<UEdGraphNode*>::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<UMaterialGraphNode>(*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<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpression* SelectedExpression = GraphNode->MaterialExpression;
|
|
SelectedExpression->bRealtimePreview = !SelectedExpression->bRealtimePreview;
|
|
|
|
if (SelectedExpression->bRealtimePreview)
|
|
{
|
|
SelectedExpression->bCollapsed = false;
|
|
}
|
|
|
|
RefreshExpressionPreviews();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectDownsteamNodes()
|
|
{
|
|
TArray<UMaterialGraphNode*> NodesToCheck;
|
|
TArray<UMaterialGraphNode*> CheckedNodes;
|
|
TArray<UMaterialGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
NodesToCheck.Add(GraphNode);
|
|
}
|
|
}
|
|
|
|
while (NodesToCheck.Num() > 0)
|
|
{
|
|
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
|
|
TArray<UEdGraphPin*> 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<UMaterialGraphNode>(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<UMaterialGraphNode*> NodesToCheck;
|
|
TArray<UMaterialGraphNode*> CheckedNodes;
|
|
TArray<UMaterialGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
NodesToCheck.Add(GraphNode);
|
|
}
|
|
}
|
|
|
|
while (NodesToCheck.Num() > 0)
|
|
{
|
|
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
|
|
TArray<UEdGraphPin*> 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<UMaterialGraphNode>(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<UObject*> SelectedNodes = GraphEditor->GetSelectedNodes().Array();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
UMaterialGraphNode* SelectedGraphNode = Cast<UMaterialGraphNode>(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<UClass>(ANY_PACKAGE, *InAddedAssetData.AssetClass.ToString());
|
|
|
|
if(Asset->IsChildOf(UMaterialFunction::StaticClass()))
|
|
{
|
|
ForceRefreshExpressionPreviews();
|
|
}
|
|
}
|
|
|
|
|
|
void FMaterialEditor::OnAssetPostImport(UFactory* InFactory, UObject* InObject)
|
|
{
|
|
UTexture* Texture = Cast<UTexture>(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<UMaterialExpressionTextureBase>(Expression))
|
|
{
|
|
if (ExpressionTexture->Texture == Texture)
|
|
{
|
|
UpdateOriginalMaterial();
|
|
break;
|
|
}
|
|
}
|
|
else if (UMaterialExpressionMaterialFunctionCall* ExpressionFunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>(Expression))
|
|
{
|
|
struct Local
|
|
{
|
|
static bool ReferencesImportedObject(UMaterialFunction* Function, UTexture* InTexture)
|
|
{
|
|
for (UMaterialExpression* MaterialExpression : Function->FunctionExpressions)
|
|
{
|
|
if (UMaterialExpressionTextureBase* MaterialExpressionTexture = Cast<UMaterialExpressionTextureBase>(MaterialExpression))
|
|
{
|
|
if (MaterialExpressionTexture->Texture == InTexture)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (UMaterialExpressionMaterialFunctionCall* MaterialExpressionFunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>(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<EMaterialUsage>(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<UMaterial*> MaterialsToOverride;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
// Find all materials that reference this function
|
|
for (TObjectIterator<UMaterial> 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<UMaterialInstance> 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<UMaterial*> MaterialsToOverride;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
// Find all materials that reference this function
|
|
for (TObjectIterator<UMaterial> 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<UMaterialInstance> 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<SDockTab> FMaterialEditor::SpawnTab_Preview(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab =
|
|
SNew(SDockTab)
|
|
.Label(LOCTEXT("ViewportTabTitle", "Viewport"))
|
|
[
|
|
Viewport.ToSharedRef()
|
|
];
|
|
|
|
Viewport->OnAddedToTab( SpawnedTab );
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("GraphCanvasTitle", "Graph"));
|
|
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
SpawnedTab->SetContent(GraphEditor.ToSharedRef());
|
|
}
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_MaterialProperties(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> 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<SDockTab> FMaterialEditor::SpawnTab_HLSLCode(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> 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<SDockTab> FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args)
|
|
{
|
|
check( Args.GetTabId() == PaletteTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.Palette"))
|
|
.Label(LOCTEXT("MaterialPaletteTitle", "Palette"))
|
|
[
|
|
SNew( SBox )
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialPalette")))
|
|
[
|
|
Palette.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Stats(const FSpawnTabArgs& Args)
|
|
{
|
|
check( Args.GetTabId() == StatsTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.CompilerResults"))
|
|
.Label(LOCTEXT("MaterialStatsTitle", "Stats"))
|
|
[
|
|
SNew( SBox )
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialStats")))
|
|
[
|
|
Stats.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Find(const FSpawnTabArgs& Args)
|
|
{
|
|
check(Args.GetTabId() == FindTabId);
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.FindResults"))
|
|
.Label(LOCTEXT("MaterialFindTitle", "Find Results"))
|
|
[
|
|
SNew(SBox)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialFind")))
|
|
[
|
|
FindResults.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
void FMaterialEditor::SetPreviewExpression(UMaterialExpression* NewPreviewExpression)
|
|
{
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(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<UMaterial>(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<UMaterialExpressionBreakMaterialAttributes>(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<UMaterialExpression>(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<UMaterialExpressionTextureBase>( NewExpression );
|
|
if( METextureBase )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
if( UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop<UTexture>() )
|
|
{
|
|
METextureBase->Texture = SelectedTexture;
|
|
}
|
|
METextureBase->AutoSetSampleType();
|
|
}
|
|
|
|
UMaterialExpressionMaterialFunctionCall* MEMaterialFunction = Cast<UMaterialExpressionMaterialFunctionCall>( NewExpression );
|
|
if( MEMaterialFunction )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
MEMaterialFunction->SetMaterialFunction(MaterialFunction, NULL, GEditor->GetSelectedObjects()->GetTop<UMaterialFunction>());
|
|
}
|
|
|
|
UMaterialExpressionCollectionParameter* MECollectionParameter = Cast<UMaterialExpressionCollectionParameter>( NewExpression );
|
|
if( MECollectionParameter )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
MECollectionParameter->Collection = GEditor->GetSelectedObjects()->GetTop<UMaterialParameterCollection>();
|
|
}
|
|
}
|
|
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>( NewExpression );
|
|
if( FunctionInput )
|
|
{
|
|
FunctionInput->ConditionallyGenerateId(true);
|
|
FunctionInput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>( NewExpression );
|
|
if( FunctionOutput )
|
|
{
|
|
FunctionOutput->ConditionallyGenerateId(true);
|
|
FunctionOutput->ValidateName();
|
|
}
|
|
|
|
NewExpression->UpdateParameterGuid(true, true);
|
|
|
|
UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast<UMaterialExpressionTextureSampleParameter>( 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<UMaterialExpressionComponentMask>( 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<UMaterialExpressionStaticComponentMaskParameter>( 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<UMaterialExpressionRotateAboutAxis>( NewExpression );
|
|
if( RotateAboutAxisExpression )
|
|
{
|
|
// Create a default expression for the Position input
|
|
UMaterialExpressionWorldPosition* WorldPositionExpression = NewObject<UMaterialExpressionWorldPosition>(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<UMaterialExpressionTransformPosition>(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<UMaterialExpressionDynamicParameter>(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<UMaterialExpressionComment>(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<UEdGraphNode*> NodesToDelete;
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
NodesToDelete.Add(CastChecked<UEdGraphNode>(*NodeIt));
|
|
}
|
|
|
|
DeleteNodes(NodesToDelete);
|
|
}
|
|
|
|
void FMaterialEditor::DeleteNodes(const TArray<UEdGraphNode*>& 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<UMaterialGraphNode>(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<UMaterialGraphNode_Comment>(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<UMaterialGraphNode_Root>(*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<UEdGraphNode>(*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<UEdGraphNode>(*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<UEdGraphNode>(*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<UMaterialGraphNode>(*SelectedIter))
|
|
{
|
|
Node->PostCopyNode();
|
|
}
|
|
else if (UMaterialGraphNode_Comment* Comment = Cast<UMaterialGraphNode_Comment>(*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<UEdGraphNode>(*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<UEdGraphNode*> 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<UEdGraphNode*>::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<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(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<UMaterialExpressionTextureBase>( NewExpression );
|
|
if( TextureSample )
|
|
{
|
|
TextureSample->IsDefaultMeshpaintTexture = false;
|
|
}
|
|
|
|
NewExpression->UpdateParameterGuid(true, true);
|
|
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>( NewExpression );
|
|
if( FunctionInput )
|
|
{
|
|
FunctionInput->ConditionallyGenerateId(true);
|
|
FunctionInput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>( NewExpression );
|
|
if( FunctionOutput )
|
|
{
|
|
FunctionOutput->ConditionallyGenerateId(true);
|
|
FunctionOutput->ValidateName();
|
|
}
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(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<UMaterialGraphNode>(*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<UMaterialGraphSchema>()->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<FMatExpressionPreview*> 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<SGraphEditor> 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<UEdGraph>(Material->MaterialGraph));
|
|
|
|
// Create the title bar widget
|
|
TSharedPtr<SWidget> 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<UEdGraphNode*> 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<UMaterialGraphNode>(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<UEdGraphNode*>& NodesToRemove)
|
|
{
|
|
FString FunctionWarningString;
|
|
bool bFirstExpression = true;
|
|
for (int32 Index = 0; Index < NodesToRemove.Num(); ++Index)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(NodesToRemove[Index]))
|
|
{
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(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<UMaterialGraphNode>(*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<UMaterialGraphNode>(*NodeIt))
|
|
{
|
|
MaterialExpressionClasses::Get()->AddMaterialExpressionToFavorites(GraphNode->MaterialExpression->GetClass());
|
|
EditorOptions->FavoriteExpressions.AddUnique(GraphNode->MaterialExpression->GetClass()->GetName());
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
|
|
{
|
|
TArray<UObject*> SelectedObjects;
|
|
|
|
UObject* EditObject = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
EditObject = MaterialFunction;
|
|
}
|
|
|
|
if( NewSelection.Num() == 0 )
|
|
{
|
|
SelectedObjects.Add(EditObject);
|
|
}
|
|
else
|
|
{
|
|
for(TSet<class UObject*>::TConstIterator SetIt(NewSelection);SetIt;++SetIt)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*SetIt))
|
|
{
|
|
SelectedObjects.Add(GraphNode->MaterialExpression);
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(*SetIt))
|
|
{
|
|
SelectedObjects.Add(CommentNode->MaterialExpressionComment);
|
|
}
|
|
else
|
|
{
|
|
SelectedObjects.Add(EditObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
GetDetailView()->SetObjects( SelectedObjects, true );
|
|
}
|
|
|
|
void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(Node);
|
|
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpressionConstant3Vector* Constant3Expression = Cast<UMaterialExpressionConstant3Vector>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionConstant4Vector* Constant4Expression = Cast<UMaterialExpressionConstant4Vector>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionFunctionInput* InputExpression = Cast<UMaterialExpressionFunctionInput>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionVectorParameter* VectorExpression = Cast<UMaterialExpressionVectorParameter>(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<FColorChannels> 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<float>::Create( TAttribute<float>::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<UMaterialExpressionTextureSample>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast<UMaterialExpressionTextureSampleParameter>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionMaterialFunctionCall* FunctionExpression = Cast<UMaterialExpressionMaterialFunctionCall>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionCollectionParameter* CollectionParameter = Cast<UMaterialExpressionCollectionParameter>(GraphNode->MaterialExpression);
|
|
|
|
TArray<UObject*> 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<UEdGraphPin*> 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
|