You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
7304 lines
249 KiB
C++
7304 lines
249 KiB
C++
// Copyright 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 "EngineModule.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 "Widgets/Input/SNumericEntryBox.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "WorkflowOrientedApp/WorkflowUObjectDocuments.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/MaterialGraphNode_Composite.h"
|
|
#include "MaterialGraph/MaterialGraphNode_PinBase.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 "StaticParameterSet.h"
|
|
#include "Engine/TextureCube.h"
|
|
#include "Engine/Texture2DArray.h"
|
|
#include "Engine/TextureCubeArray.h"
|
|
#include "Dialogs/Dialogs.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Editor.h"
|
|
#include "MaterialEditorModule.h"
|
|
#include "MaterialEditingLibrary.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "MaterialCachedData.h"
|
|
|
|
#include "Materials/MaterialExpressionBreakMaterialAttributes.h"
|
|
#include "Materials/MaterialExpressionCollectionParameter.h"
|
|
#include "Materials/MaterialExpressionComment.h"
|
|
#include "Materials/MaterialExpressionComposite.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/MaterialExpressionPinBase.h"
|
|
#include "Materials/MaterialExpressionTextureBase.h"
|
|
#include "Materials/MaterialExpressionTextureSample.h"
|
|
#include "Materials/MaterialExpressionParticleSubUV.h"
|
|
#include "Materials/MaterialExpressionReroute.h"
|
|
#include "Materials/MaterialExpressionRuntimeVirtualTextureSample.h"
|
|
#include "Materials/MaterialExpressionRuntimeVirtualTextureSampleParameter.h"
|
|
#include "Materials/MaterialExpressionScalarParameter.h"
|
|
#include "Materials/MaterialExpressionStaticComponentMaskParameter.h"
|
|
#include "Materials/MaterialExpressionStaticSwitchParameter.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter.h"
|
|
#include "Materials/MaterialExpressionTextureObjectParameter.h"
|
|
#include "Materials/MaterialExpressionTextureObject.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter2D.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameterCube.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter2DArray.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameterCubeArray.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameterSubUV.h"
|
|
#include "Materials/MaterialExpressionTransformPosition.h"
|
|
#include "Materials/MaterialExpressionVectorParameter.h"
|
|
#include "Materials/MaterialExpressionDoubleVectorParameter.h"
|
|
#include "Materials/MaterialExpressionStaticBoolParameter.h"
|
|
#include "Materials/MaterialExpressionCustomOutput.h"
|
|
#include "Materials/MaterialFunction.h"
|
|
#include "Materials/MaterialFunctionInstance.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
|
|
#include "MaterialGraphNode_Knot.h"
|
|
#include "MaterialEditorActions.h"
|
|
#include "MaterialExpressionClasses.h"
|
|
#include "MaterialCompiler.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Tabs/MaterialEditorTabFactories.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 "Materials/MaterialExpressionNamedReroute.h"
|
|
#include "Materials/MaterialExpressionReroute.h"
|
|
#include "Materials/MaterialExpressionStrata.h"
|
|
|
|
#include "MaterialStats.h"
|
|
#include "MaterialEditorTabs.h"
|
|
#include "MaterialEditorModes.h"
|
|
#include "Materials/MaterialExpression.h"
|
|
#include "MaterialCachedHLSLTree.h"
|
|
|
|
#include "SMaterialParametersOverviewWidget.h"
|
|
#include "SMaterialEditorCustomPrimitiveDataWidget.h"
|
|
#include "IPropertyRowGenerator.h"
|
|
#include "Widgets/Layout/SScrollBox.h"
|
|
#include "UObject/TextProperty.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "ToolMenus.h"
|
|
#include "MaterialEditorContext.h"
|
|
#include "UObject/MetaData.h"
|
|
#include "ToolMenus.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);
|
|
|
|
///////////////////////////
|
|
// FMatExpressionPreview //
|
|
///////////////////////////
|
|
|
|
FMatExpressionPreview::FMatExpressionPreview()
|
|
: FMaterial()
|
|
, FMaterialRenderProxy(TEXT("FMatExpressionPreview"))
|
|
, UnrelatedNodesOpacity(1.0f)
|
|
{
|
|
// Register this FMaterial derivative with AddEditorLoadedMaterialResource since it does not have a corresponding UMaterialInterface
|
|
FMaterial::AddEditorLoadedMaterialResource(this);
|
|
SetQualityLevelProperties(GMaxRHIFeatureLevel);
|
|
}
|
|
|
|
FMatExpressionPreview::FMatExpressionPreview(UMaterialExpression* InExpression)
|
|
: FMaterial()
|
|
, FMaterialRenderProxy(GetPathNameSafe(InExpression->Material))
|
|
, UnrelatedNodesOpacity(1.0f)
|
|
, Expression(InExpression)
|
|
{
|
|
FMaterial::AddEditorLoadedMaterialResource(this);
|
|
FPlatformMisc::CreateGuid(Id);
|
|
|
|
check(InExpression->Material && InExpression->Material->GetExpressions().Contains(InExpression));
|
|
SetQualityLevelProperties(GMaxRHIFeatureLevel);
|
|
|
|
UMaterial* BaseMaterial = InExpression->Material;
|
|
if (BaseMaterial->IsUsingNewHLSLGenerator())
|
|
{
|
|
FMaterialCachedHLSLTree* LocalTree = new FMaterialCachedHLSLTree();
|
|
LocalTree->GenerateTree(BaseMaterial, nullptr, InExpression);
|
|
CachedHLSLTree.Reset(LocalTree);
|
|
|
|
FMaterialCachedExpressionData* LocalCachedData = new FMaterialCachedExpressionData();
|
|
LocalCachedData->UpdateForCachedHLSLTree(*LocalTree, nullptr);
|
|
CachedExpressionData.Reset(LocalCachedData);
|
|
}
|
|
else
|
|
{
|
|
ReferencedTextures = InExpression->Material->GetReferencedTextures();
|
|
}
|
|
}
|
|
|
|
FMatExpressionPreview::~FMatExpressionPreview()
|
|
{
|
|
}
|
|
|
|
void FMatExpressionPreview::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObjects(ReferencedTextures);
|
|
if (CachedExpressionData)
|
|
{
|
|
CachedExpressionData->AddReferencedObjects(Collector);
|
|
}
|
|
}
|
|
|
|
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;
|
|
const uint32 Output0Type = Expression->GetOutputType(OutputIndex);
|
|
int32 PreviewCodeChunk = INDEX_NONE;
|
|
if (Output0Type != MCT_Strata)
|
|
{
|
|
PreviewCodeChunk = Expression->CompilePreview(Compiler, OutputIndex);
|
|
}
|
|
else
|
|
{
|
|
// This is used for instance to prevent any Strata typed output to be used as preview, for instance for material functions.
|
|
PreviewCodeChunk = Compiler->Constant(0.0f);
|
|
}
|
|
|
|
// Get back into gamma corrected space, as DrawTile does not do this adjustment.
|
|
Ret = Compiler->Power(Compiler->Max(PreviewCodeChunk, 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 if (Property == MP_FrontMaterial)
|
|
{
|
|
Ret = Compiler->StrataCreateAndRegisterNullMaterial();
|
|
}
|
|
else
|
|
{
|
|
Ret = Compiler->Constant(1.0f);
|
|
}
|
|
|
|
// output should always be the right type for this property
|
|
return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property), MFCF_ExactMatch);
|
|
}
|
|
|
|
void FMatExpressionPreview::NotifyCompilationFinished()
|
|
{
|
|
if (Expression.IsValid() && Expression->GraphNode)
|
|
{
|
|
CastChecked<UMaterialGraphNode>(Expression->GraphNode)->bPreviewNeedsUpdate = true;
|
|
}
|
|
FMaterialRenderProxy::CacheUniformExpressions_GameThread(true);
|
|
}
|
|
|
|
TArrayView<const TObjectPtr<UObject>> FMatExpressionPreview::GetReferencedTextures() const
|
|
{
|
|
if (CachedExpressionData)
|
|
{
|
|
// Path for new HLSL translator
|
|
return MakeArrayView(CachedExpressionData->ReferencedTextures);
|
|
}
|
|
|
|
// Legacy path
|
|
return MakeArrayView(ReferencedTextures);
|
|
}
|
|
|
|
const FMaterialCachedHLSLTree* FMatExpressionPreview::GetCachedHLSLTree() const
|
|
{
|
|
return CachedHLSLTree.Get();
|
|
}
|
|
|
|
bool FMatExpressionPreview::IsUsingControlFlow() const
|
|
{
|
|
if (Expression.IsValid() && Expression->Material)
|
|
{
|
|
return Expression->Material->IsUsingControlFlow();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FMatExpressionPreview::IsUsingNewHLSLGenerator() const
|
|
{
|
|
if (Expression.IsValid() && Expression->Material)
|
|
{
|
|
return Expression->Material->IsUsingNewHLSLGenerator();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/////////////////////
|
|
// FMaterialEditor //
|
|
/////////////////////
|
|
|
|
void FMaterialEditor::RegisterToolbarTab(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) )
|
|
.SetDisplayName( LOCTEXT("ViewportTab", "Viewport") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) )
|
|
.SetDisplayName( LOCTEXT("DetailsTab", "Details") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) )
|
|
.SetDisplayName( LOCTEXT("PaletteTab", "Palette") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.Palette"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find))
|
|
.SetDisplayName(LOCTEXT("FindTab", "Find Results"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.FindResults"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PreviewSettingsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_PreviewSettings))
|
|
.SetDisplayName( LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::ParameterDefaultsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_ParameterDefaults))
|
|
.SetDisplayName(LOCTEXT("ParametersTab", "Parameters"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::CustomPrimitiveTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_CustomPrimitiveData))
|
|
.SetDisplayName(LOCTEXT("CustomPrimitiveTab", "Custom Primitive Data"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(FMaterialEditorTabs::LayerPropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_LayerProperties))
|
|
.SetDisplayName(LOCTEXT("LayerPropertiesTab", "Layer Parameters"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers"));
|
|
|
|
MaterialStatsManager->RegisterTabs();
|
|
|
|
OnRegisterTabSpawners().Broadcast(InTabManager);
|
|
}
|
|
|
|
void FMaterialEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
DocumentManager->SetTabManager(InTabManager);
|
|
FWorkflowCentricApplication::RegisterTabSpawners(InTabManager);
|
|
}
|
|
|
|
void FMaterialEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PreviewTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PropertiesTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PaletteTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::FindTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PreviewSettingsTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::ParameterDefaultsTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::CustomPrimitiveTabId);
|
|
InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::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();
|
|
|
|
// Ensure there are no null entries
|
|
check(Material->GetExpressions().Find(nullptr) == INDEX_NONE);
|
|
|
|
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;
|
|
bPreviewStaticSwitches = false;
|
|
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::NumericParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnNumericParameterDefaultChanged);
|
|
|
|
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();
|
|
RegisterToolBar();
|
|
|
|
TSharedPtr<FMaterialEditor> ThisPtr(SharedThis(this));
|
|
DocumentManager->Initialize(ThisPtr);
|
|
|
|
// Register the document factories
|
|
{
|
|
TSharedRef<FDocumentTabFactory> GraphEditorFactory = MakeShareable(new FMaterialGraphEditorSummoner(ThisPtr,
|
|
FMaterialGraphEditorSummoner::FOnCreateGraphEditorWidget::CreateSP(this, &FMaterialEditor::CreateGraphEditorWidget)
|
|
));
|
|
|
|
// Also store off a reference to the grapheditor factory so we can find all the tabs spawned by it later.
|
|
GraphEditorTabFactoryPtr = GraphEditorFactory;
|
|
DocumentManager->RegisterDocumentFactory(GraphEditorFactory);
|
|
}
|
|
|
|
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
|
|
const TSharedRef<FTabManager::FLayout> DummyLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea());
|
|
TArray< UObject* > ObjectsToEdit;
|
|
ObjectsToEdit.Add(ObjectToEdit);
|
|
ObjectsToEdit.Add(Material);
|
|
ObjectsToEdit.Add(MaterialEditorInstance);
|
|
InitAssetEditor( Mode, InitToolkitHost, MaterialEditorAppIdentifier, DummyLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, false );
|
|
AddMenuExtender(GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "MaterialEditor" );
|
|
AddMenuExtender(MaterialEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
ExtendToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
{
|
|
AddApplicationMode(
|
|
FMaterialEditorApplicationModes::StandardMaterialEditorMode,
|
|
MakeShareable(new FMaterialEditorApplicationMode(SharedThis(this))));
|
|
SetCurrentMode(FMaterialEditorApplicationModes::StandardMaterialEditorMode);
|
|
}
|
|
|
|
// @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);
|
|
|
|
MaterialFunction->MaterialGraph = Material->MaterialGraph;
|
|
MaterialFunction->EditorMaterial = Material;
|
|
|
|
Material->AssignExpressionCollection(MaterialFunction->GetExpressionCollection());
|
|
Material->bEnableExecWire = MaterialFunction->IsUsingControlFlow();
|
|
Material->bEnableNewHLSLGenerator = MaterialFunction->IsUsingNewHLSLGenerator();
|
|
|
|
// Ensure there are no null entries
|
|
check(Material->GetExpressions().Find(nullptr) == INDEX_NONE);
|
|
|
|
if (Material->GetExpressions().Num() == 0)
|
|
{
|
|
// If this is an empty function, create an output by default and start previewing it
|
|
if (FocusedGraphEdPtr.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->GetExpressions().Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
UMaterialExpression* Expression = Material->GetExpressions()[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);
|
|
}
|
|
}
|
|
|
|
//Material->CreateExecutionFlowExpressions();
|
|
}
|
|
|
|
// 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 (Generator.IsValid() && MaterialEditorInstance)
|
|
{
|
|
UpdateGenerator();
|
|
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdateGenerator()
|
|
{
|
|
if (MaterialEditorInstance && Generator.IsValid())
|
|
{
|
|
MaterialEditorInstance->RegenerateArrays();
|
|
TArray<UObject*> Objects;
|
|
Objects.Add(MaterialEditorInstance);
|
|
Generator->SetObjects(Objects);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::NavigateTab(FDocumentTracker::EOpenDocumentCause InCause)
|
|
{
|
|
OpenDocument(nullptr, InCause);
|
|
}
|
|
|
|
FMaterialEditor::FMaterialEditor()
|
|
: bMaterialDirty(false)
|
|
, bStatsFromPreviewMaterial(false)
|
|
, Material(nullptr)
|
|
, OriginalMaterial(nullptr)
|
|
, ExpressionPreviewMaterial(nullptr)
|
|
, EmptyMaterial(nullptr)
|
|
, PreviewExpression(nullptr)
|
|
, MaterialFunction(nullptr)
|
|
, OriginalMaterialObject(nullptr)
|
|
, EditorOptions(nullptr)
|
|
, ScopedTransaction(nullptr)
|
|
, bAlwaysRefreshAllPreviews(false)
|
|
, bHideUnusedConnectors(false)
|
|
, bLivePreview(true)
|
|
, bIsRealtime(false)
|
|
, bShowBuiltinStats(false)
|
|
, bHideUnrelatedNodes(false)
|
|
, bLockNodeFadeState(false)
|
|
, bFocusWholeChain(false)
|
|
, bSelectRegularNode(false)
|
|
, MenuExtensibilityManager(new FExtensibilityManager)
|
|
, ToolBarExtensibilityManager(new FExtensibilityManager)
|
|
, MaterialEditorInstance(nullptr)
|
|
{
|
|
DocumentManager = MakeShareable(new FDocumentTracker);
|
|
}
|
|
|
|
FMaterialEditor::~FMaterialEditor()
|
|
{
|
|
// Broadcast that this editor is going down to all listeners
|
|
OnMaterialEditorClosed().Broadcast();
|
|
|
|
for (const auto& It : OverriddenNumericParametersToRevert)
|
|
{
|
|
SetNumericParameterDefaultOnDependentMaterials(It.Key, It.Value, UE::Shader::FValue(), false);
|
|
}
|
|
|
|
// Unregister this delegate
|
|
FEditorSupportDelegates::MaterialUsageFlagsChanged.RemoveAll(this);
|
|
FEditorSupportDelegates::NumericParameterDefaultChanged.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);
|
|
FMaterial::DeferredDeleteArray(ExpressionPreviews);
|
|
}
|
|
|
|
check( !ScopedTransaction );
|
|
|
|
GEditor->UnregisterForUndo( this );
|
|
|
|
MaterialEditorInstance = NULL;
|
|
}
|
|
|
|
void FMaterialEditor::GetAllMaterialExpressionGroups(TArray<FString>* OutGroups)
|
|
{
|
|
UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
|
|
if (!EditorOnlyData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FParameterGroupData> UpdatedGroups;
|
|
for (const UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
FMaterialParameterMetadata ParameterMeta;
|
|
if (MaterialExpression->GetParameterValue(ParameterMeta))
|
|
{
|
|
const FString GroupName = ParameterMeta.Group.ToString();
|
|
OutGroups->AddUnique(GroupName);
|
|
if (Material->AttemptInsertNewGroupName(GroupName))
|
|
{
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, 0));
|
|
}
|
|
else
|
|
{
|
|
FParameterGroupData* ParameterGroupDataElement = EditorOnlyData->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement)
|
|
{
|
|
return GroupName == DataElement.GroupName;
|
|
});
|
|
UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority));
|
|
}
|
|
}
|
|
}
|
|
EditorOnlyData->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);
|
|
}
|
|
}
|
|
|
|
static void AssignPinSourceIndices(UEdGraphNode* Node)
|
|
{
|
|
int32 NumInputDataPins = 0;
|
|
int32 NumOutputDataPins = 0;
|
|
int32 NumInputExecPins = 0;
|
|
int32 NumOutputExecPins = 0;
|
|
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
int32 SourceIndex = INDEX_NONE;
|
|
if (Pin->PinType.PinCategory == UMaterialGraphSchema::PC_Exec)
|
|
{
|
|
switch (Pin->Direction)
|
|
{
|
|
case EGPD_Input: SourceIndex = NumInputExecPins++; break;
|
|
case EGPD_Output: SourceIndex = NumOutputExecPins++; break;
|
|
default: checkNoEntry(); break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (Pin->Direction)
|
|
{
|
|
case EGPD_Input: SourceIndex = NumInputDataPins++; break;
|
|
case EGPD_Output: SourceIndex = NumOutputDataPins++; break;
|
|
default: checkNoEntry(); break;
|
|
}
|
|
}
|
|
|
|
if (Pin->SourceIndex == INDEX_NONE)
|
|
{
|
|
Pin->SourceIndex = SourceIndex;
|
|
}
|
|
ensure(Pin->SourceIndex == SourceIndex);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::CollapseNodesIntoGraph(UEdGraphNode* InGatewayNode, UMaterialGraphNode* InEntryNode, UMaterialGraphNode* InResultNode, UEdGraph* InSourceGraph, UEdGraph* InDestinationGraph, TSet<UEdGraphNode*>& InCollapsableNodes)
|
|
{
|
|
const UMaterialGraphSchema* MaterialSchema = GetDefault<UMaterialGraphSchema>(); // ?
|
|
|
|
// Keep track of the statistics of the node positions so the new nodes can be located reasonably well
|
|
float SumNodeX = 0.0f;
|
|
float SumNodeY = 0.0f;
|
|
float MinNodeX = 1e9f;
|
|
float MinNodeY = 1e9f;
|
|
float MaxNodeX = -1e9f;
|
|
float MaxNodeY = -1e9f;
|
|
|
|
// Move the nodes over, which may create cross-graph references that we need fix up ASAP
|
|
for (TSet<UEdGraphNode*>::TConstIterator NodeIt(InCollapsableNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = *NodeIt;
|
|
Node->Modify();
|
|
|
|
// Update stats
|
|
SumNodeX += Node->NodePosX;
|
|
SumNodeY += Node->NodePosY;
|
|
MinNodeX = FMath::Min<float>(MinNodeX, Node->NodePosX);
|
|
MinNodeY = FMath::Min<float>(MinNodeY, Node->NodePosY);
|
|
MaxNodeX = FMath::Max<float>(MaxNodeX, Node->NodePosX);
|
|
MaxNodeY = FMath::Max<float>(MaxNodeY, Node->NodePosY);
|
|
|
|
// Move the node over
|
|
InSourceGraph->Nodes.Remove(Node);
|
|
InDestinationGraph->Nodes.Add(Node);
|
|
Node->Rename(/*NewName=*/ NULL, /*NewOuter=*/ InDestinationGraph);
|
|
|
|
// Move the sub-graph to the new graph
|
|
if(UMaterialGraphNode_Composite* Composite = Cast<UMaterialGraphNode_Composite>(Node))
|
|
{
|
|
InSourceGraph->SubGraphs.Remove(Composite->BoundGraph);
|
|
InDestinationGraph->SubGraphs.Add(Composite->BoundGraph);
|
|
Composite->BoundGraph->SubgraphExpression = CastChecked<UMaterialGraphNode_Composite>(InGatewayNode)->MaterialExpression;
|
|
}
|
|
|
|
// Mark the node's expression as owned by the gateway node's expression
|
|
UMaterialGraphNode* GatewayMaterialNode = CastChecked<UMaterialGraphNode>(InGatewayNode);
|
|
if (UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Node))
|
|
{
|
|
MaterialNode->MaterialExpression->SubgraphExpression = GatewayMaterialNode->MaterialExpression;
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(Node))
|
|
{
|
|
CommentNode->MaterialExpressionComment->SubgraphExpression = GatewayMaterialNode->MaterialExpression;
|
|
}
|
|
|
|
TArray<UEdGraphPin*> OutputGatewayExecPins;
|
|
|
|
// Find cross-graph links
|
|
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
|
|
{
|
|
UEdGraphPin* LocalPin = Node->Pins[PinIndex];
|
|
|
|
bool bIsGatewayPin = false;
|
|
if(LocalPin->LinkedTo.Num())
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UEdGraphPin* TrialPin = LocalPin->LinkedTo[LinkIndex];
|
|
if (!InCollapsableNodes.Contains(TrialPin->GetOwningNode()))
|
|
{
|
|
bIsGatewayPin = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Thunk cross-graph links thru the gateway
|
|
if (bIsGatewayPin)
|
|
{
|
|
// Local port is either the entry or the result node in the collapsed graph
|
|
// Remote port is the node placed in the source graph
|
|
UMaterialGraphNode* LocalPort = (LocalPin->Direction == EGPD_Input) ? InEntryNode : InResultNode;
|
|
|
|
// Add a new pin to the entry/exit node and to the composite node
|
|
UEdGraphPin* LocalPortPin = NULL;
|
|
UEdGraphPin* RemotePortPin = NULL;
|
|
|
|
if(LocalPin->LinkedTo[0]->GetOwningNode() != InEntryNode)
|
|
{
|
|
const FName UniquePortName = InGatewayNode->CreateUniquePinName(LocalPin->PinName);
|
|
|
|
if(!RemotePortPin && !LocalPortPin)
|
|
{
|
|
FEdGraphPinType PinType = LocalPin->PinType;
|
|
RemotePortPin = InGatewayNode->CreatePin(LocalPin->Direction, PinType, UniquePortName);
|
|
LocalPortPin = LocalPort->CreatePin((LocalPin->Direction == EGPD_Input) ? EGPD_Output : EGPD_Input, PinType, UniquePortName);
|
|
|
|
// Create reroute expressions / pins for the pinbase.
|
|
UMaterialGraphNode_Composite* CompositeNode = CastChecked<UMaterialGraphNode_Composite>(InGatewayNode);
|
|
UMaterialExpression* GatewayRerouteExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionReroute::StaticClass());
|
|
UMaterialExpressionReroute* GatewayReroutePin = CastChecked<UMaterialExpressionReroute>(GatewayRerouteExpression);
|
|
UMaterialExpressionPinBase* PinBase = CastChecked<UMaterialExpressionPinBase>(LocalPort->MaterialExpression);
|
|
|
|
GatewayRerouteExpression->SubgraphExpression = CompositeNode->MaterialExpression;
|
|
PinBase->ReroutePins.Add(FCompositeReroute{UniquePortName, decltype(FCompositeReroute::Expression)(GatewayReroutePin)});
|
|
PinBase->Modify();
|
|
}
|
|
}
|
|
|
|
check(LocalPortPin);
|
|
check(RemotePortPin);
|
|
|
|
LocalPin->Modify();
|
|
|
|
// Route the links
|
|
for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UEdGraphPin* RemotePin = LocalPin->LinkedTo[LinkIndex];
|
|
RemotePin->Modify();
|
|
|
|
if (!InCollapsableNodes.Contains(RemotePin->GetOwningNode()) && RemotePin->GetOwningNode() != InEntryNode && RemotePin->GetOwningNode() != InResultNode)
|
|
{
|
|
// Fix up the remote pin
|
|
RemotePin->LinkedTo.Remove(LocalPin);
|
|
RemotePin->MakeLinkTo(RemotePortPin);
|
|
|
|
// The Entry Node only supports a single link, so if we made links above
|
|
// we need to break them now, to make room for the new link.
|
|
if (LocalPort == InEntryNode)
|
|
{
|
|
LocalPortPin->BreakAllPinLinks();
|
|
}
|
|
|
|
// Fix up the local pin
|
|
LocalPin->LinkedTo.Remove(RemotePin);
|
|
--LinkIndex;
|
|
LocalPin->MakeLinkTo(LocalPortPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reposition the newly created nodes
|
|
const int32 NumNodes = InCollapsableNodes.Num();
|
|
const float CenterX = NumNodes == 0 ? SumNodeX : SumNodeX / NumNodes;
|
|
const float CenterY = NumNodes == 0 ? SumNodeY : SumNodeY / NumNodes;
|
|
const float MinusOffsetX = 160.0f; //@TODO: Random magic numbers
|
|
const float PlusOffsetX = 300.0f;
|
|
|
|
// Put the gateway node at the center of the empty space in the old graph
|
|
InGatewayNode->NodePosX = CenterX;
|
|
InGatewayNode->NodePosY = CenterY;
|
|
InGatewayNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
if (UMaterialGraphNode_Composite* CompositeNode = Cast<UMaterialGraphNode_Composite>(InGatewayNode))
|
|
{
|
|
CompositeNode->MaterialExpression->MaterialExpressionEditorX = InGatewayNode->NodePosX;
|
|
CompositeNode->MaterialExpression->MaterialExpressionEditorY = InGatewayNode->NodePosY;
|
|
}
|
|
|
|
// Put the entry and exit nodes on either side of the nodes in the new graph
|
|
if (NumNodes != 0)
|
|
{
|
|
InEntryNode->NodePosX = MinNodeX - MinusOffsetX;
|
|
InEntryNode->NodePosY = CenterY;
|
|
InEntryNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
InEntryNode->MaterialExpression->MaterialExpressionEditorX = InEntryNode->NodePosX;
|
|
InEntryNode->MaterialExpression->MaterialExpressionEditorY = InEntryNode->NodePosY;
|
|
|
|
InResultNode->NodePosX = MaxNodeX + PlusOffsetX;
|
|
InResultNode->NodePosY = CenterY;
|
|
InResultNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
InResultNode->MaterialExpression->MaterialExpressionEditorX = InResultNode->NodePosX;
|
|
InResultNode->MaterialExpression->MaterialExpressionEditorY = InResultNode->NodePosY;
|
|
}
|
|
|
|
AssignPinSourceIndices(InGatewayNode);
|
|
AssignPinSourceIndices(InEntryNode);
|
|
AssignPinSourceIndices(InResultNode);
|
|
}
|
|
|
|
void FMaterialEditor::CollapseNodes(TSet<UEdGraphNode*>& InCollapsableNodes)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraph* SourceGraph = FocusedGraphEd->GetCurrentGraph();
|
|
SourceGraph->Modify();
|
|
|
|
// Create the composite node that will serve as the gateway into the subgraph
|
|
UMaterialGraphNode_Composite* GatewayNode = NULL;
|
|
{
|
|
GatewayNode = Cast<UMaterialGraphNode_Composite>(FMaterialGraphSchemaAction_NewComposite::SpawnNode(SourceGraph, FVector2D(0, 0)));
|
|
GatewayNode->bCanRenameNode = true;
|
|
check(GatewayNode);
|
|
}
|
|
|
|
UEdGraph* DestinationGraph = GatewayNode->BoundGraph;
|
|
UMaterialExpressionComposite* CompositeExpression = CastChecked<UMaterialExpressionComposite>(GatewayNode->MaterialExpression);
|
|
|
|
CollapseNodesIntoGraph(GatewayNode,
|
|
Cast<UMaterialGraphNode>(CompositeExpression->InputExpressions->GraphNode),
|
|
Cast<UMaterialGraphNode>(CompositeExpression->OutputExpressions->GraphNode),
|
|
SourceGraph,
|
|
DestinationGraph,
|
|
InCollapsableNodes);
|
|
AssignPinSourceIndices(GatewayNode);
|
|
|
|
UpdateMaterialAfterGraphChange();
|
|
|
|
// Now that the expressions are updated, reconstruct the nodes
|
|
// Need to do this to prevent user from copying a freshly collapsed node not built from its expression
|
|
GatewayNode->ReconstructNode();
|
|
Cast<UMaterialGraphNode>(CompositeExpression->InputExpressions->GraphNode)->ReconstructNode();
|
|
Cast<UMaterialGraphNode>(CompositeExpression->OutputExpressions->GraphNode)->ReconstructNode();
|
|
}
|
|
|
|
void FMaterialEditor::ExpandNode(UEdGraphNode* InNodeToExpand, UEdGraph* InSourceGraph, TSet<UEdGraphNode*>& OutExpandedNodes)
|
|
{
|
|
UEdGraph* DestinationGraph = InNodeToExpand->GetGraph();
|
|
UEdGraph* SourceGraph = InSourceGraph;
|
|
check(SourceGraph);
|
|
|
|
// Mark all edited objects so they will appear in the transaction record if needed.
|
|
DestinationGraph->Modify();
|
|
SourceGraph->Modify();
|
|
InNodeToExpand->Modify();
|
|
|
|
UEdGraphNode* Entry = nullptr;
|
|
UEdGraphNode* Result = nullptr;
|
|
|
|
const bool bIsCollapsedGraph = InNodeToExpand->IsA<UMaterialGraphNode_Composite>();
|
|
|
|
MoveNodesToGraph(SourceGraph->Nodes, DestinationGraph, OutExpandedNodes, &Entry, &Result, bIsCollapsedGraph);
|
|
CollapseGatewayNode(InNodeToExpand, Entry, Result, &OutExpandedNodes);
|
|
|
|
bool bPreviewExpressionDeleted = false;
|
|
|
|
if (Entry)
|
|
{
|
|
UMaterialExpressionPinBase* PinBase = Cast<UMaterialExpressionPinBase>(Cast<UMaterialGraphNode>(Entry)->MaterialExpression);
|
|
PinBase->DeleteReroutePins();
|
|
Material->GetExpressionCollection().RemoveExpression(PinBase);
|
|
PinBase->MarkAsGarbage();
|
|
Entry->DestroyNode();
|
|
bPreviewExpressionDeleted |= PinBase == PreviewExpression;
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
UMaterialExpressionPinBase* PinBase = Cast<UMaterialExpressionPinBase>(Cast<UMaterialGraphNode>(Result)->MaterialExpression);
|
|
PinBase->DeleteReroutePins();
|
|
Material->GetExpressionCollection().RemoveExpression(PinBase);
|
|
PinBase->MarkAsGarbage();
|
|
Result->DestroyNode();
|
|
bPreviewExpressionDeleted |= PinBase == PreviewExpression;
|
|
}
|
|
|
|
// Make sure any subgraphs get propagated appropriately
|
|
if (SourceGraph->SubGraphs.Num() > 0)
|
|
{
|
|
DestinationGraph->SubGraphs.Append(SourceGraph->SubGraphs);
|
|
SourceGraph->SubGraphs.Empty();
|
|
}
|
|
|
|
// Remove the gateway node and source graph
|
|
UMaterialExpression* CompositeExpression = Cast<UMaterialGraphNode>(InNodeToExpand)->MaterialExpression;
|
|
CompositeExpression->Modify();
|
|
Material->GetExpressionCollection().RemoveExpression(CompositeExpression);
|
|
CompositeExpression->MarkAsGarbage();
|
|
InNodeToExpand->DestroyNode();
|
|
bPreviewExpressionDeleted |= CompositeExpression == PreviewExpression;
|
|
|
|
if (bPreviewExpressionDeleted)
|
|
{
|
|
// The preview expression was deleted. Null out our reference to it and reset to the normal preview material
|
|
SetPreviewExpression(nullptr);
|
|
RegenerateCodeView();
|
|
UpdatePreviewMaterial();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::MoveNodesToAveragePos(TSet<UEdGraphNode*>& AverageNodes, FVector2D SourcePos, bool bExpandedNodesNeedUniqueGuid) const
|
|
{
|
|
if (AverageNodes.Num() > 0)
|
|
{
|
|
FVector2D AvgNodePosition(0.0f, 0.0f);
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(AverageNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
AvgNodePosition.X += Node->NodePosX;
|
|
AvgNodePosition.Y += Node->NodePosY;
|
|
}
|
|
|
|
float InvNumNodes = 1.0f / float(AverageNodes.Num());
|
|
AvgNodePosition.X *= InvNumNodes;
|
|
AvgNodePosition.Y *= InvNumNodes;
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
for (UEdGraphNode* ExpandedNode : AverageNodes)
|
|
{
|
|
ExpandedNode->NodePosX = (ExpandedNode->NodePosX - AvgNodePosition.X) + SourcePos.X;
|
|
ExpandedNode->NodePosY = (ExpandedNode->NodePosY - AvgNodePosition.Y) + SourcePos.Y;
|
|
|
|
ExpandedNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
if (bExpandedNodesNeedUniqueGuid)
|
|
{
|
|
ExpandedNode->CreateNewGuid();
|
|
}
|
|
|
|
//Add expanded node to selection
|
|
FocusedGraphEd->SetNodeSelection(ExpandedNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::MoveNodesToGraph(TArray<UEdGraphNode*>& SourceNodes, UEdGraph* DestinationGraph, TSet<UEdGraphNode*>& OutExpandedNodes, UEdGraphNode** OutEntry, UEdGraphNode** OutResult, const bool bIsCollapsedGraph)
|
|
{
|
|
// Move the nodes over, remembering any that are boundary nodes
|
|
while (SourceNodes.Num())
|
|
{
|
|
UEdGraphNode* Node = SourceNodes.Pop();
|
|
UEdGraph* OriginalGraph = Node->GetGraph();
|
|
|
|
Node->Modify();
|
|
OriginalGraph->Modify();
|
|
Node->Rename(/*NewName=*/ nullptr, /*NewOuter=*/ DestinationGraph, REN_DontCreateRedirectors);
|
|
|
|
// Remove the node from the original graph
|
|
OriginalGraph->RemoveNode(Node, false);
|
|
|
|
// We do not check CanPasteHere when determining CanCollapseNodes, unlike CanCollapseSelectionToFunction/Macro,
|
|
// so when expanding a collapsed graph we don't want to check the CanPasteHere function:
|
|
if (!bIsCollapsedGraph && !Node->CanPasteHere(DestinationGraph))
|
|
{
|
|
Node->BreakAllNodeLinks();
|
|
continue;
|
|
}
|
|
|
|
// Successfully added the node to the graph, we may need to remove flags
|
|
if (Node->HasAllFlags(RF_Transient) && !DestinationGraph->HasAllFlags(RF_Transient))
|
|
{
|
|
Node->SetFlags(RF_Transactional);
|
|
Node->ClearFlags(RF_Transient);
|
|
TArray<UObject*> Subobjects;
|
|
GetObjectsWithOuter(Node, Subobjects);
|
|
for (UObject* Subobject : Subobjects)
|
|
{
|
|
Subobject->ClearFlags(RF_Transient);
|
|
Subobject->SetFlags(RF_Transactional);
|
|
}
|
|
}
|
|
|
|
DestinationGraph->AddNode(Node, /* bFromUI */ false, /* bSelectNewNode */ false);
|
|
|
|
if (UMaterialGraphNode_Composite* Composite = Cast<UMaterialGraphNode_Composite>(Node))
|
|
{
|
|
OriginalGraph->SubGraphs.Remove(Composite->BoundGraph);
|
|
DestinationGraph->SubGraphs.Add(Composite->BoundGraph);
|
|
}
|
|
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Node);
|
|
if (MaterialNode && MaterialNode->MaterialExpression->IsA(UMaterialExpressionPinBase::StaticClass()))
|
|
{
|
|
UMaterialExpressionPinBase* PinBase = Cast<UMaterialExpressionPinBase>(MaterialNode->MaterialExpression);
|
|
|
|
if (PinBase && PinBase->PinDirection == EGPD_Output)
|
|
{
|
|
*OutEntry = Node;
|
|
}
|
|
else if (PinBase && PinBase->PinDirection == EGPD_Input)
|
|
{
|
|
*OutResult = Node;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutExpandedNodes.Add(Node);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CollapseGatewayNode(UEdGraphNode* InNode, UEdGraphNode* InEntryNode, UEdGraphNode* InResultNode, TSet<UEdGraphNode*>* OutExpandedNodes)
|
|
{
|
|
bool bSuccessful = true;
|
|
|
|
// We iterate the array in reverse so we can both remove the subpins safely after we've read them and
|
|
// so we have split nested structs we combine them back together in the right order
|
|
for (int32 BoundaryPinIndex = InNode->Pins.Num() - 1; BoundaryPinIndex >= 0; --BoundaryPinIndex)
|
|
{
|
|
UEdGraphPin* const BoundaryPin = InNode->Pins[BoundaryPinIndex];
|
|
|
|
// For each pin in the gateway node, find the associated pin in the entry or result node.
|
|
UEdGraphNode* const GatewayNode = (BoundaryPin->Direction == EGPD_Input) ? InEntryNode : InResultNode;
|
|
UEdGraphPin* GatewayPin = nullptr;
|
|
if (GatewayNode)
|
|
{
|
|
for (int32 PinIdx = GatewayNode->Pins.Num() - 1; PinIdx >= 0; --PinIdx)
|
|
{
|
|
UEdGraphPin* const Pin = GatewayNode->Pins[PinIdx];
|
|
|
|
// Function graphs have a single exec path through them, so only one exec pin for input and another for output. In this fashion, they must not be handled by name.
|
|
if ((Pin->PinName == BoundaryPin->PinName) && (Pin->Direction != BoundaryPin->Direction))
|
|
{
|
|
GatewayPin = Pin;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GatewayPin)
|
|
{
|
|
//@TODO: This is same as UEdGraphSchema_K2::CombineTwoPinNetsAndRemoveOldPins, except we don't care about default values
|
|
// since material graphs pins can't do that. More consolidation
|
|
auto CombineTwoPinNetsAndRemoveOldPins = [](UEdGraphPin* InPinA, UEdGraphPin* InPinB)
|
|
{
|
|
// Make direct connections between the things that connect to A or B, removing A and B from the picture
|
|
for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA)
|
|
{
|
|
UEdGraphPin* FarA = InPinA->LinkedTo[IndexA];
|
|
// TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed.
|
|
// (Probably should use a check() instead once it's removed though. See additional cases above.
|
|
if (FarA != nullptr)
|
|
{
|
|
for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB)
|
|
{
|
|
UEdGraphPin* FarB = InPinB->LinkedTo[IndexB];
|
|
|
|
if (FarB != nullptr)
|
|
{
|
|
FarA->Modify();
|
|
FarB->Modify();
|
|
FarA->MakeLinkTo(FarB);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
};
|
|
CombineTwoPinNetsAndRemoveOldPins(BoundaryPin, GatewayPin);
|
|
}
|
|
}
|
|
|
|
return bSuccessful;
|
|
}
|
|
|
|
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) const
|
|
{
|
|
return NodeFeatureLevel == TestFeatureLevel;
|
|
}
|
|
|
|
bool FMaterialEditor::IsFeaturePreviewAvailable(ERHIFeatureLevel::Type TestFeatureLevel) const
|
|
{
|
|
return GMaxRHIFeatureLevel >= 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" );
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
DetailsViewArgs.NotifyHook = 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
|
|
);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionRuntimeVirtualTextureSampleParameter::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
|
|
);
|
|
|
|
FOnGetDetailCustomizationInstance LayoutCompositeDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCompositeDetails::MakeInstance);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionComposite::StaticClass(),
|
|
LayoutCompositeDetails
|
|
);
|
|
|
|
MaterialDetailsView->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingProperties);
|
|
|
|
PropertyEditorModule.RegisterCustomClassLayout( UMaterial::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialDetailCustomization::MakeInstance ) );
|
|
PropertyEditorModule.RegisterCustomClassLayout( UMaterialFunction::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialFunctionDetailCustomization::MakeInstance) );
|
|
|
|
MaterialEditorInstance = NewObject<UMaterialEditorPreviewParameters>(GetTransientPackage(), NAME_None, RF_Transactional);
|
|
MaterialEditorInstance->PreviewMaterial = Material;
|
|
MaterialEditorInstance->OriginalMaterial = OriginalMaterial;
|
|
if (MaterialFunction)
|
|
{
|
|
MaterialEditorInstance->OriginalFunction = MaterialFunction->ParentFunction;
|
|
}
|
|
FPropertyEditorModule& Module = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
if (!Generator.IsValid())
|
|
{
|
|
FPropertyRowGeneratorArgs Args;
|
|
Generator = Module.CreatePropertyRowGenerator(Args);
|
|
}
|
|
|
|
MaterialParametersOverviewWidget = SNew(SMaterialParametersOverviewPanel)
|
|
.InMaterialEditorInstance(MaterialEditorInstance)
|
|
.InGenerator(Generator);
|
|
Generator->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingParametersFromOverview);
|
|
Generator->OnRowsRefreshed().AddSP(this, &FMaterialEditor::GeneratorRowsRefreshed);
|
|
MaterialCustomPrimitiveDataWidget = SNew(SMaterialCustomPrimitiveDataPanel, MaterialEditorInstance);
|
|
|
|
MaterialLayersFunctionsInstance = SNew(SMaterialLayersFunctionsMaterialWrapper)
|
|
.InMaterialEditorInstance(MaterialEditorInstance)
|
|
.InGenerator(Generator);
|
|
|
|
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)
|
|
{
|
|
// If this is a comment only change, skip updating the preview material and viewport
|
|
{
|
|
bool bIsCommentOnlyChange = true;
|
|
for (int32 Index = 0; Index < PropertyChangedEvent.GetNumObjectsBeingEdited(); ++Index)
|
|
{
|
|
const UObject* EditedObject = PropertyChangedEvent.GetObjectBeingEdited(Index);
|
|
if (!EditedObject->IsA<UMaterialExpression>())
|
|
{
|
|
bIsCommentOnlyChange = false;
|
|
break;
|
|
}
|
|
|
|
if (EditedObject->IsA<UMaterialExpressionComment>())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (PropertyChangedEvent.GetPropertyName() != TEXT("Desc"))
|
|
{
|
|
bIsCommentOnlyChange = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bIsCommentOnlyChange)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool bRefreshNodePreviews = false;
|
|
if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
FStructProperty* Property = CastField<FStructProperty>(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();
|
|
MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnFinishedChangingParametersFromOverview(const FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
bool bRefreshNodePreviews = false;
|
|
if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
RefreshExpressionPreviews(true);
|
|
RefreshPreviewViewport();
|
|
TArray<TWeakObjectPtr<UObject>> SelectedObjects = MaterialDetailsView->GetSelectedObjects();
|
|
|
|
// Don't set directly on color structs since the color picker will do that for us
|
|
FStructProperty* StructProperty = CastField<FStructProperty>(PropertyChangedEvent.Property);
|
|
if (!StructProperty || StructProperty->Struct->GetFName() != "LinearColor")
|
|
{
|
|
MaterialDetailsView->SetObjects(SelectedObjects, true);
|
|
}
|
|
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnChangeBreadCrumbGraph(UEdGraph* InGraph)
|
|
{
|
|
if (InGraph && FocusedGraphEdPtr.IsValid())
|
|
{
|
|
OpenDocument(InGraph, FDocumentTracker::NavigatingCurrentDocument);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::GeneratorRowsRefreshed()
|
|
{
|
|
MaterialParametersOverviewWidget->Refresh();
|
|
if (MaterialLayersFunctionsInstance)
|
|
{
|
|
MaterialLayersFunctionsInstance->Refresh();
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
AddToolbarExtender(GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "MaterialEditor" );
|
|
AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
void FMaterialEditor::InitToolMenuContext(FToolMenuContext& MenuContext)
|
|
{
|
|
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
|
|
|
|
UMaterialEditorMenuContext* Context = NewObject<UMaterialEditorMenuContext>();
|
|
Context->MaterialEditor = SharedThis(this);
|
|
MenuContext.AddObject(Context);
|
|
}
|
|
|
|
void FMaterialEditor::RegisterToolBar()
|
|
{
|
|
const FName MenuName = FAssetEditorToolkit::GetToolMenuToolbarName();
|
|
if (!UToolMenus::Get()->IsMenuRegistered(MenuName))
|
|
{
|
|
UToolMenu* ToolBar = UToolMenus::Get()->RegisterMenu(MenuName, "AssetEditor.DefaultToolBar", EMultiBoxType::ToolBar);
|
|
|
|
FToolMenuInsert InsertAfterAssetSection("Asset", EToolMenuInsertType::After);
|
|
FToolMenuSection& MaterialSection = ToolBar->AddSection("MaterialTools", TAttribute<FText>(), InsertAfterAssetSection);
|
|
MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().Apply));
|
|
MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().FindInMaterial));
|
|
MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().CameraHome));
|
|
UMaterialEditorMenuContext* Context = ToolBar->FindContext<UMaterialEditorMenuContext>();
|
|
if (!MaterialFunction)
|
|
{
|
|
MaterialSection.AddEntry(FToolMenuEntry::InitComboButton(
|
|
"Hierarchy",
|
|
FToolUIActionChoice(),
|
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu)
|
|
{
|
|
UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext<UMaterialEditorMenuContext>();
|
|
if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid())
|
|
{
|
|
SubMenuContext->MaterialEditor.Pin()->GenerateInheritanceMenu(InSubMenu);
|
|
}
|
|
}),
|
|
LOCTEXT("Hierarchy", "Hierarchy"),
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MaterialEditor.Hierarchy"),
|
|
false
|
|
));
|
|
}
|
|
FToolMenuEntry LiveUpdateMenu = FToolMenuEntry::InitComboButton(
|
|
"LiveUpdate",
|
|
FUIAction(),
|
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu)
|
|
{
|
|
FToolMenuSection& Section = InSubMenu->FindOrAddSection("LiveUpdateOptions");
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ToggleLivePreview));
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ToggleRealtimeExpressions));
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews));
|
|
}),
|
|
LOCTEXT("LiveUpdate_Label", "Live Update"),
|
|
LOCTEXT("LiveUpdate_Tooltip", "Set the elements of the Material Editor UI to update in realtime"),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MaterialEditor.LiveUpdate")
|
|
);
|
|
LiveUpdateMenu.StyleNameOverride = "CalloutToolbar";
|
|
MaterialSection.AddEntry(LiveUpdateMenu);
|
|
|
|
|
|
FToolMenuSection& GraphSection = ToolBar->AddSection("Graph", TAttribute<FText>(), InsertAfterAssetSection);
|
|
|
|
FToolMenuEntry CleaningMenu = FToolMenuEntry::InitComboButton(
|
|
"CleanGraph",
|
|
FUIAction(),
|
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu)
|
|
{
|
|
FToolMenuSection& Section = InSubMenu->FindOrAddSection("General");
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().CleanUnusedExpressions));
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ShowHideConnectors));
|
|
}),
|
|
LOCTEXT("GraphCleanup_Label", "Clean Graph"),
|
|
LOCTEXT("GraphCleanup_Tooltip", "Tools to help clean up graph nodes and connections"),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GraphEditor.Clean")
|
|
);
|
|
CleaningMenu.StyleNameOverride = "CalloutToolbar";
|
|
GraphSection.AddEntry(CleaningMenu);
|
|
GraphSection.AddEntry(FToolMenuEntry::InitComboButton(
|
|
"NodePreview",
|
|
FUIAction(),
|
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu)
|
|
{
|
|
UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext<UMaterialEditorMenuContext>();
|
|
if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid())
|
|
{
|
|
TSharedPtr<FMaterialEditor> MaterialEditor = StaticCastSharedPtr<FMaterialEditor>(SubMenuContext->MaterialEditor.Pin());
|
|
MaterialEditor->GeneratePreviewMenuContent(InSubMenu);
|
|
}
|
|
}),
|
|
LOCTEXT("NodePreview_Label", "Preview State"),
|
|
LOCTEXT("NodePreviewToolTip", "Preview the graph state for a given feature level, material quality, or static switch value."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "FullBlueprintEditor.SwitchToScriptingMode"),
|
|
false
|
|
));
|
|
GraphSection.AddEntry(FToolMenuEntry::InitToolBarButton(
|
|
FMaterialEditorCommands::Get().ToggleHideUnrelatedNodes,
|
|
TAttribute<FText>(),
|
|
TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes")
|
|
));
|
|
GraphSection.AddEntry(FToolMenuEntry::InitComboButton(
|
|
"HideUnrelatedNodesOptions",
|
|
FUIAction(),
|
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu)
|
|
{
|
|
UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext<UMaterialEditorMenuContext>();
|
|
if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid())
|
|
{
|
|
TSharedPtr<FMaterialEditor> MaterialEditor = StaticCastSharedPtr<FMaterialEditor>(SubMenuContext->MaterialEditor.Pin());
|
|
MaterialEditor->MakeHideUnrelatedNodesOptionsMenu(InSubMenu);
|
|
}
|
|
}),
|
|
LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"),
|
|
LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"),
|
|
TAttribute<FSlateIcon>(),
|
|
true
|
|
));
|
|
|
|
{
|
|
FToolMenuSection& Section = ToolBar->AddSection("Stats", TAttribute<FText>(), InsertAfterAssetSection);
|
|
Section.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats));
|
|
Section.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().TogglePlatformStats));
|
|
|
|
}
|
|
|
|
|
|
}
|
|
};
|
|
|
|
void FMaterialEditor::AddInheritanceMenuEntry(FToolMenuSection& Section, const FAssetData& AssetData, bool bIsFunctionPreviewMaterial)
|
|
{
|
|
FExecuteAction OpenAction;
|
|
if (bIsFunctionPreviewMaterial)
|
|
{
|
|
OpenAction.BindStatic(&FMaterialEditorUtilities::OnOpenFunction, AssetData);
|
|
}
|
|
else
|
|
{
|
|
OpenAction.BindStatic(&FMaterialEditorUtilities::OnOpenMaterial, AssetData);
|
|
}
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("ParentName"), FText::FromName(AssetData.AssetName));
|
|
FText Label = FText::Format(LOCTEXT("InstanceParentName", "{ParentName}"), Args);
|
|
|
|
Section.AddEntry(FToolMenuEntry::InitMenuEntry(
|
|
NAME_None,
|
|
Label,
|
|
LOCTEXT("OpenInEditor", "Open In Editor"),
|
|
FSlateIcon(),
|
|
FUIAction(OpenAction)
|
|
));
|
|
}
|
|
|
|
void FMaterialEditor::GenerateInheritanceMenu(UToolMenu* Menu)
|
|
{
|
|
RebuildInheritanceList();
|
|
Menu->bShouldCloseWindowAfterMenuSelection = true;
|
|
Menu->SetMaxHeight(500);
|
|
|
|
if (!MaterialFunction)
|
|
{
|
|
FToolMenuSection& Section = Menu->AddSection("MaterialInstances", LOCTEXT("MaterialInstances", "Material Instances"));
|
|
if (MaterialChildList.Num() == 0)
|
|
{
|
|
const FText NoChildText = LOCTEXT("NoInstancesFound", "No Instances Found");
|
|
TSharedRef<SWidget> NoChildWidget = SNew(STextBlock)
|
|
.Text(NoChildText);
|
|
Section.AddEntry(FToolMenuEntry::InitWidget("NoInstancesFound", NoChildWidget, FText::GetEmpty()));
|
|
}
|
|
for (const FAssetData& MaterialChild : MaterialChildList)
|
|
{
|
|
FMaterialEditor::AddInheritanceMenuEntry(Section, MaterialChild, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::GeneratePreviewMenuContent(UToolMenu* Menu)
|
|
{
|
|
Menu->bShouldCloseWindowAfterMenuSelection = true;
|
|
|
|
FToolMenuSection& QualityLevelSection = Menu->AddSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level"));
|
|
{
|
|
QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All);
|
|
QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Epic);
|
|
QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High);
|
|
QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium);
|
|
QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low);
|
|
}
|
|
|
|
FToolMenuSection& FeatureLevelSection = Menu->AddSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level"));
|
|
{
|
|
FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All);
|
|
FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES31);
|
|
FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5);
|
|
FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM6);
|
|
}
|
|
|
|
FToolMenuSection& StaticSwitchSection = Menu->AddSection("StaticSwitchPreview", LOCTEXT("StaticSwitchHeading", "Switch Params"));
|
|
TSharedRef<SWidget> StaticSwitchCheckbox = SNew(SBox)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(bPreviewStaticSwitches ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
|
.OnCheckStateChanged_Lambda(
|
|
[this](ECheckBoxState NewState)
|
|
{
|
|
bPreviewStaticSwitches = (NewState == ECheckBoxState::Checked);
|
|
bPreviewFeaturesChanged = true;
|
|
}
|
|
)
|
|
.Style(FAppStyle::Get(), "Menu.CheckBox")
|
|
.ToolTipText(LOCTEXT("StaticSwitchCheckBoxToolTip", "Hide disabled nodes in the graph, according to switch params."))
|
|
.Content()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DisableInactiveSwitch", "Hide Disabled"))
|
|
]
|
|
]
|
|
];
|
|
StaticSwitchSection.AddEntry(FToolMenuEntry::InitMenuEntry("StaticSwitchCheckbox", FUIAction(), StaticSwitchCheckbox));
|
|
}
|
|
|
|
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 the changes of the modified material to the original material?\n{0}\n(Selecting 'No' will cause all changes to be lost!)"),
|
|
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::AddGraphEditorPinActionsToContextMenu(FToolMenuSection& InSection) const
|
|
{
|
|
// Promote To Parameter
|
|
{
|
|
FToolUIAction PromoteToParameterAction;
|
|
PromoteToParameterAction.ExecuteAction = FToolMenuExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteToParameter);
|
|
PromoteToParameterAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &FMaterialEditor::OnCanPromoteToParameter);
|
|
|
|
TSharedPtr<FUICommandInfo> PromoteToParameterCommand = FMaterialEditorCommands::Get().PromoteToParameter;
|
|
InSection.AddMenuEntry(
|
|
PromoteToParameterCommand->GetCommandName(),
|
|
PromoteToParameterCommand->GetLabel(),
|
|
PromoteToParameterCommand->GetDescription(),
|
|
PromoteToParameterCommand->GetIcon(),
|
|
PromoteToParameterAction
|
|
);
|
|
}
|
|
|
|
{
|
|
auto AddStrataContextualMenu = [&](TSharedPtr<FUICommandInfo> CreateNodeCommand, EStrataNodeForPin StrataNodeForPin)
|
|
{
|
|
FToolUIAction CreateNodeAction;
|
|
CreateNodeAction.ExecuteAction = FToolMenuExecuteAction::CreateSP(this, &FMaterialEditor::OnCreateStrataNodeForPin, StrataNodeForPin);
|
|
CreateNodeAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &FMaterialEditor::OnCanCreateStrataNodeForPin, StrataNodeForPin);
|
|
|
|
InSection.AddMenuEntry(
|
|
CreateNodeCommand->GetCommandName(),
|
|
CreateNodeCommand->GetLabel(),
|
|
CreateNodeCommand->GetDescription(),
|
|
CreateNodeCommand->GetIcon(),
|
|
CreateNodeAction
|
|
);
|
|
};
|
|
AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateSlabNode, EStrataNodeForPin::Slab);
|
|
AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateHorizontalMixNode, EStrataNodeForPin::HorizontalMix);
|
|
AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateVerticalLayerNode, EStrataNodeForPin::VerticalLayer);
|
|
AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateWeightNode, EStrataNodeForPin::Weight);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
uint32 NumVirtualTextureLookups = MaterialResource->GetEstimatedNumVirtualTextureLookups();
|
|
if (NumVirtualTextureLookups > 0)
|
|
{
|
|
Canvas->DrawShadowedString(
|
|
5,
|
|
DrawPositionY,
|
|
*FString::Printf(TEXT("Virtual Texture Lookups (Est.): %u"), NumVirtualTextureLookups),
|
|
FontToUse,
|
|
FLinearColor(1, 1, 0)
|
|
);
|
|
DrawPositionY += SpacingBetweenLines;
|
|
}
|
|
|
|
const uint32 NumVirtualTextureStacks = MaterialResource->GetNumVirtualTextureStacks();
|
|
if (NumVirtualTextureStacks > 0)
|
|
{
|
|
Canvas->DrawShadowedString(
|
|
5,
|
|
DrawPositionY,
|
|
*FString::Printf(TEXT("Virtual Texture Stacks: %u"), NumVirtualTextureStacks),
|
|
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()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraphNode* FocusNode = NULL;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
bool bSetPreviewExpression = false;
|
|
UMaterialExpressionFunctionOutput* FirstOutput = NULL;
|
|
for (int32 ExpressionIndex = Material->GetExpressions().Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
UMaterialExpression* Expression = Material->GetExpressions()[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;
|
|
FocusedGraphEd->GetViewLocation(CurrLocation, CurrZoomLevel);
|
|
FocusedGraphEd->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);
|
|
}
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->SetPreviewMaterial(nullptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PreviewUIViewport.IsValid())
|
|
{
|
|
PreviewUIViewport->SetPreviewMaterial(nullptr);
|
|
}
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->SetPreviewMaterial(InMaterialInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RefreshPreviewViewport()
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::LoadEditorSettings()
|
|
{
|
|
EditorOptions = NewObject<UMaterialEditorOptions>();
|
|
|
|
if (EditorOptions->bHideUnusedConnectorsSetting) {OnHideConnectors();}
|
|
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->GetViewportClient())
|
|
{
|
|
PreviewViewport->GetViewportClient()->SetRealtime(true);
|
|
}
|
|
}
|
|
|
|
if (EditorOptions->bHideUnrelatedNodes)
|
|
{
|
|
ToggleHideUnrelatedNodes();
|
|
}
|
|
|
|
// 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->bHideUnusedConnectorsSetting = IsOnHideConnectorsChecked();
|
|
EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews();
|
|
EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked();
|
|
EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked();
|
|
EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes;
|
|
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->AssignExpressionCollection(Material->GetExpressionCollection());
|
|
ExpressionPreviewMaterial->bEnableExecWire = Material->IsUsingControlFlow();
|
|
ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator();
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
ExpressionPreviewMaterial->BlendMode = MaterialFunction->PreviewBlendMode;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
Material->MaterialGraph->UpdatePinTypes();
|
|
|
|
if (!PreviewExpression)
|
|
{
|
|
UpdateStatsMaterials();
|
|
|
|
// Null out the expression preview material so they can be GC'ed
|
|
ExpressionPreviewMaterial = NULL;
|
|
}
|
|
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;
|
|
FMaterialResource* CurrentResource = Material->GetMaterialResource(FeatureLevel);
|
|
if(CurrentResource && CurrentResource->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->AssignExpressionCollection(Material->GetExpressionCollection());
|
|
|
|
// Preserve the thumbnail info
|
|
UThumbnailInfo* OriginalThumbnailInfo = MaterialFunction->ParentFunction->ThumbnailInfo;
|
|
UThumbnailInfo* ThumbnailInfo = MaterialFunction->ThumbnailInfo;
|
|
MaterialFunction->ParentFunction->ThumbnailInfo = NULL;
|
|
MaterialFunction->ThumbnailInfo = NULL;
|
|
|
|
// Cache any metadata
|
|
const TMap<FName, FString>* MetaData = UMetaData::GetMapForObject(MaterialFunction->ParentFunction);
|
|
|
|
// 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 the metadata
|
|
if (MetaData)
|
|
{
|
|
UMetaData* PackageMetaData = MaterialFunction->ParentFunction->GetOutermost()->GetMetaData();
|
|
PackageMetaData->SetObjectValues(MaterialFunction->ParentFunction, *MetaData);
|
|
}
|
|
|
|
// 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 (UMaterialExpression* CurrentExpression : MaterialFunction->ParentFunction->GetExpressions())
|
|
{
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid expression whilst saving material function."));
|
|
|
|
// Link the expressions back to their function
|
|
if (CurrentExpression)
|
|
{
|
|
CurrentExpression->Material = NULL;
|
|
CurrentExpression->Function = MaterialFunction->ParentFunction;
|
|
}
|
|
}
|
|
for (UMaterialExpressionComment* CurrentExpression : MaterialFunction->ParentFunction->GetEditorComments())
|
|
{
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid comment whilst saving material function."));
|
|
|
|
// 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;
|
|
|
|
// Cache any metadata
|
|
const TMap<FName, FString>* MetaData = UMetaData::GetMapForObject(OriginalMaterial);
|
|
|
|
// 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;
|
|
|
|
// Restore the metadata
|
|
if (MetaData)
|
|
{
|
|
UMetaData* PackageMetaData = OriginalMaterial->GetOutermost()->GetMetaData();
|
|
PackageMetaData->SetObjectValues(OriginalMaterial, *MetaData);
|
|
}
|
|
|
|
// 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->GetExpressions())
|
|
{
|
|
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->GetExpressions())
|
|
{
|
|
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
|
|
FMaterialResource* CurrentResource = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel);
|
|
if (CurrentResource)
|
|
{
|
|
CompileErrors = CurrentResource->GetCompileErrors();
|
|
}
|
|
|
|
bool bFoundFunctionOutput = false;
|
|
for (UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
if (MaterialExpression->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() != EMaterialFunctionUsage::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 estimated virtual texture look-up counts
|
|
uint32 NumVirtualTextureLookups = MaterialResource->GetEstimatedNumVirtualTextureLookups();
|
|
if (NumVirtualTextureLookups > 0)
|
|
{
|
|
FString LookupsString = FString::Printf(TEXT("Virtual Texture Lookups (Est.): %u"), NumVirtualTextureLookups);
|
|
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(LookupsString, FLinearColor::Yellow)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(LookupsString)));
|
|
Messages.Add(Line);
|
|
}
|
|
|
|
const uint32 NumVirtualTextureStacks = MaterialResource->GetNumVirtualTextureStacks();
|
|
if (NumVirtualTextureStacks > 0u)
|
|
{
|
|
FString VTString = FString::Printf(TEXT("Virtual Texture Stacks: %u"), NumVirtualTextureStacks);
|
|
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(VTString, FLinearColor::Yellow)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(VTString)));
|
|
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);
|
|
}
|
|
|
|
if (FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap())
|
|
{
|
|
const FString StrataMaterialDescription = ShaderMap->GetStrataMaterialDescription();
|
|
|
|
if (StrataMaterialDescription.Len())
|
|
{
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(StrataMaterialDescription)));
|
|
Messages.Add(Line);
|
|
}
|
|
|
|
// Add shader count
|
|
FString ShaderCountString = FString::Printf(TEXT("Shader Count: %u"), ShaderMap->GetShaderNum());
|
|
TSharedRef<FTokenizedMessage> ShaderCountLine = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
ShaderCountLine->AddToken(FTextToken::Create(FText::FromString(ShaderCountString)));
|
|
Messages.Add(ShaderCountLine);
|
|
}
|
|
}
|
|
|
|
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->TryInvokeTab(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 (UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
if (MaterialExpression->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]);
|
|
}
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
TSharedPtr<SWidget> TitleBar = FocusedGraphEd->GetTitleBar();
|
|
TSharedPtr<SMaterialEditorTitleBar> MaterialTitleBar = StaticCastSharedPtr<SMaterialEditorTitleBar>(TitleBar);
|
|
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);
|
|
|
|
MaterialTitleBar->RequestRefresh();
|
|
}
|
|
|
|
if (MaterialTitleBar->MaterialInfoList)
|
|
{
|
|
MaterialTitleBar->MaterialInfoList->SetVisibility(CompileErrors.IsEmpty() ? EVisibility::Collapsed : EVisibility::Visible);
|
|
}
|
|
}
|
|
|
|
// extract material stats
|
|
MaterialStatsManager->SetMaterial(MaterialForStats);
|
|
MaterialStatsManager->Update();
|
|
}
|
|
|
|
void FMaterialEditor::UpdateGraphNodeStates()
|
|
{
|
|
const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel);
|
|
|
|
bool bUpdatedErrorState = false;
|
|
bool bToggledVisibleState = bPreviewFeaturesChanged;
|
|
bool bShowAllNodes = true;
|
|
|
|
TArray<UMaterialExpression*> VisibleExpressions;
|
|
|
|
FStaticParameterSet StaticSwitchSet;
|
|
if (bPreviewFeaturesChanged && bPreviewStaticSwitches)
|
|
{
|
|
for (UMaterialExpression* Expression : Material->GetExpressions())
|
|
{
|
|
if (UMaterialExpressionStaticSwitchParameter* StaticSwitch = Cast<UMaterialExpressionStaticSwitchParameter>(Expression))
|
|
{
|
|
FStaticSwitchParameter SwitchParam;
|
|
SwitchParam.Value = StaticSwitch->DefaultValue;
|
|
SwitchParam.ExpressionGUID = StaticSwitch->ExpressionGUID;
|
|
StaticSwitchSet.EditorOnly.StaticSwitchParameters.Add(SwitchParam);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bPreviewFeaturesChanged)
|
|
{
|
|
Material->GetAllReferencedExpressions(VisibleExpressions, StaticSwitchSet.IsEmpty() ? nullptr : &StaticSwitchSet, NodeFeatureLevel, NodeQualityLevel);
|
|
if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || !StaticSwitchSet.IsEmpty())
|
|
{
|
|
bShowAllNodes = false;
|
|
}
|
|
}
|
|
|
|
// Update main material graph and all subgraphs
|
|
bUpdatedErrorState |= UpdateGraphNodeState(Material->MaterialGraph, ErrorMaterialResource, VisibleExpressions, bShowAllNodes);
|
|
|
|
bPreviewFeaturesChanged = false;
|
|
|
|
if (bUpdatedErrorState || bToggledVisibleState)
|
|
{
|
|
// Rebuild the SGraphNodes to display/hide error block
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::UpdateGraphNodeState(UEdGraph* Graph, const FMaterialResource* ErrorMaterialResource, TArray<UMaterialExpression*>& VisibleExpressions, bool bShowAllNodes)
|
|
{
|
|
bool bUpdatedErrorState = false;
|
|
|
|
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(Graph);
|
|
|
|
// Have to loop through everything here as there's no way to be notified when the material resource updates
|
|
for (int32 Index = 0; Index < MaterialGraph->Nodes.Num(); ++Index)
|
|
{
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(MaterialGraph->Nodes[Index]);
|
|
if (MaterialNode)
|
|
{
|
|
MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression);
|
|
MaterialNode->bIsErrorExpression = (ErrorMaterialResource != nullptr) && (ErrorMaterialResource->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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UEdGraph* SubGraph : Graph->SubGraphs)
|
|
{
|
|
bUpdatedErrorState |= UpdateGraphNodeState(SubGraph, ErrorMaterialResource, VisibleExpressions, bShowAllNodes);
|
|
}
|
|
|
|
return bUpdatedErrorState;
|
|
}
|
|
|
|
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::OnHideConnectors),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnHideConnectorsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleLivePreview,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleLivePreview),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleHideUnrelatedNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleHideUnrelatedNodes),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleHideUnrelatedNodesChecked));
|
|
|
|
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.PromoteToDouble,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.PromoteToFloat,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects));
|
|
|
|
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.SelectNamedRerouteDeclaration,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteDeclaration));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SelectNamedRerouteUsages,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteUsages));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertRerouteToNamedReroute,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertRerouteToNamedReroute));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertNamedRerouteToReroute,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertNamedRerouteToReroute));
|
|
|
|
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_Epic,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Epic),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Epic));
|
|
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_ES31,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES3_1),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::ES3_1),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES3_1));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_SM5,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM5),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::SM5),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM5));
|
|
ToolkitCommands->MapAction(
|
|
Commands.FeatureLevel_SM6,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM6),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::SM6),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM6));
|
|
}
|
|
|
|
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::OnHideConnectors()
|
|
{
|
|
bHideUnusedConnectors = !bHideUnusedConnectors;
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->SetPinVisibility(bHideUnusedConnectors ? SGraphEditor::Pin_HideNoConnection : SGraphEditor::Pin_Show);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::IsOnHideConnectorsChecked() const
|
|
{
|
|
return bHideUnusedConnectors == true;
|
|
}
|
|
|
|
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::ToggleHideUnrelatedNodes()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bHideUnrelatedNodes = !bHideUnrelatedNodes;
|
|
|
|
FocusedGraphEd->ResetAllNodesUnrelatedStates();
|
|
|
|
if (bHideUnrelatedNodes && bSelectRegularNode)
|
|
{
|
|
HideUnrelatedNodes();
|
|
}
|
|
else
|
|
{
|
|
bLockNodeFadeState = false;
|
|
bFocusWholeChain = false;
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::IsToggleHideUnrelatedNodesChecked() const
|
|
{
|
|
return bHideUnrelatedNodes == true;
|
|
}
|
|
|
|
void FMaterialEditor::CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray<UMaterialGraphNode*>& CollectedNodes)
|
|
{
|
|
for (UEdGraphPin* OutputPin : CurrentNode->Pins)
|
|
{
|
|
if (OutputPin->Direction == EGPD_Output)
|
|
{
|
|
for (auto& Link : OutputPin->LinkedTo)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
CollectDownstreamNodes(LinkedNode, CollectedNodes);
|
|
|
|
if (bFocusWholeChain)
|
|
{
|
|
CollectUpstreamNodes(LinkedNode, CollectedNodes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray<UMaterialGraphNode*>& CollectedNodes)
|
|
{
|
|
for (UEdGraphPin* InputPin : CurrentNode->Pins)
|
|
{
|
|
if (InputPin->Direction == EGPD_Input)
|
|
{
|
|
for (auto& Link : InputPin->LinkedTo)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
CollectUpstreamNodes(LinkedNode, CollectedNodes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::HideUnrelatedNodes()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<UMaterialGraphNode*> NodesToShow;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* SelectedNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
|
|
if (SelectedNode)
|
|
{
|
|
NodesToShow.Add(SelectedNode);
|
|
CollectDownstreamNodes( SelectedNode, NodesToShow );
|
|
CollectUpstreamNodes( SelectedNode, NodesToShow );
|
|
}
|
|
}
|
|
|
|
TArray<class UEdGraphNode*> AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes;
|
|
|
|
TArray<UEdGraphNode*> CommentNodes;
|
|
TArray<UEdGraphNode*> RelatedNodes;
|
|
|
|
for (auto& Node : AllNodes)
|
|
{
|
|
// Always draw the root graph node which can't cast to UMaterialGraphNode
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(Node))
|
|
{
|
|
if (NodesToShow.Contains(GraphNode))
|
|
{
|
|
Node->SetNodeUnrelated(false);
|
|
RelatedNodes.Add(Node);
|
|
}
|
|
else
|
|
{
|
|
Node->SetNodeUnrelated(true);
|
|
}
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(Node))
|
|
{
|
|
CommentNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
FocusedGraphEd->FocusCommentNodes(CommentNodes, RelatedNodes);
|
|
}
|
|
|
|
void FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu(UToolMenu* Menu)
|
|
{
|
|
Menu->bShouldCloseWindowAfterMenuSelection = true;
|
|
|
|
TSharedRef<SWidget> LockNodeStateCheckBox = SNew(SBox)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
|
.OnCheckStateChanged(this, &FMaterialEditor::OnLockNodeStateCheckStateChanged)
|
|
.Style(FAppStyle::Get(), "Menu.CheckBox")
|
|
.ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes."))
|
|
.Content()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("LockNodeState", "Lock Node State"))
|
|
]
|
|
]
|
|
];
|
|
|
|
TSharedRef<SWidget> FocusWholeChainCheckBox = SNew(SBox)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(bFocusWholeChain ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
|
.OnCheckStateChanged(this, &FMaterialEditor::OnFocusWholeChainCheckStateChanged)
|
|
.Style(FAppStyle::Get(), "Menu.CheckBox")
|
|
.ToolTipText(LOCTEXT("FocusWholeChainCheckBoxToolTip", "Focus all nodes in the chain."))
|
|
.Content()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("FocusWholeChain", "Focus Whole Chain"))
|
|
]
|
|
]
|
|
];
|
|
|
|
FToolMenuSection& OptionsSection = Menu->AddSection("OptionsSection", LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options"));
|
|
OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("LockNodeStateCheckBox", FUIAction(), LockNodeStateCheckBox));
|
|
OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("FocusWholeChainCheckBox", FUIAction(), FocusWholeChainCheckBox));
|
|
}
|
|
|
|
void FMaterialEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState)
|
|
{
|
|
bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false;
|
|
}
|
|
|
|
void FMaterialEditor::OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bFocusWholeChain = (NewCheckedState == ECheckBoxState::Checked) ? true : false;
|
|
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode)
|
|
{
|
|
FocusedGraphEd->ResetAllNodesUnrelatedStates();
|
|
|
|
HideUnrelatedNodes();
|
|
}
|
|
}
|
|
|
|
|
|
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 = 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::OnPromoteObjects()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("MaterialEditorPromote", "Material Editor: Promote"));
|
|
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;
|
|
UMaterialExpressionVectorParameter* VectorParameterExpression = Cast<UMaterialExpressionVectorParameter>(CurrentSelectedExpression);
|
|
UMaterialExpressionDoubleVectorParameter* DoubleVectorParameterExpression = Cast<UMaterialExpressionDoubleVectorParameter>(CurrentSelectedExpression);
|
|
|
|
// Setup the class to convert to
|
|
UClass* ClassToCreate = nullptr;
|
|
if (VectorParameterExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionDoubleVectorParameter::StaticClass();
|
|
}
|
|
else if (DoubleVectorParameterExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionVectorParameter::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
|
|
NewExpression->SetParameterName(CurrentSelectedExpression->GetParameterName());
|
|
if (VectorParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionDoubleVectorParameter>(NewExpression)->DefaultValue = FVector4d(VectorParameterExpression->DefaultValue);
|
|
}
|
|
else if (DoubleVectorParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionVectorParameter>(NewExpression)->DefaultValue = FLinearColor(DoubleVectorParameterExpression->DefaultValue);
|
|
}
|
|
|
|
if (bNeedsRefresh)
|
|
{
|
|
// Refresh the expression preview if we changed its properties after it was created
|
|
NewExpression->bNeedToUpdatePreview = true;
|
|
RefreshExpressionPreview(NewExpression, true);
|
|
|
|
UpdateGenerator();
|
|
}
|
|
|
|
NodesToDelete.AddUnique(GraphNode);
|
|
NodesToSelect.Add(NewGraphNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the replaced nodes
|
|
DeleteNodes(NodesToDelete);
|
|
|
|
// Select each of the newly converted expressions
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
for (TArray<UEdGraphNode*>::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(*NodeIter, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertObjects()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = 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);
|
|
UMaterialExpressionRuntimeVirtualTextureSample* RuntimeVirtualTextureSampleExpression = Cast<UMaterialExpressionRuntimeVirtualTextureSample>(CurrentSelectedExpression);
|
|
|
|
// Setup the class to convert to
|
|
UClass* ClassToCreate = NULL;
|
|
if (Constant1Expression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionScalarParameter::StaticClass();
|
|
}
|
|
else if (Constant2Expression || Constant3Expression || Constant4Expression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionVectorParameter::StaticClass();
|
|
}
|
|
else if (ParticleSubUVExpression) // Has to come before the TextureSample comparison...
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameterSubUV::StaticClass();
|
|
}
|
|
else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCube::StaticClass()))
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameterCube::StaticClass();
|
|
}
|
|
else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTexture2DArray::StaticClass()))
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameter2DArray::StaticClass();
|
|
}
|
|
else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCubeArray::StaticClass()))
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameterCubeArray::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 (RuntimeVirtualTextureSampleExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionRuntimeVirtualTextureSampleParameter::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 (RuntimeVirtualTextureSampleExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionRuntimeVirtualTextureSampleParameter* NewRuntimeVirtualTextureExpression = CastChecked<UMaterialExpressionRuntimeVirtualTextureSampleParameter>(NewExpression);
|
|
NewRuntimeVirtualTextureExpression->VirtualTexture = RuntimeVirtualTextureSampleExpression->VirtualTexture;
|
|
NewRuntimeVirtualTextureExpression->MaterialType = RuntimeVirtualTextureSampleExpression->MaterialType;
|
|
NewRuntimeVirtualTextureExpression->MipValueMode = RuntimeVirtualTextureSampleExpression->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 );
|
|
|
|
UpdateGenerator();
|
|
}
|
|
|
|
NodesToDelete.AddUnique(GraphNode);
|
|
NodesToSelect.Add(NewGraphNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the replaced nodes
|
|
DeleteNodes(NodesToDelete);
|
|
|
|
// Select each of the newly converted expressions
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
for ( TArray<UEdGraphNode*>::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter )
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(*NodeIter, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertTextures()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = 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
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
for ( TArray<UEdGraphNode*>::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter )
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(*NodeIter, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectNamedRerouteDeclaration()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionNamedRerouteUsage* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(CurrentSelectedExpression);
|
|
if (Usage && Usage->Declaration)
|
|
{
|
|
UEdGraphNode* DeclarationGraphNode = Usage->Declaration->GraphNode;
|
|
if (DeclarationGraphNode)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(DeclarationGraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FocusedGraphEd->ZoomToFit(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectNamedRerouteUsages()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionNamedRerouteDeclaration* Declaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(CurrentSelectedExpression);
|
|
for(UMaterialExpression* Expression : Material->GetExpressions())
|
|
{
|
|
auto* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(Expression);
|
|
if (Usage && Usage->Declaration == Declaration)
|
|
{
|
|
UEdGraphNode* UsageGraphNode = Usage->GraphNode;
|
|
if (UsageGraphNode)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(UsageGraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FocusedGraphEd->ZoomToFit(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertRerouteToNamedReroute()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode_Knot* GraphNode = Cast<UMaterialGraphNode_Knot>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UEdGraph* Graph = GraphNode->GetGraph();
|
|
const FScopedTransaction Transaction(LOCTEXT("ConvertRerouteToNamedReroute", "Convert reroute to named reroute"));
|
|
Graph->Modify();
|
|
|
|
auto* Declaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(FMaterialEditorUtilities::CreateNewMaterialExpression(
|
|
Graph,
|
|
UMaterialExpressionNamedRerouteDeclaration::StaticClass(),
|
|
FVector2D(GraphNode->NodePosX - 50, GraphNode->NodePosY),
|
|
false,
|
|
true));
|
|
if (!Declaration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraphPin* DeclarationInputPin = nullptr;
|
|
for (auto* Pin : Declaration->GraphNode->GetAllPins())
|
|
{
|
|
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
|
|
{
|
|
DeclarationInputPin = Pin;
|
|
break;
|
|
}
|
|
}
|
|
if (!ensure(DeclarationInputPin))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto* InputPin : GraphNode->GetInputPin()->LinkedTo)
|
|
{
|
|
InputPin->MakeLinkTo(DeclarationInputPin);
|
|
}
|
|
|
|
TArray<UEdGraphPin*> OutputPins = GraphNode->GetOutputPin()->LinkedTo;
|
|
OutputPins.Sort([](UEdGraphPin& A, UEdGraphPin& B) { return A.GetOwningNode()->NodePosY < B.GetOwningNode()->NodePosY; });
|
|
|
|
int Index = -OutputPins.Num() / 2;
|
|
for (auto* OutputPin : OutputPins)
|
|
{
|
|
auto* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(FMaterialEditorUtilities::CreateNewMaterialExpression(
|
|
Material->MaterialGraph,
|
|
UMaterialExpressionNamedRerouteUsage::StaticClass(),
|
|
FVector2D(GraphNode->NodePosX + 50, GraphNode->NodePosY + Index * 50),
|
|
false,
|
|
true));
|
|
if (Usage)
|
|
{
|
|
Usage->Declaration = Declaration;
|
|
Usage->DeclarationGuid = Declaration->VariableGuid;
|
|
Usage->GraphNode->GetAllPins()[0]->MakeLinkTo(OutputPin); // usage node has a single pin
|
|
}
|
|
Index++;
|
|
}
|
|
GraphNode->DestroyNode();
|
|
FocusedGraphEd->SetNodeSelection(Declaration->GraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertNamedRerouteToReroute()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UEdGraph* Graph = GraphNode->GetGraph();
|
|
const FScopedTransaction Transaction(LOCTEXT("ConvertNamedRerouteToReroute", "Convert named reroute to reroute"));
|
|
Graph->Modify();
|
|
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionNamedRerouteDeclaration* Declaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(CurrentSelectedExpression);
|
|
if (!Declaration)
|
|
{
|
|
UMaterialExpressionNamedRerouteUsage* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(CurrentSelectedExpression);
|
|
if (Usage)
|
|
{
|
|
Declaration = Usage->Declaration;
|
|
}
|
|
}
|
|
if (!Declaration)
|
|
{
|
|
return;
|
|
}
|
|
UEdGraphNode* DeclarationGraphNode = Declaration->GraphNode;
|
|
FVector2D KnotPosition(DeclarationGraphNode->NodePosX + 50, DeclarationGraphNode->NodePosY);
|
|
|
|
UMaterialExpression* Reroute = FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, UMaterialExpressionReroute::StaticClass(), KnotPosition, false, true);
|
|
auto KnotGraphNode = CastChecked<UMaterialGraphNode_Knot>(Reroute->GraphNode);
|
|
|
|
for (UEdGraphPin* Pin : DeclarationGraphNode->GetAllPins())
|
|
{
|
|
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
|
|
{
|
|
for (UEdGraphPin* InputPin : Pin->LinkedTo)
|
|
{
|
|
KnotGraphNode->GetInputPin()->MakeLinkTo(InputPin);
|
|
}
|
|
}
|
|
if (Pin->Direction == EEdGraphPinDirection::EGPD_Output)
|
|
{
|
|
for (UEdGraphPin* OutputPin : Pin->LinkedTo)
|
|
{
|
|
KnotGraphNode->GetOutputPin()->MakeLinkTo(OutputPin);
|
|
}
|
|
}
|
|
}
|
|
DeclarationGraphNode->DestroyNode();
|
|
|
|
for(UMaterialExpression* Expression : Material->GetExpressions())
|
|
{
|
|
auto* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(Expression);
|
|
if (Usage && Usage->Declaration == Declaration)
|
|
{
|
|
UEdGraphNode* UsageGraphNode = Usage->GraphNode;
|
|
if (UsageGraphNode)
|
|
{
|
|
UEdGraphPin* Pin = Usage->GraphNode->GetAllPins()[0]; // usage node has a single pin
|
|
for (UEdGraphPin* OutputPin : Pin->LinkedTo)
|
|
{
|
|
KnotGraphNode->GetOutputPin()->MakeLinkTo(OutputPin);
|
|
}
|
|
UsageGraphNode->DestroyNode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnPreviewNode()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
|
|
SetPreviewExpression(GraphNode->MaterialExpression);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnToggleRealtimePreview()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = 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 = 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();
|
|
for (UEdGraphPin* Pin : CurrentNode->Pins)
|
|
{
|
|
if (Pin->Direction == EGPD_Output)
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Pin->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);
|
|
}
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectUpstreamNodes()
|
|
{
|
|
TArray<UMaterialGraphNode*> NodesToCheck;
|
|
TArray<UMaterialGraphNode*> CheckedNodes;
|
|
TArray<UMaterialGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = 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();
|
|
for (UEdGraphPin* Pin : CurrentNode->Pins)
|
|
{
|
|
if (Pin->Direction == EGPD_Input)
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Pin->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);
|
|
}
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnForceRefreshPreviews()
|
|
{
|
|
ForceRefreshExpressionPreviews();
|
|
RefreshPreviewViewport();
|
|
}
|
|
|
|
void FMaterialEditor::OnCreateComment()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
CreateNewMaterialExpressionComment(FocusedGraphEd->GetPasteLocation());
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnCreateComponentMaskNode()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
CreateNewMaterialExpression(UMaterialExpressionComponentMask::StaticClass(), FocusedGraphEd->GetPasteLocation(), true, false);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnFindInMaterial()
|
|
{
|
|
TabManager->TryInvokeTab(FMaterialEditorTabs::FindTabId);
|
|
FindResults->FocusForUse();
|
|
}
|
|
|
|
void FMaterialEditor::OnGraphEditorFocused(const TSharedRef<class SGraphEditor>& InGraphEditor)
|
|
{
|
|
if (FocusedGraphEdPtr == InGraphEditor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update the graph editor that is currently focused
|
|
FocusedGraphEdPtr = InGraphEditor;
|
|
|
|
// Refresh navigation history
|
|
TSharedPtr<SWidget> TitleBar = FocusedGraphEdPtr.Pin()->GetTitleBar();
|
|
StaticCastSharedPtr<SMaterialEditorTitleBar>(TitleBar)->RequestRefresh();
|
|
|
|
// Update the inspector as well, to show selection from the focused graph editor
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if (bHideUnrelatedNodes && SelectedNodes.Num() <= 0)
|
|
{
|
|
FocusedGraphEdPtr.Pin()->ResetAllNodesUnrelatedStates();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnGraphEditorBackgrounded(const TSharedRef<SGraphEditor>& InGraphEditor)
|
|
{
|
|
FocusedGraphEdPtr = nullptr;
|
|
}
|
|
|
|
UClass* FMaterialEditor::GetOnPromoteToParameterClass(const UEdGraphPin* TargetPin) const
|
|
{
|
|
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_Anisotropy:
|
|
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_EmissiveColor:
|
|
case MP_BaseColor:
|
|
case MP_SubsurfaceColor:
|
|
case MP_SpecularColor:
|
|
case MP_Normal:
|
|
case MP_Tangent:
|
|
return UMaterialExpressionVectorParameter::StaticClass();
|
|
|
|
case MP_FrontMaterial:
|
|
return nullptr;
|
|
|
|
}
|
|
}
|
|
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();
|
|
|
|
case MCT_Strata: return nullptr;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FMaterialEditor::OnPromoteToParameter(const FToolMenuContext& InMenuContext) const
|
|
{
|
|
UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext<UGraphNodeContextMenuContext>();
|
|
const UEdGraphPin* TargetPin = NodeContext->Pin;
|
|
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, const_cast<UEdGraphPin*>(TargetPin), NewNodePos));
|
|
|
|
if (MaterialNode->MaterialExpression->HasAParameterName())
|
|
{
|
|
MaterialNode->MaterialExpression->SetParameterName(TargetPin->PinName);
|
|
MaterialNode->MaterialExpression->ValidateParameterName(false);
|
|
}
|
|
}
|
|
if (MaterialEditorInstance != nullptr)
|
|
{
|
|
MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnCanPromoteToParameter(const FToolMenuContext& InMenuContext) const
|
|
{
|
|
UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext<UGraphNodeContextMenuContext>();
|
|
if (!NodeContext)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const UEdGraphPin* TargetPin = NodeContext->Pin;
|
|
if (TargetPin && (TargetPin->Direction == EEdGraphPinDirection::EGPD_Input) && (TargetPin->LinkedTo.Num() == 0))
|
|
{
|
|
return GetOnPromoteToParameterClass(TargetPin) != nullptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::OnCreateStrataNodeForPin(const FToolMenuContext& InMenuContext, EStrataNodeForPin NodeForPin) const
|
|
{
|
|
UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext<UGraphNodeContextMenuContext>();
|
|
const UEdGraphPin* TargetPin = NodeContext->Pin;
|
|
UMaterialGraphNode_Base* PinNode = Cast<UMaterialGraphNode_Base>(TargetPin->GetOwningNode());
|
|
const bool bTargetPinIsInput = TargetPin->Direction == EEdGraphPinDirection::EGPD_Input;
|
|
|
|
FMaterialGraphSchemaAction_NewNode Action;
|
|
Action.MaterialExpressionClass = UMaterialExpressionStrataSlabBSDF::StaticClass();
|
|
if (NodeForPin == EStrataNodeForPin::HorizontalMix)
|
|
{
|
|
Action.MaterialExpressionClass = UMaterialExpressionStrataHorizontalMixing::StaticClass();
|
|
}
|
|
else if (NodeForPin == EStrataNodeForPin::VerticalLayer)
|
|
{
|
|
Action.MaterialExpressionClass = UMaterialExpressionStrataVerticalLayering::StaticClass();
|
|
}
|
|
else if (NodeForPin == EStrataNodeForPin::Weight)
|
|
{
|
|
Action.MaterialExpressionClass = UMaterialExpressionStrataWeight::StaticClass();
|
|
}
|
|
|
|
check(PinNode);
|
|
UEdGraph* GraphObj = PinNode->GetGraph();
|
|
check(GraphObj);
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("CreateStrataNode", "Create Strata Node"));
|
|
GraphObj->Modify();
|
|
|
|
// Set position of new node to be close to node we clicked on
|
|
FVector2D NewNodePos;
|
|
NewNodePos.X = PinNode->NodePosX + (bTargetPinIsInput ? -200 : 300);
|
|
NewNodePos.Y = PinNode->NodePosY;
|
|
|
|
|
|
if (bTargetPinIsInput)
|
|
{
|
|
UMaterialGraphNode* NewNode = Cast<UMaterialGraphNode>(Action.PerformAction(GraphObj, const_cast<UEdGraphPin*>(TargetPin), NewNodePos));
|
|
}
|
|
else
|
|
{
|
|
// Link manually
|
|
UMaterialGraphNode* NewNode = Cast<UMaterialGraphNode>(Action.PerformAction(GraphObj, nullptr, NewNodePos));
|
|
const TArray<FExpressionInput*> NewNodeExpressionInputs = NewNode->MaterialExpression->GetInputs();
|
|
|
|
// From that direction, the node is never going to be a root node (a root node has no output we can connect from).
|
|
UMaterialGraphNode* TargetPinNode = Cast<UMaterialGraphNode>(TargetPin->GetOwningNode());
|
|
|
|
check(NewNodeExpressionInputs.Num() > 0 && TargetPin->SourceIndex < TargetPinNode->MaterialExpression->GetOutputs().Num());
|
|
|
|
FName TargetPinName = TargetPinNode->MaterialExpression->GetOutputs()[TargetPin->SourceIndex].OutputName;
|
|
UMaterialEditingLibrary::ConnectMaterialExpressions(TargetPinNode->MaterialExpression, TargetPinName.ToString(), NewNode->MaterialExpression, FString());
|
|
|
|
Material->MaterialGraph->RebuildGraph();
|
|
}
|
|
|
|
if (MaterialEditorInstance != nullptr)
|
|
{
|
|
MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnCanCreateStrataNodeForPin(const FToolMenuContext& InMenuContext, EStrataNodeForPin NodeForPin) const
|
|
{
|
|
UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext<UGraphNodeContextMenuContext>();
|
|
const UEdGraphPin* TargetPin = NodeContext->Pin;
|
|
UMaterialGraphNode_Root* RootPinNode = Cast<UMaterialGraphNode_Root>(TargetPin->GetOwningNode());
|
|
UMaterialGraphNode* OtherPinNode = Cast<UMaterialGraphNode>(TargetPin->GetOwningNode());
|
|
|
|
if ((TargetPin->Direction == EEdGraphPinDirection::EGPD_Input) && (TargetPin->LinkedTo.Num() == 0))
|
|
{
|
|
if (RootPinNode != nullptr)
|
|
{
|
|
EMaterialProperty propertyId = (EMaterialProperty)FCString::Atoi(*TargetPin->PinType.PinSubCategory.ToString());
|
|
switch (propertyId)
|
|
{
|
|
case MP_FrontMaterial:
|
|
return true;
|
|
}
|
|
}
|
|
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_Strata:
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (TargetPin && (TargetPin->Direction == EEdGraphPinDirection::EGPD_Output) && (TargetPin->LinkedTo.Num() == 0) && NodeForPin != EStrataNodeForPin::Slab)
|
|
{
|
|
if (OtherPinNode)
|
|
{
|
|
const TArray<FExpressionOutput>& ExpressionOutputs = OtherPinNode->MaterialExpression->GetOutputs();
|
|
check(TargetPin->SourceIndex < ExpressionOutputs.Num());
|
|
if (OtherPinNode->MaterialExpression->GetOutputType(TargetPin->SourceIndex) == MCT_Strata)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FString FMaterialEditor::GetDocLinkForSelectedNode()
|
|
{
|
|
FString DocumentationLink;
|
|
|
|
TArray<UObject*> SelectedNodes = 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>(InAddedAssetData.AssetClassPath);
|
|
|
|
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::SetNumericParameterDefaultOnDependentMaterials(EMaterialParameterType Type, FName ParameterName, const UE::Shader::FValue& 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->GetCachedExpressionData().FunctionInfos.Num(); FunctionIndex++)
|
|
{
|
|
if (CurrentMaterial->GetCachedExpressionData().FunctionInfos[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->OverrideNumericParameterDefault(Type, 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->OverrideNumericParameterDefault(Type, ParameterName, Value, bOverride, FeatureLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnNumericParameterDefaultChanged(class UMaterialExpression* Expression, EMaterialParameterType Type, FName ParameterName, const UE::Shader::FValue& Value)
|
|
{
|
|
check(Expression);
|
|
|
|
if (Expression->Material == Material && OriginalMaterial)
|
|
{
|
|
SetNumericParameterDefaultOnDependentMaterials(Type, ParameterName, Value, true);
|
|
OverriddenNumericParametersToRevert.Add(MakeTuple(Type, ParameterName));
|
|
}
|
|
|
|
OnParameterDefaultChanged();
|
|
}
|
|
|
|
void FMaterialEditor::OnParameterDefaultChanged()
|
|
{
|
|
// Brute force all flush virtual textures if this material writes to any runtime virtual texture.
|
|
if (Material->GetCachedExpressionData().bHasRuntimeVirtualTextureOutput)
|
|
{
|
|
ENQUEUE_RENDER_COMMAND(FlushVTCommand)([](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
GetRendererModule().FlushVirtualTextureCache();
|
|
});
|
|
}
|
|
}
|
|
|
|
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_MaterialProperties(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedPtr<SDockTab> DetailsTab = SNew(SDockTab)
|
|
.Label( LOCTEXT("MaterialDetailsTitle", "Details") )
|
|
[
|
|
MaterialDetailsView.ToSharedRef()
|
|
];
|
|
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
// Since we're initialising, make sure nothing is selected
|
|
FocusedGraphEdPtr.Pin()->ClearSelectionSet();
|
|
}
|
|
SpawnedDetailsTab = DetailsTab;
|
|
return DetailsTab.ToSharedRef();
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args)
|
|
{
|
|
check( Args.GetTabId() == FMaterialEditorTabs::PaletteTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.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() == FMaterialEditorTabs::FindTabId);
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.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() == FMaterialEditorTabs::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)
|
|
.Label(LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings"))
|
|
[
|
|
SNew(SBox)
|
|
[
|
|
InWidget
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_ParameterDefaults(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("Parameters", "Parameters"))
|
|
[
|
|
SNew(SBox)
|
|
[
|
|
MaterialParametersOverviewWidget.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_CustomPrimitiveData(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("CustomPrimitiveData", "Custom Primitive Data"))
|
|
[
|
|
SNew(SBox)
|
|
[
|
|
MaterialCustomPrimitiveDataWidget.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_LayerProperties(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.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->GetExpressionCollection().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;
|
|
ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator();
|
|
ExpressionPreviewMaterial->bEnableExecWire = Material->IsUsingControlFlow();
|
|
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->AssignExpressionCollection(Material->GetExpressionCollection());
|
|
|
|
// 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)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
if (Node->GetGraph() != FocusedGraphEd->GetCurrentGraph())
|
|
{
|
|
JumpToHyperlink(Node->GetGraph());
|
|
|
|
// Graph changed so editor changed, use new one to jump
|
|
FocusedGraphEdPtr.Pin()->JumpToNode(Node, false);
|
|
}
|
|
else
|
|
{
|
|
FocusedGraphEd->JumpToNode(Node, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::FindOpenTabsContainingDocument(const UObject* DocumentID, TArray<TSharedPtr<SDockTab>>& Results)
|
|
{
|
|
int32 StartingCount = Results.Num();
|
|
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
|
|
DocumentManager->FindMatchingTabs(Payload, /*inout*/ Results);
|
|
|
|
// Did we add anything new?
|
|
return (StartingCount != Results.Num());
|
|
}
|
|
|
|
TSharedPtr<SDockTab> FMaterialEditor::OpenDocument(const UObject* DocumentID, FDocumentTracker::EOpenDocumentCause Cause)
|
|
{
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
TSharedPtr<SDockTab> DocumentTab = DocumentManager->OpenDocument(Payload, Cause);
|
|
|
|
return DocumentTab;
|
|
}
|
|
|
|
void FMaterialEditor::CloseDocumentTab(const UObject* DocumentID)
|
|
{
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
DocumentManager->CloseTab(Payload);
|
|
}
|
|
|
|
UMaterialExpression* FMaterialEditor::CreateNewMaterialExpression(UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource, const UEdGraph* Graph)
|
|
{
|
|
check( NewExpressionClass->IsChildOf(UMaterialExpression::StaticClass()) );
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
UMaterialGraph* ExpressionGraph = Graph ? CastChecked<UMaterialGraph>(const_cast<UEdGraph*>(Graph)) : Material->MaterialGraph;
|
|
ExpressionGraph->Modify();
|
|
|
|
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 && FocusedGraphEd )
|
|
{
|
|
FocusedGraphEd->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)
|
|
{
|
|
ExpressionGraph->AddExpression(NewExpression, bAutoSelect);
|
|
|
|
// Select the new node.
|
|
if ( bAutoSelect && FocusedGraphEd )
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(NewExpression->GraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
RegenerateCodeView();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
|
|
RefreshExpressionPreviews();
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
SetMaterialDirty();
|
|
return NewExpression;
|
|
}
|
|
|
|
UMaterialExpressionComposite* FMaterialEditor::CreateNewMaterialExpressionComposite(const FVector2D& NodePos, const UEdGraph* Graph)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
UMaterialGraph* ExpressionGraph = Graph ? CastChecked<UMaterialGraph>(const_cast<UEdGraph*>(Graph)) : Material->MaterialGraph;
|
|
ExpressionGraph->Modify();
|
|
|
|
UMaterialExpressionComposite* NewComposite = nullptr;
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewComposite", "Material Editor: New Composite"));
|
|
Material->Modify();
|
|
|
|
UObject* ExpressionOuter = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
ExpressionOuter = MaterialFunction;
|
|
}
|
|
|
|
UMaterialExpression* NewExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionComposite::StaticClass(), nullptr, NodePos.X, NodePos.Y);
|
|
NewComposite = Cast<UMaterialExpressionComposite>(NewExpression);
|
|
|
|
if (NewComposite)
|
|
{
|
|
UMaterialExpression* InputPinBase = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionPinBase::StaticClass());
|
|
NewComposite->InputExpressions = Cast<UMaterialExpressionPinBase>(InputPinBase);
|
|
NewComposite->InputExpressions->PinDirection = EGPD_Output;
|
|
NewComposite->InputExpressions->SubgraphExpression = NewComposite;
|
|
|
|
UMaterialExpression* OutputPinBase = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionPinBase::StaticClass());
|
|
NewComposite->OutputExpressions = Cast<UMaterialExpressionPinBase>(OutputPinBase);
|
|
NewComposite->OutputExpressions->PinDirection = EGPD_Input;
|
|
NewComposite->OutputExpressions->SubgraphExpression = NewComposite;
|
|
|
|
// Create graph node, subgraph, and pinbase graph nodes
|
|
{
|
|
ExpressionGraph->AddExpression(NewComposite, true);
|
|
|
|
UMaterialGraphNode_Composite* CompositeNode = CastChecked<UMaterialGraphNode_Composite>(NewComposite->GraphNode);
|
|
CompositeNode->BoundGraph = ExpressionGraph->AddSubGraph(NewComposite);
|
|
CompositeNode->BoundGraph->Rename(*CastChecked<UMaterialExpressionComposite>(CompositeNode->MaterialExpression)->SubgraphName);
|
|
|
|
CompositeNode->BoundGraph->AddExpression(InputPinBase, false);
|
|
CompositeNode->BoundGraph->AddExpression(OutputPinBase, false);
|
|
}
|
|
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
FocusedGraphEd->SetNodeSelection(NewComposite->GraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
RefreshExpressionPreviews();
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
SetMaterialDirty();
|
|
return NewComposite;
|
|
}
|
|
|
|
UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos, const UEdGraph* Graph)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
UMaterialGraph* ExpressionGraph = Graph ? CastChecked<UMaterialGraph>(const_cast<UEdGraph*>(Graph)) : Material->MaterialGraph;
|
|
ExpressionGraph->Modify();
|
|
|
|
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->GetExpressionCollection().AddComment( NewComment );
|
|
|
|
FSlateRect Bounds;
|
|
if (FocusedGraphEd && FocusedGraphEd->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();
|
|
|
|
ExpressionGraph->AddComment(NewComment, true);
|
|
|
|
// Select the new comment.
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
FocusedGraphEd->SetNodeSelection(NewComment->GraphNode, true);
|
|
}
|
|
}
|
|
|
|
Material->MarkPackageDirty();
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
SetMaterialDirty();
|
|
return NewComment;
|
|
}
|
|
|
|
void FMaterialEditor::ForceRefreshExpressionPreviews()
|
|
{
|
|
// Initialize expression previews.
|
|
const bool bOldAlwaysRefreshAllPreviews = bAlwaysRefreshAllPreviews;
|
|
bAlwaysRefreshAllPreviews = true;
|
|
RefreshExpressionPreviews();
|
|
bAlwaysRefreshAllPreviews = bOldAlwaysRefreshAllPreviews;
|
|
}
|
|
|
|
void FMaterialEditor::AddToSelection(UMaterialExpression* Expression)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(Expression->GraphNode, true);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::JumpToExpression(UMaterialExpression* Expression)
|
|
{
|
|
check(Expression);
|
|
UEdGraphNode* ExpressionNode = nullptr;
|
|
|
|
// Note: 'Expression' may be from a serialized material with no graph, we compare to our material with a graph if this occurs
|
|
if (Expression->GraphNode)
|
|
{
|
|
ExpressionNode = Expression->GraphNode;
|
|
}
|
|
else if (Expression->bIsParameterExpression)
|
|
{
|
|
TArray<UMaterialExpression*>* GraphExpressions = Material->EditorParameters.Find(Expression->GetParameterName());
|
|
if (GraphExpressions && GraphExpressions->Num() == 1)
|
|
{
|
|
ExpressionNode = GraphExpressions->Last()->GraphNode;
|
|
}
|
|
else
|
|
{
|
|
UMaterialExpressionParameter* GraphExpression = Material->FindExpressionByGUID<UMaterialExpressionParameter>(Expression->GetParameterExpressionId());
|
|
ExpressionNode = GraphExpression ? ToRawPtr(GraphExpression->GraphNode) : nullptr;
|
|
}
|
|
}
|
|
else if (UMaterialExpressionFunctionOutput* ExpressionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression))
|
|
{
|
|
TArray<UMaterialExpressionFunctionOutput*> FunctionOutputExpressions;
|
|
Material->GetAllFunctionOutputExpressions(FunctionOutputExpressions);
|
|
UMaterialExpressionFunctionOutput** GraphExpression = FunctionOutputExpressions.FindByPredicate(
|
|
[&](const UMaterialExpressionFunctionOutput* GraphExpressionOutput)
|
|
{
|
|
return GraphExpressionOutput->Id == ExpressionOutput->Id;
|
|
});
|
|
ExpressionNode = GraphExpression ? ToRawPtr((*GraphExpression)->GraphNode) : nullptr;
|
|
}
|
|
|
|
JumpToNode(ExpressionNode);
|
|
}
|
|
|
|
void FMaterialEditor::SelectAllNodes()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->SelectAllNodes();
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanSelectAllNodes() const
|
|
{
|
|
return FocusedGraphEdPtr.IsValid();
|
|
}
|
|
|
|
void FMaterialEditor::DeleteSelectedNodes()
|
|
{
|
|
TArray<UEdGraphNode*> NodesToDelete;
|
|
const FGraphPanelSelectionSet SelectedNodes = 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") );
|
|
DeleteNodesInternal(NodesToDelete, bHaveExpressionsToDelete, bPreviewExpressionDeleted);
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
} // ScopedTransaction
|
|
|
|
// Deselect all expressions and comments.
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
FocusedGraphEd->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 = 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()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Cache off the old selection
|
|
const FGraphPanelSelectionSet OldSelectedNodes = GetSelectedNodes();
|
|
|
|
// Clear the selection and only select the nodes that can be duplicated
|
|
FGraphPanelSelectionSet RemainingNodes;
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != NULL) && Node->CanDuplicateNode())
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(Node, true);
|
|
}
|
|
else
|
|
{
|
|
RemainingNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Delete the duplicatable nodes
|
|
DeleteSelectedNodes();
|
|
|
|
// Reselect whatever's left from the original selection after the deletion
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(Node, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::DeleteNodesInternal(const TArray<class UEdGraphNode*>& NodesToDelete, bool& bHaveExpressionsToDelete, bool& bPreviewExpressionDeleted)
|
|
{
|
|
Material->Modify();
|
|
|
|
for (int32 Index = 0; Index < NodesToDelete.Num(); ++Index)
|
|
{
|
|
if (NodesToDelete[Index]->CanUserDeleteNode())
|
|
{
|
|
// If this is a user-selected pinbase, don't allow the delete to pass
|
|
if (Cast<UMaterialGraphNode_PinBase>(NodesToDelete[Index]) && GetSelectedNodes().Contains(NodesToDelete[Index]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(NodesToDelete[Index]))
|
|
{
|
|
// Break all node links first so that we don't update the material before deleting
|
|
GraphNode->Modify();
|
|
GraphNode->BreakAllNodeLinks();
|
|
|
|
UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression;
|
|
|
|
bHaveExpressionsToDelete = true;
|
|
|
|
DestroyColorPicker();
|
|
|
|
if( PreviewExpression == MaterialExpression )
|
|
{
|
|
// The expression being previewed is also being deleted
|
|
bPreviewExpressionDeleted = true;
|
|
}
|
|
|
|
if (UMaterialGraphNode_PinBase* PinBaseNode = Cast<UMaterialGraphNode_PinBase>(GraphNode))
|
|
{
|
|
UMaterialExpressionPinBase* PinBase = CastChecked<UMaterialExpressionPinBase>(MaterialExpression);
|
|
PinBase->DeleteReroutePins();
|
|
}
|
|
|
|
if (UMaterialGraphNode_Composite* CompositeNode = Cast<UMaterialGraphNode_Composite>(GraphNode))
|
|
{
|
|
CloseDocumentTab(CompositeNode->BoundGraph);
|
|
|
|
UMaterialExpressionComposite* SubgraphComposite = CastChecked<UMaterialExpressionComposite>(MaterialExpression);
|
|
SubgraphComposite->Modify();
|
|
|
|
// Remove all subgraph nodes, note that composites remove their subgraph in DestroyNode
|
|
const TArray<UEdGraphNode*> SubgraphNodesToDelete = CompositeNode->BoundGraph->Nodes;
|
|
DeleteNodesInternal(SubgraphNodesToDelete, bHaveExpressionsToDelete, bPreviewExpressionDeleted);
|
|
}
|
|
if(MaterialExpression)
|
|
{
|
|
MaterialExpression->Modify();
|
|
Material->GetExpressionCollection().RemoveExpression( MaterialExpression );
|
|
Material->RemoveExpressionParameter(MaterialExpression);
|
|
// Make sure the deleted expression is caught by gc
|
|
MaterialExpression->MarkAsGarbage();
|
|
}
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(NodesToDelete[Index]))
|
|
{
|
|
CommentNode->Modify();
|
|
CommentNode->BreakAllNodeLinks();
|
|
CommentNode->MaterialExpressionComment->Modify();
|
|
Material->GetExpressionCollection().RemoveComment( CommentNode->MaterialExpressionComment );
|
|
}
|
|
|
|
// Now that we are done with the node, remove it
|
|
FBlueprintEditorUtils::RemoveNode(NULL, NodesToDelete[Index], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::CopySelectedNodes()
|
|
{
|
|
// Export the selected nodes and place the text on the clipboard
|
|
const FGraphPanelSelectionSet SelectedNodes = 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 = 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()
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
PasteNodesHere(FocusedGraphEd->GetPasteLocation(), FocusedGraphEd->GetCurrentGraph());
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::PostPasteMaterialExpression(UMaterialExpression* NewExpression)
|
|
{
|
|
// Deep copy subgraph expressions
|
|
if (UMaterialExpressionComposite* Composite = Cast<UMaterialExpressionComposite>(NewExpression))
|
|
{
|
|
UMaterialGraphNode_Composite* CompositeNode = Cast<UMaterialGraphNode_Composite>(Composite->GraphNode);
|
|
DeepCopyExpressions(CompositeNode->BoundGraph, Composite);
|
|
|
|
// We just updated all our child expressions, reconstruct node.
|
|
Composite->GraphNode->ReconstructNode();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
NewExpression->GraphNode->Pins.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::PasteNodesHere(const FVector2D& Location, const class UEdGraph* Graph)
|
|
{
|
|
// Undo/Redo support
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorPaste", "Material Editor: Paste") );
|
|
Material->MaterialGraph->Modify();
|
|
Material->Modify();
|
|
|
|
UMaterialGraph* ExpressionGraph = Graph ? CastChecked<UMaterialGraph>(const_cast<UEdGraph*>(Graph)) : Material->MaterialGraph;
|
|
ExpressionGraph->Modify();
|
|
|
|
// Clear the selection set (newly pasted stuff will be selected)
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
}
|
|
|
|
// Grab the text to paste from the clipboard.
|
|
FString TextToImport;
|
|
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
|
|
|
|
// Import the nodes
|
|
TSet<UEdGraphNode*> PastedNodes;
|
|
FEdGraphUtilities::ImportNodesFromText(ExpressionGraph, 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;
|
|
}
|
|
|
|
TArray<UMaterialExpression*> NewMaterialExpressions;
|
|
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;
|
|
NewExpression->SubgraphExpression = ExpressionGraph->SubgraphExpression;
|
|
|
|
// Make sure the param name is valid after the paste
|
|
if (NewExpression->HasAParameterName())
|
|
{
|
|
NewExpression->ValidateParameterName();
|
|
}
|
|
|
|
NewMaterialExpressions.Add(NewExpression);
|
|
Material->GetExpressionCollection().AddExpression(NewExpression);
|
|
|
|
ensure(NewExpression->GraphNode == GraphNode);
|
|
PostPasteMaterialExpression(NewExpression);
|
|
}
|
|
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;
|
|
CommentNode->MaterialExpressionComment->SubgraphExpression = ExpressionGraph->SubgraphExpression;
|
|
Material->GetExpressionCollection().AddComment(CommentNode->MaterialExpressionComment);
|
|
}
|
|
}
|
|
|
|
// Select the newly pasted stuff
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->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();
|
|
}
|
|
|
|
for (auto* NewExpression : NewMaterialExpressions)
|
|
{
|
|
// For named reroute fixup: once all nodes are added to Material->Expressions, and that all their Material property are valid
|
|
NewExpression->PostCopyNode(NewMaterialExpressions);
|
|
}
|
|
|
|
UpdateMaterialAfterGraphChange();
|
|
|
|
// Update UI
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->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();
|
|
UpdateGenerator();
|
|
|
|
if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || bPreviewStaticSwitches)
|
|
{
|
|
bPreviewFeaturesChanged = true;
|
|
}
|
|
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ResetAllNodesUnrelatedStates();
|
|
}
|
|
|
|
HideUnrelatedNodes();
|
|
}
|
|
|
|
Material->MaterialGraph->UpdatePinTypes();
|
|
}
|
|
|
|
void FMaterialEditor::JumpToHyperlink(const UObject* ObjectReference)
|
|
{
|
|
if (const UEdGraphNode* Node = Cast<const UEdGraphNode>(ObjectReference))
|
|
{
|
|
JumpToNode(Node);
|
|
}
|
|
else if (const UEdGraph* Graph = Cast<const UEdGraph>(ObjectReference))
|
|
{
|
|
// Navigating into things should re-use the current tab when it makes sense
|
|
FDocumentTracker::EOpenDocumentCause OpenMode = FDocumentTracker::OpenNewDocument;
|
|
if ((Graph == Material->MaterialGraph) || Cast<UMaterialGraphNode_Composite>(Graph->GetOuter()))
|
|
{
|
|
OpenMode = FDocumentTracker::NavigatingCurrentDocument;
|
|
}
|
|
else
|
|
{
|
|
// Walk up the outer chain to see if any tabs have a parent of this document open for edit, and if so
|
|
// we should reuse that one and drill in deeper instead
|
|
for (UObject* WalkPtr = const_cast<UEdGraph*>(Graph); WalkPtr != nullptr; WalkPtr = WalkPtr->GetOuter())
|
|
{
|
|
TArray< TSharedPtr<SDockTab> > TabResults;
|
|
if (FindOpenTabsContainingDocument(WalkPtr, /*out*/ TabResults))
|
|
{
|
|
// See if the parent was active
|
|
bool bIsActive = false;
|
|
for (TSharedPtr<SDockTab> Tab : TabResults)
|
|
{
|
|
if (Tab->IsActive())
|
|
{
|
|
bIsActive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bIsActive)
|
|
{
|
|
OpenMode = FDocumentTracker::NavigatingCurrentDocument;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force it to open in a new document if shift is pressed
|
|
const bool bIsShiftPressed = FSlateApplication::Get().GetModifierKeys().IsShiftDown();
|
|
if (bIsShiftPressed)
|
|
{
|
|
auto PayloadAlreadyOpened = [&]()
|
|
{
|
|
TArray< TSharedPtr<SDockTab> > GraphEditorTabs;
|
|
DocumentManager->FindAllTabsForFactory(GraphEditorTabFactoryPtr, /*out*/ GraphEditorTabs);
|
|
|
|
for (TSharedPtr<SDockTab>& GraphEditorTab : GraphEditorTabs)
|
|
{
|
|
TSharedRef<SGraphEditor> Editor = StaticCastSharedRef<SGraphEditor>((GraphEditorTab)->GetContent());
|
|
|
|
if (Editor->GetCurrentGraph() == Graph)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
OpenMode = PayloadAlreadyOpened() ? FDocumentTracker::RestorePreviousDocument : FDocumentTracker::ForceOpenNewDocument;
|
|
}
|
|
|
|
// Open the document
|
|
OpenDocument(Graph, OpenMode);
|
|
}
|
|
}
|
|
|
|
int32 FMaterialEditor::GetNumberOfSelectedNodes() const
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
return FocusedGraphEd->GetSelectedNodes().Num();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FGraphPanelSelectionSet FMaterialEditor::GetSelectedNodes() const
|
|
{
|
|
FGraphPanelSelectionSet CurrentSelection;
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
CurrentSelection = FocusedGraphEdPtr.Pin()->GetSelectedNodes();
|
|
}
|
|
return CurrentSelection;
|
|
}
|
|
|
|
void FMaterialEditor::GetBoundsForNode(const UEdGraphNode* InNode, class FSlateRect& OutRect, float InPadding) const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->GetBoundsForNode(InNode, OutRect, InPadding);
|
|
}
|
|
}
|
|
|
|
|
|
FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression)
|
|
{
|
|
bool bNewlyCreated;
|
|
return GetExpressionPreview(InExpression, bNewlyCreated);
|
|
}
|
|
|
|
void FMaterialEditor::UndoGraphAction()
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
int32 NumExpressions = Material->GetExpressions().Num();
|
|
GEditor->UndoTransaction();
|
|
|
|
if(NumExpressions != Material->GetExpressions().Num())
|
|
{
|
|
Material->BuildEditorParameterList();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RedoGraphAction()
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
int32 NumExpressions = Material->GetExpressions().Num();
|
|
GEditor->RedoTransaction();
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
// @TODO Find a more coherent check for this, rather than rely on sage knowledge that the schema won't exist.
|
|
// If our previous transaction created the current graph, it won't have a schema after undo.
|
|
if (!IsValidChecked(FocusedGraphEd->GetCurrentGraph()))
|
|
{
|
|
CloseDocumentTab(FocusedGraphEd->GetCurrentGraph());
|
|
DocumentManager->CleanInvalidTabs();
|
|
DocumentManager->RefreshAllTabs();
|
|
GetTabManager()->TryInvokeTab(FMaterialEditorTabs::GraphEditor);
|
|
}
|
|
|
|
// Active graph can change above, re-acquire ptr
|
|
FocusedGraphEdPtr.Pin()->NotifyGraphChanged();
|
|
}
|
|
|
|
if(NumExpressions != Material->GetExpressions().Num())
|
|
{
|
|
Material->BuildEditorParameterList();
|
|
}
|
|
|
|
UpdateGenerator();
|
|
}
|
|
|
|
void FMaterialEditor::OnCollapseNodes()
|
|
{
|
|
const UMaterialGraphSchema* Schema = GetDefault<UMaterialGraphSchema>();
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
TSet<UEdGraphNode*> CollapsableNodes;
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*SelectedNode))
|
|
{
|
|
CollapsableNodes.Add(SelectedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort for deterministic pin ordering, unaffected by selection order.
|
|
CollapsableNodes.StableSort([](const UEdGraphNode& A, const UEdGraphNode& B)
|
|
{
|
|
if (A.NodePosY == B.NodePosY)
|
|
{
|
|
return A.NodePosX > B.NodePosX;
|
|
}
|
|
return A.NodePosY < B.NodePosY;
|
|
});
|
|
|
|
// Collapse them
|
|
if (CollapsableNodes.Num())
|
|
{
|
|
const FScopedTransaction Transaction(FGraphEditorCommands::Get().CollapseNodes->GetDescription());
|
|
Material->Modify();
|
|
Material->MaterialGraph->Modify();
|
|
|
|
CollapseNodes(CollapsableNodes);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanCollapseNodes() const
|
|
{
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
const UMaterialGraphSchema* Schema = GetDefault<UMaterialGraphSchema>();
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*Node))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::OnExpandNodes()
|
|
{
|
|
const FScopedTransaction Transaction(FGraphEditorCommands::Get().ExpandNodes->GetLabel());
|
|
Material->Modify();
|
|
Material->MaterialGraph->Modify();
|
|
|
|
TSet<UEdGraphNode*> ExpandedNodes;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
// Expand selected nodes into the focused graph context.
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if (FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
}
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
ExpandedNodes.Empty();
|
|
bool bExpandedNodesNeedUniqueGuid = true;
|
|
|
|
DocumentManager->CleanInvalidTabs();
|
|
|
|
if (UMaterialGraphNode_Composite* SelectedCompositeNode = Cast<UMaterialGraphNode_Composite>(*NodeIt))
|
|
{
|
|
// No need to assign unique GUIDs since the source graph will be removed.
|
|
bExpandedNodesNeedUniqueGuid = false;
|
|
|
|
// Expand the composite node back into the world
|
|
UEdGraph* SourceGraph = SelectedCompositeNode->BoundGraph;
|
|
ExpandNode(SelectedCompositeNode, SourceGraph, /*inout*/ ExpandedNodes);
|
|
|
|
FBlueprintEditorUtils::RemoveGraph(nullptr, SourceGraph, EGraphRemoveFlags::None);
|
|
SourceGraph->MarkAsGarbage();
|
|
}
|
|
|
|
UEdGraphNode* SourceNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
check(SourceNode);
|
|
MoveNodesToAveragePos(ExpandedNodes, FVector2D(SourceNode->NodePosX, SourceNode->NodePosY), bExpandedNodesNeedUniqueGuid);
|
|
}
|
|
|
|
UMaterialExpression* SubgraphExpression = FocusedGraphEd ? ToRawPtr(Cast<UMaterialGraph>(FocusedGraphEd->GetCurrentGraph())->SubgraphExpression) : ToRawPtr(Material->MaterialGraph->SubgraphExpression);
|
|
|
|
for (UEdGraphNode* ExpandedNode : ExpandedNodes)
|
|
{
|
|
if (UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(ExpandedNode))
|
|
{
|
|
MaterialNode->MaterialExpression->Modify();
|
|
MaterialNode->MaterialExpression->SubgraphExpression = SubgraphExpression;
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(ExpandedNode))
|
|
{
|
|
CommentNode->MaterialExpressionComment->Modify();
|
|
CommentNode->MaterialExpressionComment->SubgraphExpression = SubgraphExpression;
|
|
}
|
|
}
|
|
|
|
UpdateMaterialAfterGraphChange();
|
|
}
|
|
|
|
bool FMaterialEditor::CanExpandNodes() const
|
|
{
|
|
// Does the selection set contain any composite nodes that are legal to expand?
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (Cast<UMaterialGraphNode_Composite>(*NodeIt))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignTop()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignTop();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignMiddle()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignMiddle();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignBottom()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignBottom();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignLeft()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignLeft();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignCenter()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignCenter();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnAlignRight()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnAlignRight();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnStraightenConnections()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnStraightenConnections();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnDistributeNodesH()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnDistributeNodesH();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnDistributeNodesV()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->OnDistributeNodesV();
|
|
}
|
|
}
|
|
|
|
|
|
void FMaterialEditor::PostUndo(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
}
|
|
|
|
Material->BuildEditorParameterList();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
|
|
RefreshExpressionPreviews();
|
|
|
|
// Remove any tabs are that are pending kill or otherwise invalid UObject pointers.
|
|
bool bNeedOpenGraphEditor = false;
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
// @TODO Find a more coherent check for this, rather than rely on sage knowledge that the schema won't exist.
|
|
// If our previous transaction created the current graph, it won't have a schema after undo.
|
|
if (!FocusedGraphEd->GetCurrentGraph()->GetSchema())
|
|
{
|
|
CloseDocumentTab(FocusedGraphEd->GetCurrentGraph());
|
|
DocumentManager->CleanInvalidTabs();
|
|
DocumentManager->RefreshAllTabs();
|
|
GetTabManager()->TryInvokeTab(FMaterialEditorTabs::GraphEditor);
|
|
}
|
|
|
|
// Active graph can change above, re-acquire ptr
|
|
FocusedGraphEdPtr.Pin()->NotifyGraphChanged();
|
|
}
|
|
|
|
SetMaterialDirty();
|
|
|
|
UpdateGenerator();
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::NotifyPreChange(FProperty* PropertyAboutToChange)
|
|
{
|
|
check( !ScopedTransaction );
|
|
ScopedTransaction = new FScopedTransaction( NSLOCTEXT("UnrealEd", "MaterialEditorEditProperties", "Material Editor: Edit Properties") );
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged)
|
|
{
|
|
check( ScopedTransaction );
|
|
|
|
if ( PropertyThatChanged )
|
|
{
|
|
MaterialCustomPrimitiveDataWidget->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, bEnableExecWire))
|
|
{
|
|
Material->MaterialGraph->RebuildGraph();
|
|
}
|
|
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 );
|
|
SetPreviewMaterial(Material);
|
|
|
|
if (ExpressionPreviewMaterial)
|
|
{
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
|
|
}
|
|
else
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_Surface;
|
|
}
|
|
|
|
SetPreviewMaterial(ExpressionPreviewMaterial);
|
|
}
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
}
|
|
else if (bPreviewStaticSwitches &&
|
|
PropertyThatChanged->GetOwnerClass() == UMaterialExpressionStaticBoolParameter::StaticClass() &&
|
|
NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialExpressionStaticBoolParameter, DefaultValue))
|
|
{
|
|
bPreviewFeaturesChanged = true;
|
|
}
|
|
|
|
FGraphPanelSelectionSet SelectedNodes = 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 if (PropertyThatChanged->IsA<FTextProperty>())
|
|
{
|
|
// Do nothing to the expression if we are just changing the label
|
|
}
|
|
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)
|
|
&& PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionVectorParameter, ChannelNames)
|
|
&& PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionTextureSampleParameter, ChannelNames))
|
|
{
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
RefreshExpressionPreviews();
|
|
RegenerateCodeView();
|
|
}
|
|
|
|
GetDefault<UMaterialGraphSchema>()->ForceVisualizationCacheClear();
|
|
}
|
|
|
|
delete ScopedTransaction;
|
|
ScopedTransaction = NULL;
|
|
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
|
|
UpdateGenerator();
|
|
}
|
|
|
|
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;
|
|
|
|
Material->UpdateCachedExpressionData();
|
|
|
|
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.
|
|
FMaterial::DeferredDeleteArray(ExpressionPreviews);
|
|
|
|
for (UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(MaterialExpression->GraphNode);
|
|
if (GraphNode)
|
|
{
|
|
GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Only refresh expressions that are marked for realtime update.
|
|
for (UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
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 (UMaterialExpression* MaterialExpression : Material->GetExpressions())
|
|
{
|
|
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 we don't hold any other references before queuing the deferred delete as it may execute immediately (e.g. -onethread, or just very fast RT).
|
|
// There's no danger in releasing the last reference to FMaterial as this doesn't delete it anyway, it needs to be deleted manually via DeferredDelete.
|
|
ExpressionPreviews.RemoveAt(PreviewIndex);
|
|
FMaterial::DeferredDelete(ExpressionPreview);
|
|
ExpressionPreview = nullptr;
|
|
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 && !MaterialExpression->IsA<UMaterialExpressionCustomOutput>())
|
|
{
|
|
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, EMaterialShaderPrecompileMode::None);
|
|
}
|
|
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();
|
|
MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance);
|
|
}
|
|
|
|
/** Create new tab for the supplied graph - don't call this directly, instead call OpenDocument to track history.*/
|
|
TSharedRef<SGraphEditor> FMaterialEditor::CreateGraphEditorWidget(TSharedRef<class FTabInfo> InTabInfo, class UEdGraph* InGraph)
|
|
{
|
|
check((InGraph != nullptr) && Cast<UMaterialGraph>(InGraph));
|
|
|
|
if (!GraphEditorCommands)
|
|
{
|
|
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 )
|
|
);
|
|
GraphEditorCommands->MapAction(FGenericCommands::Get().Rename,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnRenameNode),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::CanRenameNodes)
|
|
);
|
|
|
|
// 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().PromoteToDouble,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().PromoteToFloat,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects)
|
|
);
|
|
|
|
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().SelectNamedRerouteDeclaration,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteDeclaration)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectNamedRerouteUsages,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteUsages)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertRerouteToNamedReroute,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertRerouteToNamedReroute)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertNamedRerouteToReroute,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertNamedRerouteToReroute)
|
|
);
|
|
|
|
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)
|
|
);
|
|
|
|
// Collapse Node Commands
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CollapseNodes,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::OnCollapseNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCollapseNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ExpandNodes,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::OnExpandNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanExpandNodes ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FMaterialEditor::CanExpandNodes )
|
|
);
|
|
|
|
// 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*>(InGraph));
|
|
|
|
// Create the title bar widget
|
|
TSharedPtr<SWidget> TitleBarWidget = SNew(SMaterialEditorTitleBar)
|
|
.EdGraphObj(InGraph)
|
|
.TitleText(this, &FMaterialEditor::GetOriginalObjectName)
|
|
.OnDifferentGraphCrumbClicked(this, &FMaterialEditor::OnChangeBreadCrumbGraph)
|
|
.HistoryNavigationWidget(InTabInfo->CreateHistoryNavigationWidget())
|
|
.MaterialInfoList(&MaterialInfoList);
|
|
|
|
return SNew(SGraphEditor)
|
|
.AdditionalCommands(GraphEditorCommands)
|
|
.IsEditable(true)
|
|
.TitleBar(TitleBarWidget)
|
|
.Appearance(this, &FMaterialEditor::GetGraphAppearance)
|
|
.GraphToEdit(InGraph)
|
|
.GraphEvents(InEvents)
|
|
.ShowGraphStateOverlay(false)
|
|
.OnNavigateHistoryBack(FSimpleDelegate::CreateSP(this, &FMaterialEditor::NavigateTab, FDocumentTracker::NavigateBackwards))
|
|
.OnNavigateHistoryForward(FSimpleDelegate::CreateSP(this, &FMaterialEditor::NavigateTab, FDocumentTracker::NavigateForwards))
|
|
.AssetEditorToolkit(this->AsShared());
|
|
}
|
|
|
|
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::DeepCopyExpressions(UMaterialGraph* CopyGraph, UMaterialExpression* NewSubgraphExpression)
|
|
{
|
|
if (!CopyGraph || !NewSubgraphExpression)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CopyGraph->Modify();
|
|
CopyGraph->SubgraphExpression = NewSubgraphExpression;
|
|
|
|
// Duplicate subnodes
|
|
auto DuplicateExpression = [&](auto* Expression)
|
|
{
|
|
using ExpressionType = typename std::remove_pointer<decltype(Expression)>::type;
|
|
return Cast<ExpressionType>(UMaterialEditingLibrary::DuplicateMaterialExpression(Material, MaterialFunction, Expression));
|
|
};
|
|
|
|
for (UEdGraphNode* Node : CopyGraph->Nodes)
|
|
{
|
|
if (UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Node))
|
|
{
|
|
MaterialNode->Modify();
|
|
MaterialNode->Rename(/*NewName=*/ NULL, /*NewOuter=*/ CopyGraph);
|
|
if (UMaterialExpressionPinBase* PinBase = Cast<UMaterialExpressionPinBase>(MaterialNode->MaterialExpression))
|
|
{
|
|
UMaterialExpressionPinBase* OldPinBase = PinBase;
|
|
UMaterialExpressionPinBase* NewPinBase = DuplicateExpression(OldPinBase);
|
|
MaterialNode->MaterialExpression = NewPinBase;
|
|
|
|
NewPinBase->SubgraphExpression = NewSubgraphExpression;
|
|
NewPinBase->ReroutePins.Empty();
|
|
for (FCompositeReroute& Reroute : OldPinBase->ReroutePins)
|
|
{
|
|
UMaterialExpressionReroute* DupReroute = DuplicateExpression(ToRawPtr(Reroute.Expression));
|
|
DupReroute->SubgraphExpression = NewSubgraphExpression;
|
|
NewPinBase->ReroutePins.Add({ Reroute.Name, decltype(FCompositeReroute::Expression)(DupReroute) });
|
|
}
|
|
|
|
UMaterialExpressionComposite* SubGraphComposite = CastChecked<UMaterialExpressionComposite>(NewSubgraphExpression);
|
|
if (NewPinBase->PinDirection == EGPD_Output)
|
|
{
|
|
SubGraphComposite->InputExpressions = NewPinBase;
|
|
}
|
|
else
|
|
{
|
|
SubGraphComposite->OutputExpressions = NewPinBase;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MaterialNode->MaterialExpression = DuplicateExpression(ToRawPtr(MaterialNode->MaterialExpression));
|
|
MaterialNode->MaterialExpression->SubgraphExpression = NewSubgraphExpression;
|
|
}
|
|
|
|
// GraphNode is transient so it won't be duplicated.
|
|
MaterialNode->MaterialExpression->GraphNode = MaterialNode;
|
|
PostPasteMaterialExpression(MaterialNode->MaterialExpression);
|
|
|
|
}
|
|
else if (UMaterialGraphNode_Comment* Comment = Cast<UMaterialGraphNode_Comment>(MaterialNode))
|
|
{
|
|
Comment->Modify();
|
|
Comment->Rename(/*NewName=*/ NULL, /*NewOuter=*/ CopyGraph);
|
|
Comment->MaterialExpressionComment = DuplicateExpression(ToRawPtr(Comment->MaterialExpressionComment));
|
|
Comment->MaterialExpressionComment->SubgraphExpression = NewSubgraphExpression;
|
|
|
|
// GraphNode is transient so it won't be duplicated.
|
|
Comment->MaterialExpressionComment->GraphNode = Comment;
|
|
}
|
|
}
|
|
}
|
|
|
|
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->GetExpressionCollection().RemoveExpression(MaterialExpression);
|
|
Material->RemoveExpressionParameter(MaterialExpression);
|
|
// Make sure the deleted expression is caught by gc
|
|
MaterialExpression->MarkAsGarbage();
|
|
}
|
|
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
} // ScopedTransaction
|
|
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
FocusedGraphEd->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 = 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 = 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::UpdateDetailView()
|
|
{
|
|
GetDetailView()->InvalidateCachedState();
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
|
|
{
|
|
TArray<UObject*> SelectedObjects;
|
|
|
|
UObject* EditObject = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
EditObject = MaterialFunction;
|
|
}
|
|
|
|
bSelectRegularNode = false;
|
|
if( NewSelection.Num() == 0 )
|
|
{
|
|
SelectedObjects.Add(EditObject);
|
|
}
|
|
else
|
|
{
|
|
for(TSet<class UObject*>::TConstIterator SetIt(NewSelection);SetIt;++SetIt)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*SetIt))
|
|
{
|
|
bSelectRegularNode = true;
|
|
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();
|
|
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState)
|
|
{
|
|
if (TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin())
|
|
{
|
|
FocusedGraphEd->ResetAllNodesUnrelatedStates();
|
|
}
|
|
|
|
if (bSelectRegularNode)
|
|
{
|
|
HideUnrelatedNodes();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(Node);
|
|
|
|
if (Node && Node->CanJumpToDefinition())
|
|
{
|
|
Node->JumpToDefinition();
|
|
}
|
|
else if (GraphNode && GraphNode->MaterialExpression)
|
|
{
|
|
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;
|
|
static FName ConstantName = FName(TEXT("Constant"));
|
|
ColorPickerProperty = Constant3Expression->GetClass()->FindPropertyByName(ConstantName);
|
|
}
|
|
else if( Constant4Expression )
|
|
{
|
|
ChannelEditStruct.Red = &Constant4Expression->Constant.R;
|
|
ChannelEditStruct.Green = &Constant4Expression->Constant.G;
|
|
ChannelEditStruct.Blue = &Constant4Expression->Constant.B;
|
|
ChannelEditStruct.Alpha = &Constant4Expression->Constant.A;
|
|
static FName ConstantName = FName(TEXT("Constant"));
|
|
ColorPickerProperty = Constant4Expression->GetClass()->FindPropertyByName(ConstantName);
|
|
}
|
|
else if (InputExpression)
|
|
{
|
|
// LWC_TODO: FIX THIS. Can't keep a pointer as float to a double type.
|
|
//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 = FocusedGraphEdPtr.Pin();
|
|
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)
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(ObjectToEdit);
|
|
}
|
|
|
|
// Double click actions for named reroute nodes
|
|
if (GraphNode->MaterialExpression->IsA<UMaterialExpressionNamedRerouteDeclaration>())
|
|
{
|
|
OnSelectNamedRerouteUsages();
|
|
}
|
|
else if (GraphNode->MaterialExpression->IsA<UMaterialExpressionNamedRerouteUsage>())
|
|
{
|
|
OnSelectNamedRerouteDeclaration();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnRenameNode()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt);
|
|
if (SelectedNode != nullptr && SelectedNode->GetCanRenameNode())
|
|
{
|
|
bool ToRename = true;
|
|
FocusedGraphEd->IsNodeTitleVisible(SelectedNode, ToRename);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanRenameNodes() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != nullptr) && Node->GetCanRenameNode())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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());
|
|
UpdateGenerator();
|
|
MaterialCustomPrimitiveDataWidget->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;
|
|
if (FMaterialEditorSpawnNodeCommands::IsRegistered())
|
|
{
|
|
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->GetExpressionCollection().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();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RebuildInheritanceList()
|
|
{
|
|
if (!MaterialFunction)
|
|
{
|
|
MaterialChildList.Empty();
|
|
|
|
UMaterialEditingLibrary::GetChildInstances(OriginalMaterial, MaterialChildList);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|