You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[FYI] Chris.Bunner, Krzysztof.Narkowicz, Marcus.Wassmer, Yuriy.Odonnell, Matt.Kuhlenschmidt #rb Krzysztof.Narkowicz #ROBOMERGE-OWNER: ryan.vance #ROBOMERGE-AUTHOR: kevin.ortegren #ROBOMERGE-SOURCE: CL 6308573 via CL 6309266 via CL 6315508 #ROBOMERGE-BOT: DEVVR (Main -> Dev-VR) [CL 6348696 by joe conley in Dev-VR branch]
4906 lines
166 KiB
C++
4906 lines
166 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MaterialEditor.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "AI/NavigationSystemBase.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "EditorStyleSet.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "MaterialGraph/MaterialGraph.h"
|
|
#include "MaterialGraph/MaterialGraphNode_Comment.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "MaterialEditor/MaterialEditorInstanceConstant.h"
|
|
#include "Preferences/MaterialEditorOptions.h"
|
|
#include "MaterialGraph/MaterialGraphNode.h"
|
|
#include "MaterialGraph/MaterialGraphNode_Root.h"
|
|
#include "MaterialGraph/MaterialGraphSchema.h"
|
|
#include "MaterialEditor/PreviewMaterial.h"
|
|
#include "ThumbnailRendering/SceneThumbnailInfoWithPrimitive.h"
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
#include "Materials/MaterialInstance.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Materials/MaterialInstanceConstant.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
#include "Engine/TextureCube.h"
|
|
#include "Dialogs/Dialogs.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Editor.h"
|
|
#include "MaterialEditorModule.h"
|
|
#include "MaterialEditingLibrary.h"
|
|
#include "HAL/PlatformApplicationMisc.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/MaterialExpressionMaterialAttributeLayers.h"
|
|
#include "Materials/MaterialExpressionParameter.h"
|
|
#include "Materials/MaterialExpressionTextureBase.h"
|
|
#include "Materials/MaterialExpressionTextureSample.h"
|
|
#include "Materials/MaterialExpressionParticleSubUV.h"
|
|
#include "Materials/MaterialExpressionScalarParameter.h"
|
|
#include "Materials/MaterialExpressionStaticComponentMaskParameter.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter.h"
|
|
#include "Materials/MaterialExpressionTextureObjectParameter.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/MaterialExpressionStaticBoolParameter.h"
|
|
#include "Materials/MaterialFunction.h"
|
|
#include "Materials/MaterialFunctionInstance.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
|
|
#include "MaterialEditorActions.h"
|
|
#include "MaterialExpressionClasses.h"
|
|
#include "MaterialCompiler.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "IAssetTools.h"
|
|
#include "IAssetTypeActions.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "SMaterialEditorTitleBar.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "BusyCursor.h"
|
|
|
|
#include "PropertyEditorModule.h"
|
|
#include "MaterialEditorDetailCustomization.h"
|
|
#include "MaterialInstanceEditor.h"
|
|
|
|
#include "EditorViewportCommands.h"
|
|
|
|
#include "GraphEditor.h"
|
|
#include "GraphEditorActions.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "SNodePanel.h"
|
|
#include "MaterialEditorUtilities.h"
|
|
#include "SMaterialPalette.h"
|
|
#include "FindInMaterial.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Widgets/Colors/SColorPicker.h"
|
|
#include "EditorClassUtils.h"
|
|
#include "IDocumentation.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
|
|
#include "Developer/MessageLog/Public/IMessageLogListing.h"
|
|
#include "Developer/MessageLog/Public/MessageLogInitializationOptions.h"
|
|
#include "Developer/MessageLog/Public/MessageLogModule.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "CanvasTypes.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Materials/Material.h"
|
|
#include "AdvancedPreviewSceneModule.h"
|
|
#include "MaterialLayersFunctionsCustomization.h"
|
|
#include "MaterialEditor/MaterialEditorPreviewParameters.h"
|
|
#include "SMaterialLayersFunctionsTree.h"
|
|
#include "Materials/MaterialExpressionSetMaterialAttributes.h"
|
|
#include "Settings/EditorExperimentalSettings.h"
|
|
#include "Materials/MaterialExpressionBlendMaterialAttributes.h"
|
|
#include "Materials/MaterialExpressionMaterialLayerOutput.h"
|
|
|
|
#include "MaterialStats.h"
|
|
#include "SMaterialParametersOverviewWidget.h"
|
|
#include "IPropertyRowGenerator.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MaterialEditor"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditor, Log, All);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaterialEdUseDevShaders(
|
|
TEXT("r.MaterialEditor.UseDevShaders"),
|
|
1,
|
|
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::PaletteTabId( TEXT( "MaterialEditor_Palette" ) );
|
|
const FName FMaterialEditor::FindTabId( TEXT( "MaterialEditor_Find" ) );
|
|
const FName FMaterialEditor::PreviewSettingsTabId( TEXT ("MaterialEditor_PreviewSettings" ) );
|
|
const FName FMaterialEditor::ParameterDefaultsTabId( TEXT ("MaterialEditor_ParameterDefaults" ) );
|
|
const FName FMaterialEditor::LayerPropertiesTabId(TEXT("MaterialInstanceEditor_MaterialLayerProperties"));
|
|
///////////////////////////
|
|
// 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;
|
|
}
|
|
else if (FCString::Stristr(ShaderType->GetName(), TEXT("Simple")))
|
|
{
|
|
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), 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 if (Property == MP_ShadingModel)
|
|
{
|
|
FMaterialShadingModelField ShadingModels = Compiler->GetMaterialShadingModels();
|
|
Ret = Compiler->ShadingModel(ShadingModels.GetFirstShadingModel());
|
|
}
|
|
else
|
|
{
|
|
Ret = Compiler->Constant(1.0f);
|
|
}
|
|
|
|
// output should always be the right type for this property
|
|
return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property));
|
|
}
|
|
|
|
void FMatExpressionPreview::NotifyCompilationFinished()
|
|
{
|
|
if (Expression.IsValid() && Expression->GraphNode)
|
|
{
|
|
CastChecked<UMaterialGraphNode>(Expression->GraphNode)->bPreviewNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/////////////////////
|
|
// FMaterialEditor //
|
|
/////////////////////
|
|
|
|
void FMaterialEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->RegisterTabSpawner( PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) )
|
|
.SetDisplayName( LOCTEXT("ViewportTab", "Viewport") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner( GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_GraphCanvas) )
|
|
.SetDisplayName( LOCTEXT("GraphCanvasTab", "Graph") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x"));
|
|
|
|
InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) )
|
|
.SetDisplayName( LOCTEXT("DetailsTab", "Details") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner( PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) )
|
|
.SetDisplayName( LOCTEXT("PaletteTab", "Palette") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Palette"));
|
|
|
|
InTabManager->RegisterTabSpawner(FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find))
|
|
.SetDisplayName(LOCTEXT("FindTab", "Find Results"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.FindResults"));
|
|
|
|
InTabManager->RegisterTabSpawner(PreviewSettingsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_PreviewSettings))
|
|
.SetDisplayName( LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
|
|
InTabManager->RegisterTabSpawner(ParameterDefaultsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_ParameterDefaults))
|
|
.SetDisplayName(LOCTEXT("ParameterDefaultsTab", "Parameter Defaults"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>("MaterialEditor");
|
|
if (MaterialEditorModule->MaterialLayersEnabled())
|
|
{
|
|
InTabManager->RegisterTabSpawner(LayerPropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_LayerProperties))
|
|
.SetDisplayName(LOCTEXT("LayerPropertiesTab", "Layer Parameters"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Layers"));
|
|
}
|
|
|
|
MaterialStatsManager->RegisterTabs();
|
|
|
|
OnRegisterTabSpawners().Broadcast(InTabManager);
|
|
}
|
|
|
|
|
|
void FMaterialEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->UnregisterTabSpawner( PreviewTabId );
|
|
InTabManager->UnregisterTabSpawner( GraphCanvasTabId );
|
|
InTabManager->UnregisterTabSpawner( PropertiesTabId );
|
|
InTabManager->UnregisterTabSpawner( PaletteTabId );
|
|
InTabManager->UnregisterTabSpawner( FindTabId );
|
|
InTabManager->UnregisterTabSpawner( PreviewSettingsTabId );
|
|
InTabManager->UnregisterTabSpawner( ParameterDefaultsTabId );
|
|
InTabManager->UnregisterTabSpawner( LayerPropertiesTabId );
|
|
|
|
MaterialStatsManager->UnregisterTabs();
|
|
|
|
OnUnregisterTabSpawners().Broadcast(InTabManager);
|
|
}
|
|
|
|
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(), NAME_None, ~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);
|
|
}
|
|
}
|
|
|
|
TArray<FString> Groups;
|
|
GetAllMaterialExpressionGroups(&Groups);
|
|
}
|
|
|
|
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(), NAME_None, ~RF_Standalone, UMaterialFunction::StaticClass());
|
|
MaterialFunction->ParentFunction = InMaterialFunction;
|
|
|
|
OriginalMaterial = Material;
|
|
|
|
TArray<FString> Groups;
|
|
GetAllMaterialExpressionGroups(&Groups);
|
|
}
|
|
|
|
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);
|
|
|
|
MaterialStatsManager = FMaterialStatsUtils::CreateMaterialStats(this);
|
|
MaterialStatsManager->SetMaterialDisplayName(OriginalMaterial->GetName());
|
|
|
|
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;
|
|
|
|
NodeQualityLevel = EMaterialQualityLevel::Num;
|
|
NodeFeatureLevel = ERHIFeatureLevel::Num;
|
|
bPreviewFeaturesChanged = true;
|
|
|
|
// 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);
|
|
|
|
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);
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
|
|
BindCommands();
|
|
|
|
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_MaterialEditor_Layout_v10")
|
|
->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 )
|
|
->AddTab( PreviewSettingsTabId, ETabState::ClosedTab )
|
|
->AddTab( ParameterDefaultsTabId, ETabState::OpenedTab )
|
|
->AddTab( LayerPropertiesTabId, ETabState::ClosedTab )
|
|
->SetForegroundTab( PropertiesTabId )
|
|
)
|
|
)
|
|
->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(FMaterialStats::GetGridStatsTabName(), ETabState::ClosedTab)
|
|
->AddTab(FMaterialStats::GetGridOldStatsTabName(), 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 );
|
|
|
|
AddMenuExtender(GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
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)
|
|
{
|
|
// Support undo/redo for the material function if it exists
|
|
MaterialFunction->SetFlags(RF_Transactional);
|
|
|
|
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 function, create an output by default and start previewing it
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
check(!bMaterialDirty);
|
|
FVector2D OutputPlacement = FVector2D(200, 300);
|
|
if (GetDefault<UEditorExperimentalSettings>()->bExampleLayersAndBlends)
|
|
{
|
|
switch (MaterialFunction->GetMaterialFunctionUsage())
|
|
{
|
|
case(EMaterialFunctionUsage::MaterialLayer):
|
|
{
|
|
OutputPlacement = FVector2D(300, 269);
|
|
break;
|
|
}
|
|
case(EMaterialFunctionUsage::MaterialLayerBlend):
|
|
{
|
|
OutputPlacement = FVector2D(275, 269);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
UMaterialExpression* Expression;
|
|
if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::Default)
|
|
{
|
|
Expression = CreateNewMaterialExpression(UMaterialExpressionFunctionOutput::StaticClass(), OutputPlacement, false, true);
|
|
SetPreviewExpression(Expression);
|
|
// This shouldn't count as having dirtied the material, so reset the flag
|
|
bMaterialDirty = false;
|
|
}
|
|
else
|
|
{
|
|
Expression = CreateNewMaterialExpression(UMaterialExpressionMaterialLayerOutput::StaticClass(), OutputPlacement, false, true);
|
|
SetPreviewExpression(Expression);
|
|
// This shouldn't count as having dirtied the material, so reset the flag
|
|
bMaterialDirty = false;
|
|
}
|
|
// We can check the usage here and add the appropriate inputs too (e.g. Layer==1MA, Blend==2MA)
|
|
if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer)
|
|
{
|
|
UMaterialExpression* Input = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-350, 300), false, true);
|
|
if (Input)
|
|
{
|
|
UMaterialExpressionFunctionInput* BaseAttributesInput = Cast<UMaterialExpressionFunctionInput>(Input);
|
|
BaseAttributesInput->InputType = FunctionInput_MaterialAttributes;
|
|
BaseAttributesInput->InputName = TEXT("Material Attributes");
|
|
BaseAttributesInput->bUsePreviewValueAsDefault = true;
|
|
|
|
}
|
|
if (GetDefault<UEditorExperimentalSettings>()->bExampleLayersAndBlends)
|
|
{
|
|
UMaterialExpression* SetMaterialAttributes = CreateNewMaterialExpression(UMaterialExpressionSetMaterialAttributes::StaticClass(), FVector2D(40, 300), false, true);
|
|
if (Input && SetMaterialAttributes)
|
|
{
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(Input, FString(), SetMaterialAttributes, FString());
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(SetMaterialAttributes, FString(), Expression, FString());
|
|
bMaterialDirty = true;
|
|
}
|
|
}
|
|
}
|
|
else if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend)
|
|
{
|
|
// "Top layer" should be below "bottom layer" on the graph, to align with B on blend nodes
|
|
UMaterialExpression* InputTop = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-300, 400), false, true);
|
|
if (InputTop)
|
|
{
|
|
UMaterialExpressionFunctionInput* BaseAttributesInput = Cast<UMaterialExpressionFunctionInput>(InputTop);
|
|
BaseAttributesInput->InputType = FunctionInput_MaterialAttributes;
|
|
BaseAttributesInput->InputName = TEXT("Top Layer");
|
|
BaseAttributesInput->bUsePreviewValueAsDefault = true;
|
|
}
|
|
|
|
UMaterialExpression* InputBottom = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-300, 200), false, true);
|
|
if (InputBottom)
|
|
{
|
|
UMaterialExpressionFunctionInput* BaseAttributesInput = Cast<UMaterialExpressionFunctionInput>(InputBottom);
|
|
BaseAttributesInput->InputType = FunctionInput_MaterialAttributes;
|
|
BaseAttributesInput->InputName = TEXT("Bottom Layer");
|
|
BaseAttributesInput->bUsePreviewValueAsDefault = true;
|
|
}
|
|
if (GetDefault<UEditorExperimentalSettings>()->bExampleLayersAndBlends)
|
|
{
|
|
UMaterialExpression* BlendMaterialAttributes = CreateNewMaterialExpression(UMaterialExpressionBlendMaterialAttributes::StaticClass(), FVector2D(40, 300), false, true);
|
|
if (InputTop && InputBottom && BlendMaterialAttributes)
|
|
{
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(InputBottom, FString(), BlendMaterialAttributes, FString(TEXT("A")));
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(InputTop, FString(), BlendMaterialAttributes, FString(TEXT("B")));
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(BlendMaterialAttributes, FString(), Expression, FString());
|
|
bMaterialDirty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
if (OriginalMaterial != nullptr)
|
|
{
|
|
Material->MaterialGraph->OriginalMaterialFullName = OriginalMaterial->GetName();
|
|
}
|
|
Material->MaterialGraph->RebuildGraph();
|
|
RecenterEditor();
|
|
|
|
//Make sure the preview material is initialized.
|
|
UpdatePreviewMaterial(true);
|
|
RegenerateCodeView(true);
|
|
|
|
ForceRefreshExpressionPreviews();
|
|
|
|
if (OriginalMaterial->bUsedAsSpecialEngineMaterial)
|
|
{
|
|
FSuppressableWarningDialog::FSetupInfo Info(
|
|
NSLOCTEXT("UnrealEd", "Warning_EditingDefaultMaterial", "Editing this Default Material is an advanced workflow.\nDefault Materials must be available as a code fallback at all times, compilation errors are not handled gracefully. Save your work."),
|
|
NSLOCTEXT("UnrealEd", "Warning_EditingDefaultMaterial_Title", "Warning: Editing Default Material" ), "Warning_EditingDefaultMaterial");
|
|
Info.ConfirmText = NSLOCTEXT("ModalDialogs", "EditingDefaultMaterialOk", "Ok");
|
|
|
|
FSuppressableWarningDialog EditingDefaultMaterial( Info );
|
|
EditingDefaultMaterial.ShowModal();
|
|
}
|
|
|
|
if (GetDefault<UEditorExperimentalSettings>()->bExampleLayersAndBlends && bMaterialDirty)
|
|
{
|
|
SaveAsset_Execute();
|
|
}
|
|
}
|
|
|
|
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)
|
|
, bShowBuiltinStats(false)
|
|
, MenuExtensibilityManager(new FExtensibilityManager)
|
|
, ToolBarExtensibilityManager(new FExtensibilityManager)
|
|
, MaterialEditorInstance(NULL)
|
|
{
|
|
}
|
|
|
|
FMaterialEditor::~FMaterialEditor()
|
|
{
|
|
// Broadcast that this editor is going down to all listeners
|
|
OnMaterialEditorClosed().Broadcast();
|
|
|
|
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);
|
|
|
|
// 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 );
|
|
|
|
MaterialEditorInstance = NULL;
|
|
}
|
|
|
|
void FMaterialEditor::GetAllMaterialExpressionGroups(TArray<FString>* OutGroups)
|
|
{
|
|
TArray<FParameterGroupData> UpdatedGroups;
|
|
for (int32 MaterialExpressionIndex = 0; MaterialExpressionIndex < Material->Expressions.Num(); ++MaterialExpressionIndex)
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ];
|
|
UMaterialExpressionParameter* Param = Cast<UMaterialExpressionParameter>(MaterialExpression);
|
|
UMaterialExpressionTextureSampleParameter* TextureS = Cast<UMaterialExpressionTextureSampleParameter>(MaterialExpression);
|
|
UMaterialExpressionFontSampleParameter* FontS = Cast<UMaterialExpressionFontSampleParameter>(MaterialExpression);
|
|
if (Param)
|
|
{
|
|
const FString& GroupName = Param->Group.ToString();
|
|
OutGroups->AddUnique(GroupName);
|
|
if (Material->AttemptInsertNewGroupName(GroupName))
|
|
{
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, 0));
|
|
}
|
|
else
|
|
{
|
|
FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement)
|
|
{
|
|
return GroupName == DataElement.GroupName;
|
|
});
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority));
|
|
}
|
|
}
|
|
else if (TextureS)
|
|
{
|
|
const FString& GroupName = TextureS->Group.ToString();
|
|
OutGroups->AddUnique(GroupName);
|
|
if (Material->AttemptInsertNewGroupName(GroupName))
|
|
{
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, 0));
|
|
}
|
|
else
|
|
{
|
|
FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement)
|
|
{
|
|
return GroupName == DataElement.GroupName;
|
|
});
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority));
|
|
}
|
|
}
|
|
else if (FontS)
|
|
{
|
|
const FString& GroupName = FontS->Group.ToString();
|
|
OutGroups->AddUnique(GroupName);
|
|
if (Material->AttemptInsertNewGroupName(GroupName))
|
|
{
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, 0));
|
|
}
|
|
else
|
|
{
|
|
FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement)
|
|
{
|
|
return GroupName == DataElement.GroupName;
|
|
});
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority));
|
|
}
|
|
}
|
|
}
|
|
Material->ParameterGroupData = UpdatedGroups;
|
|
}
|
|
|
|
void FMaterialEditor::UpdatePreviewViewportsVisibility()
|
|
{
|
|
if( Material->IsUIMaterial() )
|
|
{
|
|
PreviewViewport->SetVisibility(EVisibility::Collapsed);
|
|
PreviewUIViewport->SetVisibility(EVisibility::Visible);
|
|
}
|
|
else
|
|
{
|
|
PreviewViewport->SetVisibility(EVisibility::Visible);
|
|
PreviewUIViewport->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SetQualityPreview(EMaterialQualityLevel::Type NewQuality)
|
|
{
|
|
NodeQualityLevel = NewQuality;
|
|
bPreviewFeaturesChanged = true;
|
|
}
|
|
|
|
bool FMaterialEditor::IsQualityPreviewChecked(EMaterialQualityLevel::Type TestQuality)
|
|
{
|
|
return NodeQualityLevel == TestQuality;
|
|
}
|
|
|
|
void FMaterialEditor::SetFeaturePreview(ERHIFeatureLevel::Type NewFeatureLevel)
|
|
{
|
|
NodeFeatureLevel = NewFeatureLevel;
|
|
bPreviewFeaturesChanged = true;
|
|
}
|
|
|
|
bool FMaterialEditor::IsFeaturePreviewChecked(ERHIFeatureLevel::Type TestFeatureLevel)
|
|
{
|
|
return NodeFeatureLevel == TestFeatureLevel;
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FMaterialEditor::CreateInternalWidgets()
|
|
{
|
|
PreviewViewport = SNew(SMaterialEditor3DPreviewViewport)
|
|
.MaterialEditor(SharedThis(this));
|
|
|
|
PreviewUIViewport = SNew(SMaterialEditorUIPreviewViewport, Material);
|
|
|
|
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 LayoutLayerExpressionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(
|
|
&FMaterialExpressionLayersParameterDetails::MakeInstance, FOnCollectParameterGroups::CreateSP(this, &FMaterialEditor::GetAllMaterialExpressionGroups));
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionMaterialAttributeLayers::StaticClass(),
|
|
LayoutLayerExpressionParameterDetails
|
|
);
|
|
|
|
FOnGetDetailCustomizationInstance LayoutCollectionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCollectionParameterDetails::MakeInstance);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionCollectionParameter::StaticClass(),
|
|
LayoutCollectionParameterDetails
|
|
);
|
|
|
|
MaterialDetailsView->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingProperties);
|
|
|
|
PropertyEditorModule.RegisterCustomClassLayout( UMaterial::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialDetailCustomization::MakeInstance ) );
|
|
|
|
MaterialEditorInstance = NewObject<UMaterialEditorPreviewParameters>(GetTransientPackage(), NAME_None, RF_Transactional);
|
|
MaterialEditorInstance->PreviewMaterial = Material;
|
|
MaterialEditorInstance->OriginalMaterial = OriginalMaterial;
|
|
if (MaterialFunction)
|
|
{
|
|
MaterialEditorInstance->OriginalFunction = MaterialFunction->ParentFunction;
|
|
}
|
|
MaterialParametersOverviewWidget = SNew(SMaterialParametersOverviewPanel)
|
|
.InMaterialEditorInstance(MaterialEditorInstance);
|
|
MaterialParametersOverviewWidget->GetGenerator()->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingParametersFromOverview);
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>("MaterialEditor");
|
|
if (MaterialEditorModule->MaterialLayersEnabled())
|
|
{
|
|
MaterialLayersFunctionsInstance = SNew(SMaterialLayersFunctionsMaterialWrapper)
|
|
.InMaterialEditorInstance(MaterialEditorInstance);
|
|
}
|
|
|
|
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));
|
|
|
|
RegenerateCodeView();
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
void FMaterialEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
bool bRefreshNodePreviews = false;
|
|
if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
UStructProperty* Property = Cast<UStructProperty>(PropertyChangedEvent.Property);
|
|
if (Property != nullptr)
|
|
{
|
|
if (Property->Struct->GetFName() == TEXT("LinearColor") || Property->Struct->GetFName() == TEXT("Color")) // if we changed a color property refresh the previews
|
|
{
|
|
bRefreshNodePreviews = true;
|
|
}
|
|
}
|
|
// If we changed any of the parameter values from the Parameter Defaults panel
|
|
if (PropertyChangedEvent.Property->GetName() == TEXT("Parameters"))
|
|
{
|
|
bRefreshNodePreviews = true;
|
|
}
|
|
if (bRefreshNodePreviews)
|
|
{
|
|
RefreshExpressionPreviews(true);
|
|
}
|
|
RefreshPreviewViewport();
|
|
UpdatePreviewMaterial();
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnFinishedChangingParametersFromOverview(const FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
bool bRefreshNodePreviews = false;
|
|
if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
RefreshExpressionPreviews(true);
|
|
RefreshPreviewViewport();
|
|
UpdatePreviewMaterial();
|
|
TArray<TWeakObjectPtr<UObject>> SelectedObjects = MaterialDetailsView->GetSelectedObjects();
|
|
MaterialDetailsView->SetObjects(SelectedObjects, true);
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
|
|
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];
|
|
check(EditingObject);
|
|
|
|
return FText::FromString(EditingObject->GetName());
|
|
}
|
|
|
|
FText FMaterialEditor::GetToolkitToolTipText() const
|
|
{
|
|
const UObject* EditingObject = GetEditingObjects()[0];
|
|
|
|
// Overridden to accommodate editing of multiple objects (original and preview materials)
|
|
return FAssetEditorToolkit::GetToolTipTextForObject(EditingObject);
|
|
}
|
|
|
|
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();
|
|
UpdateMaterialinfoList_Old();
|
|
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()
|
|
{
|
|
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FToolBarExtensionDelegate::CreateSP( this, &FMaterialEditor::FillToolbar )
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
AddToolbarExtender(GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "MaterialEditor" );
|
|
AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
void FMaterialEditor::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.EndSection();
|
|
|
|
ToolbarBuilder.AddSeparator();
|
|
|
|
ToolbarBuilder.BeginSection("Stats");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().TogglePlatformStats);
|
|
ToolbarBuilder.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &FMaterialEditor::GeneratePreviewMenuContent),
|
|
LOCTEXT("NodePreview_Label", "Preview Nodes"),
|
|
LOCTEXT("NodePreviewToolTip", "Preview the nodes for a given feature level and/or material quality."),
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "FullBlueprintEditor.SwitchToScriptingMode"),
|
|
false);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
};
|
|
|
|
TSharedRef< SWidget > FMaterialEditor::GeneratePreviewMenuContent()
|
|
{
|
|
bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetToolkitCommands());
|
|
|
|
MenuBuilder.BeginSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES2);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES31);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM4);
|
|
MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5);
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
|
|
}
|
|
|
|
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::GetSaveableObjects(TArray<UObject*>& OutObjects) const
|
|
{
|
|
if ((MaterialFunction != nullptr) && MaterialFunction->ParentFunction)
|
|
{
|
|
OutObjects.Add(MaterialFunction->ParentFunction);
|
|
}
|
|
else
|
|
{
|
|
OutObjects.Add(OriginalMaterial);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SaveAsset_Execute()
|
|
{
|
|
UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName());
|
|
|
|
const bool bUpdateSucceeded = (bMaterialDirty ? UpdateOriginalMaterial() : true);
|
|
|
|
if (bUpdateSucceeded)
|
|
{
|
|
IMaterialEditor::SaveAsset_Execute();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SaveAssetAs_Execute()
|
|
{
|
|
UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName());
|
|
|
|
const bool bUpdateSucceeded = (bMaterialDirty ? UpdateOriginalMaterial() : true);
|
|
|
|
if (bUpdateSucceeded)
|
|
{
|
|
IMaterialEditor::SaveAssetAs_Execute();
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnRequestClose()
|
|
{
|
|
DestroyColorPicker();
|
|
|
|
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
|
|
bMaterialDirty = false;
|
|
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,
|
|
bool bGeneratedNewShaders)
|
|
{
|
|
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<FMaterialStatsUtils::FShaderInstructionsInfo> Descriptions;
|
|
FMaterialStatsUtils::GetRepresentativeInstructionCounts(Descriptions, MaterialResource);
|
|
|
|
for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++)
|
|
{
|
|
FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"), *Descriptions[InstructionIndex].ShaderDescription, Descriptions[InstructionIndex].InstructionCount);
|
|
Canvas->DrawShadowedString(5, DrawPositionY, *InstructionCountString, FontToUse, FLinearColor(1, 1, 0));
|
|
DrawPositionY += SpacingBetweenLines;
|
|
}
|
|
|
|
// Display the number of texture samplers and samplers used by the material.
|
|
const int32 SamplersUsed = MaterialResource->GetSamplerUsage();
|
|
|
|
if (SamplersUsed >= 0)
|
|
{
|
|
int32 MaxSamplers = GetExpectedFeatureLevelMaxTextureSamplers(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;
|
|
}
|
|
|
|
uint32 NumVSTextureSamples = 0, NumPSTextureSamples = 0;
|
|
MaterialResource->GetEstimatedNumTextureSamples(NumVSTextureSamples, NumPSTextureSamples);
|
|
|
|
if (NumVSTextureSamples > 0 || NumPSTextureSamples > 0)
|
|
{
|
|
Canvas->DrawShadowedString(
|
|
5,
|
|
DrawPositionY,
|
|
*FString::Printf(TEXT("Texture Lookups (Est.): VS(%u), PS(%u)"), NumVSTextureSamples, NumPSTextureSamples),
|
|
FontToUse,
|
|
FLinearColor(1,1,0)
|
|
);
|
|
DrawPositionY += SpacingBetweenLines;
|
|
}
|
|
|
|
if (bGeneratedNewShaders)
|
|
{
|
|
int32 NumShaders = 0;
|
|
int32 NumPipelines = 0;
|
|
if(FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap())
|
|
{
|
|
ShaderMap->CountNumShaders(NumShaders, NumPipelines);
|
|
}
|
|
|
|
if (NumShaders)
|
|
{
|
|
FString ShaderCountString = FString::Printf(TEXT("Num shaders added: %i"), NumShaders);
|
|
Canvas->DrawShadowedString(5, DrawPositionY, *ShaderCountString, FontToUse, FLinearColor(1, 0.8, 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( FTranslationMatrix(FVector(0.0f, 30.0f,0.0f) ) );
|
|
|
|
// 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 / Canvas->GetDPIScale(), 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 (PreviewViewport.IsValid())
|
|
{
|
|
return PreviewViewport->SetPreviewAsset(InAsset);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FMaterialEditor::SetPreviewAssetByName(const TCHAR* InAssetName)
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
return PreviewViewport->SetPreviewAssetByName(InAssetName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::SetPreviewMaterial(UMaterialInterface* InMaterialInterface)
|
|
{
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
if (PreviewUIViewport.IsValid())
|
|
{
|
|
PreviewUIViewport->SetPreviewMaterial(InMaterialInterface);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->SetPreviewMaterial(InMaterialInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RefreshPreviewViewport()
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->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 ( PreviewViewport.IsValid() )
|
|
{
|
|
if (EditorOptions->bShowGrid)
|
|
{
|
|
PreviewViewport->TogglePreviewGrid();
|
|
}
|
|
|
|
if (EditorOptions->bRealtimeMaterialViewport)
|
|
{
|
|
PreviewViewport->OnToggleRealtime();
|
|
}
|
|
}
|
|
|
|
// Primitive type
|
|
int32 PrimType;
|
|
if(GConfig->GetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PrimType, GEditorPerProjectIni))
|
|
{
|
|
PreviewViewport->OnSetPreviewPrimitive((EThumbnailPrimType)PrimType);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SaveEditorSettings()
|
|
{
|
|
// Save the preview scene
|
|
check( PreviewViewport.IsValid() );
|
|
|
|
if ( EditorOptions )
|
|
{
|
|
EditorOptions->bShowGrid = PreviewViewport->IsTogglePreviewGridChecked();
|
|
EditorOptions->bRealtimeMaterialViewport = PreviewViewport->IsRealtime();
|
|
EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked();
|
|
EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews();
|
|
EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked();
|
|
EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked();
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
|
|
GConfig->SetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PreviewViewport->PreviewPrimType, GEditorPerProjectIni);
|
|
}
|
|
|
|
void FMaterialEditor::RegenerateCodeView(bool bForce)
|
|
{
|
|
if (!bLivePreview && !bForce)
|
|
{
|
|
//When bLivePreview is false then the source can be out of date.
|
|
return;
|
|
}
|
|
|
|
MaterialStatsManager->SignalMaterialChanged();
|
|
}
|
|
|
|
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)
|
|
{
|
|
check(ExpressionPreviewMaterial);
|
|
|
|
// 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();
|
|
}
|
|
|
|
{
|
|
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread);
|
|
UpdateContext.AddMaterial(Material);
|
|
|
|
// Update the regular preview material even when previewing an expression to allow code view regeneration.
|
|
Material->PreEditChange(NULL);
|
|
Material->PostEditChange();
|
|
}
|
|
|
|
if (!PreviewExpression)
|
|
{
|
|
UpdateStatsMaterials();
|
|
|
|
// Null out the expression preview material so they can be GC'ed
|
|
ExpressionPreviewMaterial = NULL;
|
|
}
|
|
if (MaterialEditorInstance != nullptr)
|
|
{
|
|
if (MaterialLayersFunctionsInstance.IsValid())
|
|
{
|
|
MaterialLayersFunctionsInstance->SetEditorInstance(MaterialEditorInstance);
|
|
MaterialLayersFunctionsInstance->Refresh();
|
|
}
|
|
}
|
|
|
|
MaterialStatsManager->SetMaterial(bStatsFromPreviewMaterial ? Material : OriginalMaterial);
|
|
MaterialStatsManager->SignalMaterialChanged();
|
|
|
|
// Reregister all components that use the preview material, since UMaterial::PEC does not reregister components using a bIsPreviewMaterial=true material
|
|
RefreshPreviewViewport();
|
|
}
|
|
|
|
|
|
|
|
bool 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);
|
|
|
|
if (Material->bUsedAsSpecialEngineMaterial)
|
|
{
|
|
FSuppressableWarningDialog::FSetupInfo Info(
|
|
FText::Format(NSLOCTEXT("UnrealEd", "Error_CompileErrorsInDefaultMaterial", "The current material has compilation errors for feature level {0}.\nThis material is a Default Material which must be available as a code fallback at all times, compilation errors are not allowed."), FText::FromString(*FeatureLevelName)),
|
|
NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInDefaultMaterial_Title", "Error: Compilation errors in Default Material"), "Error_CompileErrorsInDefaultMaterial");
|
|
Info.ConfirmText = NSLOCTEXT("ModalDialogs", "CompileErrorsInDefaultMaterialOk", "Ok");
|
|
|
|
FSuppressableWarningDialog CompileErrors(Info);
|
|
CompileErrors.ShowModal();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
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 false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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->GetFName(),
|
|
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];
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid expression at index [%i] whilst saving material function."), ExpressionIndex);
|
|
|
|
// Link the expressions back to their function
|
|
if (CurrentExpression)
|
|
{
|
|
CurrentExpression->Material = NULL;
|
|
CurrentExpression->Function = MaterialFunction->ParentFunction;
|
|
}
|
|
}
|
|
for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionEditorComments.Num(); ExpressionIndex++)
|
|
{
|
|
UMaterialExpressionComment* CurrentExpression = MaterialFunction->ParentFunction->FunctionEditorComments[ExpressionIndex];
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid comment at index [%i] whilst saving material function."), ExpressionIndex);
|
|
|
|
// Link the expressions back to their function
|
|
if (CurrentExpression)
|
|
{
|
|
CurrentExpression->Material = NULL;
|
|
CurrentExpression->Function = MaterialFunction->ParentFunction;
|
|
}
|
|
}
|
|
|
|
// clear the dirty flag
|
|
bMaterialDirty = false;
|
|
bStatsFromPreviewMaterial = false;
|
|
|
|
UMaterialEditingLibrary::UpdateMaterialFunction(MaterialFunction->ParentFunction, Material);
|
|
}
|
|
// Handle propagation of the material being edited
|
|
else
|
|
{
|
|
FNavigationLockContext NavUpdateLock(ENavigationLockReason::MaterialUpdate);
|
|
|
|
// 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->GetFName(),
|
|
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;
|
|
|
|
UMaterialEditingLibrary::RecompileMaterial(OriginalMaterial);
|
|
|
|
// clear the dirty flag
|
|
bMaterialDirty = false;
|
|
bStatsFromPreviewMaterial = false;
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FMaterialEditor::UpdateMaterialinfoList_Old()
|
|
{
|
|
bool bForceDisplay = false;
|
|
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;
|
|
|
|
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)
|
|
{
|
|
bool bHasValidOutput = true;
|
|
int32 NumInputs = 0;
|
|
int32 NumOutputs = 0;
|
|
// For Material Layers
|
|
|
|
if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer)
|
|
{
|
|
// Material layers must have a single MA input and output only
|
|
for (UMaterialExpression* Expression : *MaterialFunction->GetFunctionExpressions())
|
|
{
|
|
if (UMaterialExpressionFunctionInput* InputExpression = Cast<UMaterialExpressionFunctionInput>(Expression))
|
|
{
|
|
++NumInputs;
|
|
if (NumInputs > 1 || !InputExpression->IsResultMaterialAttributes(0))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer graphs only support a single material attributes input."));
|
|
}
|
|
}
|
|
else if (UMaterialExpressionFunctionOutput* OutputExpression = Cast<UMaterialExpressionFunctionOutput>(Expression))
|
|
{
|
|
++NumOutputs;
|
|
if (NumOutputs > 1 || !OutputExpression->IsResultMaterialAttributes(0))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer graphs only support a single material attributes output."));
|
|
}
|
|
}
|
|
else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast<UMaterialExpressionMaterialAttributeLayers>(Expression))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer graphs do not support layers within layers."));
|
|
}
|
|
}
|
|
|
|
if (NumInputs > 1 || NumOutputs < 1)
|
|
{
|
|
CompileErrors.Add(TEXT("Layer graphs require a single material attributes output and optionally, a single material attributes input."));
|
|
}
|
|
}
|
|
else if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend)
|
|
{
|
|
// Material layer blends can have two MA inputs and single MA output only
|
|
for (UMaterialExpression* Expression : *MaterialFunction->GetFunctionExpressions())
|
|
{
|
|
if (UMaterialExpressionFunctionInput* InputExpression = Cast<UMaterialExpressionFunctionInput>(Expression))
|
|
{
|
|
++NumInputs;
|
|
if (NumInputs > 2 || !InputExpression->IsResultMaterialAttributes(0))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer blend graphs only support two material attributes inputs."));
|
|
}
|
|
}
|
|
else if (UMaterialExpressionFunctionOutput* OutputExpression = Cast<UMaterialExpressionFunctionOutput>(Expression))
|
|
{
|
|
++NumOutputs;
|
|
if (NumOutputs > 1 || !OutputExpression->IsResultMaterialAttributes(0))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer blend graphs only support a single material attributes output."));
|
|
}
|
|
}
|
|
else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast<UMaterialExpressionMaterialAttributeLayers>(Expression))
|
|
{
|
|
CompileErrors.Add(TEXT("Layer blend graphs do not support layers within layers."));
|
|
}
|
|
}
|
|
|
|
if (NumOutputs < 1)
|
|
{
|
|
CompileErrors.Add(TEXT("Layer blend graphs can have up to two material attributes inputs and a single output."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 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 there are no errors and stats are enabled - Stats show for Materials, layers and blends
|
|
if (CompileErrors.Num() == 0 && (!MaterialFunction || MaterialFunction->GetMaterialFunctionUsage() != Default))
|
|
{
|
|
TArray<FMaterialStatsUtils::FShaderInstructionsInfo> Results;
|
|
TArray<FMaterialStatsUtils::FShaderInstructionsInfo> EmptyMaterialResults;
|
|
FMaterialStatsUtils::GetRepresentativeInstructionCounts(Results, MaterialResource);
|
|
|
|
//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) : nullptr;
|
|
if (bShowBuiltinStats && bStatsFromPreviewMaterial && EmptyMaterialResource && Results.Num() > 0)
|
|
{
|
|
FMaterialStatsUtils::GetRepresentativeInstructionCounts(EmptyMaterialResults, EmptyMaterialResource);
|
|
|
|
if (EmptyMaterialResults.Num() > 0)
|
|
{
|
|
//The instruction counts should match. If not, the preview material has been changed without the EmptyMaterial being updated to match.
|
|
if (ensure(Results.Num() == EmptyMaterialResults.Num()))
|
|
{
|
|
bBuiltinStats = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 InstructionIndex = 0; InstructionIndex < Results.Num(); InstructionIndex++)
|
|
{
|
|
FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"), *Results[InstructionIndex].ShaderDescription, Results[InstructionIndex].InstructionCount);
|
|
if (bBuiltinStats)
|
|
{
|
|
InstructionCountString += FString::Printf(TEXT(" - Built-in instructions: %u"), EmptyMaterialResults[InstructionIndex].InstructionCount);
|
|
}
|
|
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 = GetExpectedFeatureLevelMaxTextureSamplers(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);
|
|
}
|
|
|
|
// Display estimated texture look-up/sample counts
|
|
uint32 NumVSTextureSamples = 0, NumPSTextureSamples = 0;
|
|
MaterialResource->GetEstimatedNumTextureSamples(NumVSTextureSamples, NumPSTextureSamples);
|
|
|
|
if (NumVSTextureSamples > 0 || NumPSTextureSamples > 0)
|
|
{
|
|
FString SamplesString = FString::Printf(TEXT("Texture Lookups (Est.): VS(%u), PS(%u)"), NumVSTextureSamples, NumPSTextureSamples);
|
|
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(SamplesString, FLinearColor::Yellow)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(SamplesString)));
|
|
Messages.Add(Line);
|
|
}
|
|
|
|
// Display the number of custom/user interpolators used by the material.
|
|
uint32 UVScalarsUsed, CustomInterpolatorScalarsUsed;
|
|
MaterialResource->GetUserInterpolatorUsage(UVScalarsUsed, CustomInterpolatorScalarsUsed);
|
|
|
|
if (UVScalarsUsed > 0 || CustomInterpolatorScalarsUsed > 0)
|
|
{
|
|
uint32 TotalScalars = UVScalarsUsed + CustomInterpolatorScalarsUsed;
|
|
uint32 MaxScalars = FMath::DivideAndRoundUp(TotalScalars, 4u) * 4;
|
|
|
|
FString InterpolatorsString = FString::Printf(TEXT("User interpolators: %u/%u Scalars (%u/4 Vectors) (TexCoords: %i, Custom: %i)"),
|
|
TotalScalars, MaxScalars, MaxScalars / 4, UVScalarsUsed, CustomInterpolatorScalarsUsed);
|
|
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InterpolatorsString, FLinearColor::Yellow)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create( EMessageSeverity::Info );
|
|
Line->AddToken(FTextToken::Create(FText::FromString(InterpolatorsString)));
|
|
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;
|
|
|
|
auto Listing = MaterialStatsManager->GetOldStatsListing();
|
|
Listing->ClearMessages();
|
|
Listing->AddMessages(Messages);
|
|
|
|
if (bForceDisplay)
|
|
{
|
|
TabManager->InvokeTab(MaterialStatsManager->GetGridOldStatsTabName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdateMaterialInfoList()
|
|
{
|
|
// setup stats widget visibility
|
|
if (MaterialFunction)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial;
|
|
|
|
// check for errors
|
|
TArray<FString> CompileErrors;
|
|
if (MaterialFunction && ExpressionPreviewMaterial)
|
|
{
|
|
// Add a compile error message for functions missing an output
|
|
CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel)->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"));
|
|
}
|
|
}
|
|
|
|
// compute error crc
|
|
FString NewErrorHash;
|
|
for (int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++)
|
|
{
|
|
NewErrorHash += FMD5::HashAnsiString(*CompileErrors[ErrorIndex]);
|
|
}
|
|
|
|
TSharedPtr<SWidget> TitleBar = GraphEditor->GetTitleBar();
|
|
if (NewErrorHash != MaterialErrorHash)
|
|
{
|
|
MaterialErrorHash = NewErrorHash;
|
|
MaterialInfoList.Reset();
|
|
|
|
TArray< TSharedRef<class FTokenizedMessage> > Messages;
|
|
FString FeatureLevelName;
|
|
GetFeatureLevelName(GMaxRHIFeatureLevel, FeatureLevelName);
|
|
for (int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++)
|
|
{
|
|
FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]);
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Error);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(ErrorString)));
|
|
Messages.Add(Line);
|
|
|
|
MaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red)));
|
|
}
|
|
|
|
StatsListing->ClearMessages();
|
|
StatsListing->AddMessages(Messages);
|
|
|
|
StaticCastSharedPtr<SMaterialEditorTitleBar>(TitleBar)->RequestRefresh();
|
|
TitleBar->SetVisibility(EVisibility::Visible);
|
|
}
|
|
else if (!CompileErrors.Num())
|
|
{
|
|
TitleBar->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
|
|
// extract material stats
|
|
MaterialStatsManager->SetMaterial(MaterialForStats);
|
|
MaterialStatsManager->Update();
|
|
}
|
|
|
|
void FMaterialEditor::UpdateGraphNodeStates()
|
|
{
|
|
const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel);
|
|
const FMaterialResource* ErrorMaterialResourceES2 = NULL;
|
|
|
|
bool bUpdatedErrorState = false;
|
|
bool bToggledVisibleState = bPreviewFeaturesChanged;
|
|
bool bShowAllNodes = true;
|
|
|
|
TArray<UMaterialExpression*> VisibleExpressions;
|
|
|
|
if (bPreviewFeaturesChanged)
|
|
{
|
|
Material->GetAllReferencedExpressions(VisibleExpressions, nullptr, NodeFeatureLevel, NodeQualityLevel);
|
|
if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num)
|
|
{
|
|
bShowAllNodes = 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)
|
|
{
|
|
check(MaterialNode->MaterialExpression);
|
|
|
|
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 (MaterialNode->MaterialExpression && bPreviewFeaturesChanged)
|
|
{
|
|
if ((bShowAllNodes || VisibleExpressions.Contains(MaterialNode->MaterialExpression)))
|
|
{
|
|
MaterialNode->SetForceDisplayAsDisabled(false);
|
|
}
|
|
else if (!bShowAllNodes && !VisibleExpressions.Contains(MaterialNode->MaterialExpression))
|
|
{
|
|
MaterialNode->SetForceDisplayAsDisabled(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bPreviewFeaturesChanged = false;
|
|
|
|
if (bUpdatedErrorState || bToggledVisibleState)
|
|
{
|
|
// 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);
|
|
Collector.AddReferencedObject(MaterialEditorInstance);
|
|
Collector.AddReferencedObject(PreviewExpression);
|
|
for (FMatExpressionPreview& ExpressionPreview : ExpressionPreviews)
|
|
{
|
|
ExpressionPreview.AddReferencedObjects(Collector);
|
|
}
|
|
}
|
|
|
|
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( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::OnToggleRealtime ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::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.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::OnSelectDownstreamNodes));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SelectUpstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpstreamNodes));
|
|
|
|
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));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.QualityLevel_All,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Num),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Num));
|
|
ToolkitCommands->MapAction(
|
|
Commands.QualityLevel_High,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::High),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::High));
|
|
ToolkitCommands->MapAction(
|
|
Commands.QualityLevel_Medium,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Medium),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Medium));
|
|
ToolkitCommands->MapAction(
|
|
Commands.QualityLevel_Low,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Low),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Low));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_All,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::Num),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::Num));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_ES2,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES2),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES2));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_ES31,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES3_1),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES3_1));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_SM4,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM4),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM4));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_SM5,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM5),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM5));
|
|
}
|
|
|
|
void FMaterialEditor::OnApply()
|
|
{
|
|
UE_LOG(LogMaterialEditor, Log, TEXT("Applying material %s"), *GetEditingObjects()[0]->GetName());
|
|
|
|
UpdateOriginalMaterial();
|
|
|
|
GEditor->OnSceneMaterialsModified();
|
|
}
|
|
|
|
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::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);
|
|
UMaterialExpressionTextureObject* TextureObjectExpression = Cast<UMaterialExpressionTextureObject>(CurrentSelectedExpression);
|
|
UMaterialExpressionComponentMask* ComponentMaskExpression = Cast<UMaterialExpressionComponentMask>(CurrentSelectedExpression);
|
|
UMaterialExpressionParticleSubUV* ParticleSubUVExpression = Cast<UMaterialExpressionParticleSubUV>(CurrentSelectedExpression);
|
|
UMaterialExpressionScalarParameter* ScalarParameterExpression = Cast<UMaterialExpressionScalarParameter>(CurrentSelectedExpression);
|
|
UMaterialExpressionVectorParameter* VectorParameterExpression = Cast<UMaterialExpressionVectorParameter>(CurrentSelectedExpression);
|
|
UMaterialExpressionTextureObjectParameter* TextureObjectParameterExpression = Cast<UMaterialExpressionTextureObjectParameter>(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 (TextureObjectExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureObjectParameter::StaticClass();
|
|
}
|
|
else if (TextureObjectParameterExpression) // Has to come before TextureSample comparison
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureObject::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), true, true );
|
|
if (NewExpression)
|
|
{
|
|
UMaterialGraphNode* NewGraphNode = CastChecked<UMaterialGraphNode>(NewExpression->GraphNode);
|
|
NewGraphNode->ReplaceNode(GraphNode);
|
|
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Copy over any common values
|
|
if (GraphNode->NodeComment.Len() > 0)
|
|
{
|
|
bNeedsRefresh = true;
|
|
NewGraphNode->NodeComment = GraphNode->NodeComment;
|
|
}
|
|
|
|
// 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 && !TextureObjectParameterExpression)
|
|
{
|
|
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->CoordinatesDX = TextureSampleExpression->CoordinatesDX;
|
|
NewTextureExpr->CoordinatesDY = TextureSampleExpression->CoordinatesDY;
|
|
NewTextureExpr->MipValueMode = TextureSampleExpression->MipValueMode;
|
|
NewGraphNode->ReconstructNode();
|
|
}
|
|
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)
|
|
{
|
|
GraphEditor->NotifyGraphChanged();
|
|
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::OnSelectDownstreamNodes()
|
|
{
|
|
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::OnSelectUpstreamNodes()
|
|
{
|
|
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();
|
|
}
|
|
|
|
UClass* FMaterialEditor::GetOnPromoteToParameterClass(UEdGraphPin* TargetPin)
|
|
{
|
|
UMaterialGraphNode_Root* RootPinNode = Cast<UMaterialGraphNode_Root>(TargetPin->GetOwningNode());
|
|
UMaterialGraphNode* OtherPinNode = Cast<UMaterialGraphNode>(TargetPin->GetOwningNode());
|
|
|
|
if (RootPinNode != nullptr)
|
|
{
|
|
EMaterialProperty propertyId = (EMaterialProperty)FCString::Atoi(*TargetPin->PinType.PinSubCategory.ToString());
|
|
|
|
switch (propertyId)
|
|
{
|
|
case MP_Opacity:
|
|
case MP_Metallic:
|
|
case MP_Specular:
|
|
case MP_Roughness:
|
|
case MP_TessellationMultiplier:
|
|
case MP_CustomData0:
|
|
case MP_CustomData1:
|
|
case MP_AmbientOcclusion:
|
|
case MP_Refraction:
|
|
case MP_PixelDepthOffset:
|
|
case MP_ShadingModel:
|
|
case MP_OpacityMask: return UMaterialExpressionScalarParameter::StaticClass();
|
|
|
|
case MP_WorldPositionOffset:
|
|
case MP_WorldDisplacement:
|
|
case MP_EmissiveColor:
|
|
case MP_BaseColor:
|
|
case MP_SubsurfaceColor:
|
|
case MP_SpecularColor:
|
|
case MP_Normal: return UMaterialExpressionVectorParameter::StaticClass();
|
|
}
|
|
}
|
|
else if (OtherPinNode)
|
|
{
|
|
const TArray<FExpressionInput*> ExpressionInputs = OtherPinNode->MaterialExpression->GetInputs();
|
|
FName TargetPinName = OtherPinNode->GetShortenPinName(TargetPin->PinName);
|
|
|
|
for (int32 Index = 0; Index < ExpressionInputs.Num(); ++Index)
|
|
{
|
|
FExpressionInput* Input = ExpressionInputs[Index];
|
|
FName InputName = OtherPinNode->MaterialExpression->GetInputName(Index);
|
|
InputName = OtherPinNode->GetShortenPinName(InputName);
|
|
|
|
if (InputName == TargetPinName)
|
|
{
|
|
switch (OtherPinNode->MaterialExpression->GetInputType(Index))
|
|
{
|
|
case MCT_Float1:
|
|
case MCT_Float: return UMaterialExpressionScalarParameter::StaticClass();
|
|
|
|
case MCT_Float2:
|
|
case MCT_Float3:
|
|
case MCT_Float4: return UMaterialExpressionVectorParameter::StaticClass();
|
|
|
|
case MCT_StaticBool: return UMaterialExpressionStaticBoolParameter::StaticClass();
|
|
|
|
case MCT_Texture2D:
|
|
case MCT_TextureCube:
|
|
case MCT_VolumeTexture:
|
|
case MCT_Texture: return UMaterialExpressionTextureObjectParameter::StaticClass();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FMaterialEditor::OnPromoteToParameter()
|
|
{
|
|
UEdGraphPin* TargetPin = GraphEditor->GetGraphPinForMenu();
|
|
UMaterialGraphNode_Base* PinNode = Cast<UMaterialGraphNode_Base>(TargetPin->GetOwningNode());
|
|
|
|
FMaterialGraphSchemaAction_NewNode Action;
|
|
Action.MaterialExpressionClass = GetOnPromoteToParameterClass(TargetPin);
|
|
|
|
if (Action.MaterialExpressionClass != nullptr)
|
|
{
|
|
check(PinNode);
|
|
UEdGraph* GraphObj = PinNode->GetGraph();
|
|
check(GraphObj);
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("PromoteToParameter", "Promote To Parameter"));
|
|
GraphObj->Modify();
|
|
|
|
// Set position of new node to be close to node we clicked on
|
|
FVector2D NewNodePos;
|
|
NewNodePos.X = PinNode->NodePosX - 100;
|
|
NewNodePos.Y = PinNode->NodePosY;
|
|
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Action.PerformAction(GraphObj, TargetPin, NewNodePos));
|
|
|
|
if (MaterialNode->MaterialExpression->HasAParameterName())
|
|
{
|
|
MaterialNode->MaterialExpression->SetParameterName(TargetPin->PinName);
|
|
MaterialNode->MaterialExpression->ValidateParameterName(false);
|
|
}
|
|
}
|
|
if (MaterialEditorInstance != nullptr)
|
|
{
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnCanPromoteToParameter()
|
|
{
|
|
UEdGraphPin* TargetPin = GraphEditor->GetGraphPinForMenu();
|
|
|
|
if (ensure(TargetPin) && TargetPin->LinkedTo.Num() == 0)
|
|
{
|
|
return GetOnPromoteToParameterClass(TargetPin) != nullptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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::OnMaterialUsageFlagsChanged(UMaterial* MaterialThatChanged, int32 FlagThatChanged)
|
|
{
|
|
EMaterialUsage Flag = static_cast<EMaterialUsage>(FlagThatChanged);
|
|
if(MaterialThatChanged == OriginalMaterial)
|
|
{
|
|
bool bNeedsRecompile = false;
|
|
Material->SetMaterialUsage(bNeedsRecompile, 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"))
|
|
[
|
|
SNew( SOverlay )
|
|
+ SOverlay::Slot()
|
|
[
|
|
PreviewViewport.ToSharedRef()
|
|
]
|
|
+ SOverlay::Slot()
|
|
[
|
|
PreviewUIViewport.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
PreviewViewport->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)
|
|
{
|
|
TSharedPtr<SDockTab> DetailsTab = 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();
|
|
}
|
|
SpawnedDetailsTab = DetailsTab;
|
|
return DetailsTab.ToSharedRef();
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_PreviewSettings(const FSpawnTabArgs& Args)
|
|
{
|
|
check(Args.GetTabId() == PreviewSettingsTabId);
|
|
|
|
TSharedRef<SWidget> InWidget = SNullWidget::NullWidget;
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked<FAdvancedPreviewSceneModule>("AdvancedPreviewScene");
|
|
InWidget = AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(PreviewViewport->GetPreviewScene());
|
|
}
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details"))
|
|
.Label(LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings"))
|
|
[
|
|
SNew(SBox)
|
|
[
|
|
InWidget
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_ParameterDefaults(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details"))
|
|
.Label(LOCTEXT("ParameterDefaults", "Parameter Defaults"))
|
|
[
|
|
SNew(SBox)
|
|
[
|
|
MaterialParametersOverviewWidget.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_LayerProperties(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("MaterialInstanceEditor.Tabs.Properties"))
|
|
.Label(LOCTEXT("MaterialLayerPropertiesTitle", "Layer Parameter Preview"))
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
MaterialLayersFunctionsInstance.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( ExpressionPreviewMaterial == NULL )
|
|
{
|
|
// Create the expression preview material if it hasnt already been created
|
|
ExpressionPreviewMaterial = NewObject<UPreviewMaterial>(GetTransientPackage(), NAME_None, RF_Public);
|
|
ExpressionPreviewMaterial->bIsPreviewMaterial = true;
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
|
|
}
|
|
else if (Material->IsPostProcessMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_PostProcess;
|
|
}
|
|
}
|
|
|
|
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* SelectedAsset = nullptr;
|
|
if (bAutoAssignResource)
|
|
{
|
|
// Load selected assets
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
SelectedAsset = GEditor->GetSelectedObjects()->GetTop<UObject>();
|
|
}
|
|
|
|
NewExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, NewExpressionClass, SelectedAsset, NodePos.X, NodePos.Y);
|
|
|
|
if (NewExpression)
|
|
{
|
|
Material->MaterialGraph->AddExpression(NewExpression, bAutoSelect);
|
|
|
|
// Select the new node.
|
|
if ( bAutoSelect )
|
|
{
|
|
GraphEditor->SetNodeSelection(NewExpression->GraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
RegenerateCodeView();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
|
|
RefreshExpressionPreviews();
|
|
GraphEditor->NotifyGraphChanged();
|
|
SetMaterialDirty();
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
return NewExpression;
|
|
}
|
|
|
|
UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos)
|
|
{
|
|
UMaterialExpressionComment* NewComment = NULL;
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorCreateComment", "Material Editor: Create comment"));
|
|
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();
|
|
|
|
Material->MaterialGraph->AddComment(NewComment, true);
|
|
|
|
// 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
|
|
SetPreviewExpression(nullptr);
|
|
}
|
|
RegenerateCodeView();
|
|
}
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
|
|
if ( bHaveExpressionsToDelete )
|
|
{
|
|
RefreshExpressionPreviews();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanDeleteNodes() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
bool bDeletableNodeExists = false;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* GraphNode = Cast<UEdGraphNode>(*NodeIt);
|
|
if (GraphNode && GraphNode->CanUserDeleteNode())
|
|
{
|
|
bDeletableNodeExists = true;
|
|
}
|
|
}
|
|
|
|
return SelectedNodes.Num() > 0 && bDeletableNodeExists;
|
|
}
|
|
|
|
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);
|
|
FPlatformApplicationMisc::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;
|
|
FPlatformApplicationMisc::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 = MaterialFunction;
|
|
|
|
// Make sure the param name is valid after the paste
|
|
if (NewExpression->HasAParameterName())
|
|
{
|
|
NewExpression->ValidateParameterName();
|
|
}
|
|
|
|
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);
|
|
Material->AddExpressionParameter(NewExpression, Material->EditorParameters);
|
|
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>( NewExpression );
|
|
if( FunctionInput )
|
|
{
|
|
FunctionInput->ConditionallyGenerateId(true);
|
|
FunctionInput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>( NewExpression );
|
|
if( FunctionOutput )
|
|
{
|
|
FunctionOutput->ConditionallyGenerateId(true);
|
|
FunctionOutput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionMaterialFunctionCall* FunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>( NewExpression );
|
|
if( FunctionCall )
|
|
{
|
|
// When pasting new nodes, we don't want to break all node links as this information is used by UpdateMaterialAfterGraphChange() below,
|
|
// to recreate all the connections in the pasted group.
|
|
// Just update the function input/outputs here.
|
|
const bool bRecreateAndLinkNode = false;
|
|
FunctionCall->UpdateFromFunctionResource(bRecreateAndLinkNode);
|
|
|
|
// If an unknown material function has been pasted, remove the graph node pins (as the expression will also have had its inputs/outputs removed).
|
|
// This will be displayed as an orphaned "Unspecified Function" node.
|
|
if (FunctionCall->MaterialFunction == nullptr &&
|
|
FunctionCall->FunctionInputs.Num() == 0 &&
|
|
FunctionCall->FunctionOutputs.Num() == 0)
|
|
{
|
|
GraphNode->Pins.Empty();
|
|
}
|
|
}
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(Node))
|
|
{
|
|
if (CommentNode->MaterialExpressionComment)
|
|
{
|
|
CommentNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate;
|
|
CommentNode->MaterialExpressionComment->Material = Material;
|
|
CommentNode->MaterialExpressionComment->Function = MaterialFunction;
|
|
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;
|
|
FPlatformApplicationMisc::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()
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
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()
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
int32 NumExpressions = Material->Expressions.Num();
|
|
GEditor->UndoTransaction();
|
|
|
|
if(NumExpressions != Material->Expressions.Num())
|
|
{
|
|
Material->BuildEditorParameterList();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RedoGraphAction()
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
// 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::OnAlignTop()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignTop();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignMiddle()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignMiddle();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignBottom()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignMiddle();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignLeft()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignLeft();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignCenter()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignCenter();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignRight()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnAlignRight();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnStraightenConnections()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnStraightenConnections();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnDistributeNodesH()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnDistributeNodesH();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnDistributeNodesV()
|
|
{
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->OnDistributeNodesV();
|
|
}
|
|
}
|
|
|
|
|
|
void FMaterialEditor::PostUndo(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
Material->BuildEditorParameterList();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
|
|
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 )
|
|
{
|
|
if (MaterialLayersFunctionsInstance.IsValid())
|
|
{
|
|
MaterialLayersFunctionsInstance->SetEditorInstance(MaterialEditorInstance);
|
|
}
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
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);
|
|
}
|
|
}
|
|
else if( NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, MaterialDomain) ||
|
|
NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, ShadingModel))
|
|
{
|
|
Material->MaterialGraph->RebuildGraph();
|
|
TArray<TWeakObjectPtr<UObject>> SelectedObjects = MaterialDetailsView->GetSelectedObjects();
|
|
MaterialDetailsView->SetObjects( SelectedObjects, true );
|
|
|
|
if (ExpressionPreviewMaterial)
|
|
{
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
|
|
}
|
|
else
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_Surface;
|
|
}
|
|
|
|
SetPreviewMaterial(ExpressionPreviewMaterial);
|
|
}
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
}
|
|
|
|
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 (SelectedNode->MaterialExpression->IsA(UMaterialExpressionDynamicParameter::StaticClass()))
|
|
{
|
|
Material->UpdateExpressionDynamicParameters(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();
|
|
}
|
|
|
|
GetDefault<UMaterialGraphSchema>()->ForceVisualizationCacheClear();
|
|
}
|
|
|
|
delete ScopedTransaction;
|
|
ScopedTransaction = NULL;
|
|
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
}
|
|
|
|
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(bool bForceRefreshAll /*= false*/)
|
|
{
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
if ( bAlwaysRefreshAllPreviews || bForceRefreshAll)
|
|
{
|
|
// we need to make sure the rendering thread isn't drawing these tiles
|
|
SCOPED_SUSPEND_RENDERING_THREAD(true);
|
|
|
|
// Refresh all expression previews.
|
|
ExpressionPreviews.Empty();
|
|
|
|
for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ++ExpressionIndex)
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ExpressionIndex];
|
|
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(MaterialExpression->GraphNode);
|
|
if (GraphNode)
|
|
{
|
|
GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound();
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(MaterialExpression->GraphNode);
|
|
if (GraphNode)
|
|
{
|
|
GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound();
|
|
}
|
|
|
|
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 FMatExpressionPreview(MaterialExpression);
|
|
ExpressionPreviews.Add(Preview);
|
|
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();
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
|
|
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::OnSelectDownstreamNodes)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectUpstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpstreamNodes)
|
|
);
|
|
|
|
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)
|
|
);
|
|
|
|
|
|
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().PromoteToParameter,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteToParameter),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::OnCanPromoteToParameter)
|
|
);
|
|
|
|
// Alignment Commands
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesTop,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignTop)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesMiddle,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignMiddle)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesBottom,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignBottom)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesLeft,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignLeft)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesCenter,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignCenter)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesRight,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignRight)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StraightenConnections,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnStraightenConnections)
|
|
);
|
|
|
|
// Distribution Commands
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesHorizontally,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnDistributeNodesH)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesVertically,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnDistributeNodesV)
|
|
);
|
|
|
|
}
|
|
|
|
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.OnVerifyTextCommit = FOnNodeVerifyTextCommit::CreateSP(this, &FMaterialEditor::OnVerifyNodeTextCommit);
|
|
InEvents.OnSpawnNodeByShortcut = SGraphEditor::FOnSpawnNodeByShortcut::CreateSP(this, &FMaterialEditor::OnSpawnGraphNodeByShortcut, static_cast<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(this, &FMaterialEditor::GetGraphAppearance)
|
|
.GraphToEdit(Material->MaterialGraph)
|
|
.GraphEvents(InEvents)
|
|
.ShowGraphStateOverlay(false);
|
|
}
|
|
|
|
FGraphAppearanceInfo FMaterialEditor::GetGraphAppearance() const
|
|
{
|
|
FGraphAppearanceInfo AppearanceInfo;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
switch (MaterialFunction->GetMaterialFunctionUsage())
|
|
{
|
|
case EMaterialFunctionUsage::MaterialLayer:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialLayer", "MATERIAL LAYER");
|
|
break;
|
|
case EMaterialFunctionUsage::MaterialLayerBlend:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialLayerBlend", "MATERIAL LAYER BLEND");
|
|
break;
|
|
default:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialFunction", "MATERIAL FUNCTION");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Material", "MATERIAL");
|
|
}
|
|
|
|
return AppearanceInfo;
|
|
}
|
|
|
|
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.ToString();
|
|
}
|
|
|
|
if (FunctionOutput && MaterialFunction && MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::Default)
|
|
{
|
|
if (!bFirstExpression)
|
|
{
|
|
FunctionWarningString += TEXT(", ");
|
|
}
|
|
bFirstExpression = false;
|
|
FunctionWarningString += FunctionOutput->OutputName.ToString();
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
FocusDetailsPanel();
|
|
}
|
|
|
|
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
|
|
FColorPickerArgs PickerArgs;
|
|
PickerArgs.ParentWidget = GraphEditor;//AsShared();
|
|
PickerArgs.bUseAlpha = ChannelEditStruct.Alpha != nullptr;
|
|
PickerArgs.bOnlyRefreshOnOk = false;
|
|
PickerArgs.bOnlyRefreshOnMouseUp = 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);
|
|
PickerArgs.OptionalOwningDetailsView = MaterialDetailsView;
|
|
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());
|
|
MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnVerifyNodeTextCommit(const FText& NewText, UEdGraphNode* NodeBeingChanged, FText& OutErrorMessage)
|
|
{
|
|
bool bValid = true;
|
|
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(NodeBeingChanged);
|
|
if( MaterialNode && MaterialNode->MaterialExpression && MaterialNode->MaterialExpression->IsA<UMaterialExpressionParameter>() )
|
|
{
|
|
if( NewText.ToString().Len() > NAME_SIZE )
|
|
{
|
|
OutErrorMessage = FText::Format( LOCTEXT("MaterialEditorExpressionError_NameTooLong", "Parameter names must be less than {0} characters"), FText::AsNumber(NAME_SIZE));
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
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->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();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::NotifyExternalMaterialChange()
|
|
{
|
|
MaterialStatsManager->SignalMaterialChanged();
|
|
}
|
|
|
|
void FMaterialEditor::FocusDetailsPanel()
|
|
{
|
|
if (SpawnedDetailsTab.IsValid() && !SpawnedDetailsTab.Pin()->IsForeground())
|
|
{
|
|
SpawnedDetailsTab.Pin()->DrawAttention();
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|