You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3134663 on 2016/09/21 by Chris.Bunner Merging Dev-MaterialLayers to Dev-Rendering, CL 3134208. Initial material attribute extensibility changes. #jira UE-34347 Change 3142292 on 2016/09/27 by Rolando.Caloca DR - hlslcc - Fix for warning X3206: implicit truncation of vector type causing error #jira UE-31438 Change 3143557 on 2016/09/28 by Rolando.Caloca DR - Back out changelist 3142292 Change 3145354 on 2016/09/29 by Benjamin.Hyder Updating Tm-ContactShadows Change 3154832 on 2016/10/07 by Rolando.Caloca DR - vk - Fix crash on framebuffers with missing textures Change 3154838 on 2016/10/07 by Rolando.Caloca DR - vk - Enable clip distance Change 3154840 on 2016/10/07 by Rolando.Caloca DR - Remove branch per codereview Change 3155118 on 2016/10/07 by Rolando.Caloca DR - vk - Compute pipeline fixes Change 3155129 on 2016/10/07 by Rolando.Caloca DR - Added draw events for reflection captures Change 3155167 on 2016/10/07 by Rolando.Caloca DR - Use shader clear for platforms that can't use viewport or scissor Change 3155168 on 2016/10/07 by Rolando.Caloca DR - vk - Added submit gpu - Some fixes for Geometry and Compute Change 3155595 on 2016/10/07 by Rolando.Caloca DR - vk - Use new render pass system Change 3155720 on 2016/10/07 by Rolando.Caloca DR - vk - static analysis fix Change 3155732 on 2016/10/07 by Rolando.Caloca DR - Fix clears for platforms that can't use viewports, excluderects or scissor on clear Change 3156787 on 2016/10/10 by Rolando.Caloca DR - Fix mem leaks Change 3156805 on 2016/10/10 by Rolando.Caloca DR - Improve check msg per licensee Change 3156815 on 2016/10/10 by Rolando.Caloca DR - Fix infinite recursion Change 3157041 on 2016/10/10 by Rolando.Caloca DR - vk - Fix key access from multiple threads Change 3158253 on 2016/10/11 by Rolando.Caloca DR - Fix comment #jira UE-37128 PR #2852 Change 3158606 on 2016/10/11 by Rolando.Caloca DR - vk - Accessors Change 3160418 on 2016/10/12 by Daniel.Wright Lightmap textures are now outered to UMapBuildDataRegistry so that the UMapBuildDataRegistry can be moved in the content browser Change 3160644 on 2016/10/12 by Arne.Schober DR - [UE-32613] - OpenGL used to have custom code in the compiler to modify the source so that the same data and matricies can be used as DirectX, unfortunately that causes precission problem. Fortunately there is an extension available (glClipControl) which enables DirectX behaviour in OpenGL and it is widely supported. We only tested Linux and Windows and therfore only default enable on those platforms. Change 3161219 on 2016/10/13 by Luke.Thatcher [RENDERING] [!] Fix incorrect shader used in GPU Benchmark causing crash in OpenGL. Change 3161838 on 2016/10/13 by Daniel.Wright Fixed level getting added to the dirty list twice when legacy lightmaps are present Change3161884on 2016/10/13 by Arne.Schober DR - Fix Mac and DCC build Change 3162206 on 2016/10/13 by Chris.Bunner Merging Dev-MaterialLayers to Dev-Rendering, CL 3161593: Material expressions; Trig, fast-trig, saturate, round, truncate, pre-skinned normal. Added CustomEyeTangent to material attributes. Resolved some hard-coded attribute typing and other minor fixes. Change 3162491 on 2016/10/13 by Chris.Bunner Merging Dev-MaterialLayers to Dev-Rendering, CL 3162397: More fixed type-casting on material attributes. Swapped compiler::forcecast booleans to flags (and fixed a regression). Change 3163266 on 2016/10/14 by Daniel.Wright Fixed sublevels with legacy lighting data being added to the dirty packages list redundantly Change 3163524 on 2016/10/14 by Mark.Satterthwaite Bring over specific changes from Unicorn branch that increases the size of shader optional data so that it is considerably more useful. Change 3163529 on 2016/10/14 by Mark.Satterthwaite Move the Metal shader source code and compilation path into the newly enlarged shader optional data. Change3163553on 2016/10/14 by Mark.Satterthwaite Speculative fix for FORT-31590 also seen by a licensee - the Metal command buffer handler will be called from a dispatch queue thread that won't be registered with the stats system. #jira FORT-31590 Change 3163562 on 2016/10/14 by Mark.Satterthwaite Tidy up and extend the Metal debugging options: - Added rhi.Metal.BufferScribble which when enabled will fill freed buffer regions with 0xCD to help identify any areas where we are writing to a buffer while it is still being processed on the GPU. - Added rhi.Metal.BufferZeroFill which will zero-fill newly allocated buffer regions before any other data is read/written. Useful for catching cases where we might be reading uninitialised memory. - Added rhi.Metal.ResourcePurgeOnDelete which will purge the backing store of resources prior to releasing them back to the system or the respective pool. This will make any use-after-free conditions much more likely. - Added rhi.Metal.ResourceDeferDeleteNumFrames to defer releasing resources to the system or the resource pool by the specified number of frames (in addition to the current policy of waiting for the current end of frame & command-buffer completion). Useful for tracking down resource lifetime errors. - Fixed a number of bugs related to the modifications to vertex stream handling and addition of the SetShaderBytes API. - Track the start & end of FRingBuffer ranges - it appeared that the ring-buffer usage was invalid but it was in fact only my assumptions about the range that needed to be scribbled for rhi.Metal.BufferScribble. There is still the possibility that command-buffers that are implicitly parallelised by the driver may cause the ring-buffer range tracking to go awry - but with our data dependencies and the separation of the async. compute context I don't believe this is likely. - Fix up the "nometalv2" flag so that we can disable the features only available on iOS/tvOS-10/macOS-10.12 on newer devices to save having to reboot all the time. - Fixed the flickering geometry when enabling rhi.Metal.RuntimeDebugLevel=4 which breaks render passes into separate command-buffers - the occlusion query was waiting on the wrong command buffer in this case. Change 3163752 on 2016/10/14 by Mark.Satterthwaite Add missing parenthesis to fix compile error on iOS. Change 3164151 on 2016/10/16 by Benjamin.Hyder Submitting TM-AutoLOD level to QAGame #jira UE-29618 Change 3164190 on 2016/10/16 by Uriel.Doyon Materials now hold texture streaming data in the form of (UV scale X UV channel) for each texture. This data can be disabled through "r.Streaming.UseMaterialData" Defined a common framework in MeshComponent for texture streaming, used by both StaticMeshes and SkeletalMeshes. Simplified component interface for using the texture streaming build framework. Removed intermediate texture streaming build data from the static mesh components. Fixed shader compilation errors with the decals (from merge with main). Change 3164636 on 2016/10/17 by Rolando.Caloca DR - vk - Fix validation spam Change 3164679 on 2016/10/17 by Arne.Schober DR - [OR-28457] Part1, Scene View Refactoring - Removed Previous VewMatrices from SceneInfo and pass in Previous and Current ViewMatrices into Uniform Buffer creation to uniform UseCase for Shadows and CustomDepth, Fixed a Bug in Shadows with help of Daniel where the SceneView was copied unnecessary copied again. Also simplified the code in that area. Change 3164705 on 2016/10/17 by Daniel.Wright When new levels are loaded, only the Indirect Lighting Cache Allocations intersecting the level's light probes are updated to minimize hitches. This optimization requires a lighting build to compute PrecomputedLightVolume bounds. Change 3164834 on 2016/10/17 by Daniel.Wright Support directional light dynamic shadows in any channel with forward shading, which can happen with multiple shadow casting stationary directional lights (even though only the lighting of one will appear) Change 3164870 on 2016/10/17 by Arne.Schober DR - [OR-28457] Part2, Custom Depth Jitter - Allowed to overwite the viewconstant buffer in the custom depth pass. There ia also a new Project Setting available. The default constructor of the ContextDataType has been explicitly deleted to enforce compile errors when the templated code like the StaticMeshDrawList accidently tries to create a context without ViewUniformBuffer. Change 3164949 on 2016/10/17 by Rolando.Caloca DR - vk - First version of pooled occlusion queries Change 3165100 on 2016/10/17 by Rolando.Caloca DR - vk - Added driver version for Nvidia. AMD doesn't have one yet. Change 3165160 on 2016/10/17 by Rolando.Caloca DR - vk - Fix for queries not ready Change 3165230 on 2016/10/17 by Rolando.Caloca DR - vk - More fixes for occlusion queries Change 3165839 on 2016/10/18 by Rolando.Caloca DR - hlslcc - Fix default parameters getting wrong values Change 3166029 on 2016/10/18 by Rolando.Caloca DR - Switch some clears to DrawClearQuad() Change 3166066 on 2016/10/18 by Mark.Satterthwaite Update ShaderVersion due to CL #3163524 Change 3166067 on 2016/10/18 by Mark.Satterthwaite Update Mac hlslcc for RCO's 3165839. Change 3166370 on 2016/10/18 by Brian.Karis Improved hair AA Change 3166389 on 2016/10/18 by Uriel.Doyon Fixed lightmap having bigger resolutions than the engine can handle #jira UE-34737 #review-3166193 @daniel.wright Change 3166495 on 2016/10/18 by Rolando.Caloca DR - vk - Fix occlusion queries Change 3166516 on 2016/10/18 by Arne.Schober DR - Fix shaderbuild issue Change 3166650 on 2016/10/18 by Rolando.Caloca DR - vk - Enable GRHISupportsFirstInstance Change 3166799 on 2016/10/18 by Arne.Schober DR - [OR-28508] - The velocity Rendering pass was missing the adjustment for the PDO Change 3167855 on 2016/10/19 by Rolando.Caloca DR - vk - Implemented texture streaming Change 3168365 on 2016/10/19 by Rolando.Caloca DR - Fix static analysis Change 3168405 on 2016/10/19 by Mark.Satterthwaite Fix the optional shader data changes from Unicorn to prevent FindOptionalData from erronesouly testing against the trailing optional data size, which can match the tag for optional data entries if you are unlucky. #jira UE-37489 Change 3169467 on 2016/10/20 by Arne.Schober DR - UE-28039 - Fixed flickering cached shadows on dynamic objects: Adding preshadows whose depths are cached so that GatherDynamicMeshElements will still happen, which is necessary for preshadow receiver stenciling. Change 3169478 on 2016/10/20 by Arne.Schober DR - UE-28039 - missing comment Change 3169845 on 2016/10/20 by Arne.Schober DR - UE-35937 - readd Merged out check Change 3169859 on 2016/10/20 by Rolando.Caloca DR - vk - Stop popping up dialog on every run as the device name in the API doesn't match our driver database [CL 3170066 by Marcus Wassmer in Main branch]
4301 lines
141 KiB
C++
4301 lines
141 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MaterialEditorModule.h"
|
|
|
|
#include "MaterialGraph/MaterialGraphNode_Comment.h"
|
|
|
|
#include "Materials/MaterialExpressionBreakMaterialAttributes.h"
|
|
#include "Materials/MaterialExpressionCollectionParameter.h"
|
|
#include "Materials/MaterialExpressionComment.h"
|
|
#include "Materials/MaterialExpressionComponentMask.h"
|
|
#include "Materials/MaterialExpressionConstant.h"
|
|
#include "Materials/MaterialExpressionConstant2Vector.h"
|
|
#include "Materials/MaterialExpressionConstant3Vector.h"
|
|
#include "Materials/MaterialExpressionConstant4Vector.h"
|
|
#include "Materials/MaterialExpressionDynamicParameter.h"
|
|
#include "Materials/MaterialExpressionFontSampleParameter.h"
|
|
#include "Materials/MaterialExpressionFunctionInput.h"
|
|
#include "Materials/MaterialExpressionFunctionOutput.h"
|
|
#include "Materials/MaterialExpressionParameter.h"
|
|
#include "Materials/MaterialExpressionParticleSubUV.h"
|
|
#include "Materials/MaterialExpressionRotateAboutAxis.h"
|
|
#include "Materials/MaterialExpressionScalarParameter.h"
|
|
#include "Materials/MaterialExpressionStaticComponentMaskParameter.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter.h"
|
|
#include "Materials/MaterialExpressionTextureObject.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameter2D.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameterCube.h"
|
|
#include "Materials/MaterialExpressionTextureSampleParameterSubUV.h"
|
|
#include "Materials/MaterialExpressionTransformPosition.h"
|
|
#include "Materials/MaterialExpressionVectorParameter.h"
|
|
#include "Materials/MaterialFunction.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
|
|
#include "MaterialEditorActions.h"
|
|
#include "MaterialExpressionClasses.h"
|
|
#include "MaterialCompiler.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "Toolkits/IToolkitHost.h"
|
|
#include "Editor/EditorWidgets/Public/EditorWidgets.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "SMaterialEditorViewport.h"
|
|
#include "SMaterialEditorTitleBar.h"
|
|
#include "PreviewScene.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "BusyCursor.h"
|
|
|
|
#include "Editor/PropertyEditor/Public/PropertyEditorModule.h"
|
|
#include "Editor/PropertyEditor/Public/IDetailsView.h"
|
|
#include "MaterialEditorDetailCustomization.h"
|
|
#include "MaterialInstanceEditor.h"
|
|
|
|
#include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h"
|
|
#include "EditorViewportCommands.h"
|
|
|
|
#include "GraphEditor.h"
|
|
#include "GraphEditorActions.h"
|
|
#include "BlueprintEditorUtils.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "SNodePanel.h"
|
|
#include "MaterialEditorUtilities.h"
|
|
#include "SMaterialPalette.h"
|
|
#include "FindInMaterial.h"
|
|
#include "SColorPicker.h"
|
|
#include "EditorClassUtils.h"
|
|
#include "IDocumentation.h"
|
|
#include "SDockTab.h"
|
|
|
|
#include "Developer/MessageLog/Public/MessageLogModule.h"
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
#include "GenericCommands.h"
|
|
#include "CanvasTypes.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Engine/TextureCube.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MaterialEditor"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditor, Log, All);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaterialEdUseDevShaders(
|
|
TEXT("r.MaterialEditor.UseDevShaders"),
|
|
0,
|
|
TEXT("Toggles whether the material editor will use shaders that include extra overhead incurred by the editor. Material editor must be re-opened if changed at runtime."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
const FName FMaterialEditor::PreviewTabId( TEXT( "MaterialEditor_Preview" ) );
|
|
const FName FMaterialEditor::GraphCanvasTabId( TEXT( "MaterialEditor_GraphCanvas" ) );
|
|
const FName FMaterialEditor::PropertiesTabId( TEXT( "MaterialEditor_MaterialProperties" ) );
|
|
const FName FMaterialEditor::HLSLCodeTabId( TEXT( "MaterialEditor_HLSLCode" ) );
|
|
const FName FMaterialEditor::PaletteTabId( TEXT( "MaterialEditor_Palette" ) );
|
|
const FName FMaterialEditor::StatsTabId( TEXT( "MaterialEditor_Stats" ) );
|
|
const FName FMaterialEditor::FindTabId( TEXT( "MaterialEditor_Find" ) );
|
|
|
|
///////////////////////////
|
|
// FMatExpressionPreview //
|
|
///////////////////////////
|
|
|
|
bool FMatExpressionPreview::ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const
|
|
{
|
|
if(VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLocalVertexFactory"), FNAME_Find)))
|
|
{
|
|
// we only need the non-light-mapped, base pass, local vertex factory shaders for drawing an opaque Material Tile
|
|
// @todo: Added a FindShaderType by fname or something"
|
|
|
|
if (IsMobilePlatform(Platform))
|
|
{
|
|
if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingVSFNoLightMapPolicy")) ||
|
|
FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingPSFNoLightMapPolicy")))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassVSFNoLightMapPolicy")) ||
|
|
FCString::Stristr(ShaderType->GetName(), TEXT("BasePassHSFNoLightMapPolicy")) ||
|
|
FCString::Stristr(ShaderType->GetName(), TEXT("BasePassDSFNoLightMapPolicy")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSFNoLightMapPolicy")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (FCString::Stristr(ShaderType->GetName(), TEXT("Simple")))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int32 FMatExpressionPreview::CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const
|
|
{
|
|
// needs to be called in this function!!
|
|
Compiler->SetMaterialProperty(Property, OverrideShaderFrequency, bUsePreviousFrameTime);
|
|
|
|
int32 Ret = INDEX_NONE;
|
|
|
|
if( Property == MP_EmissiveColor && Expression.IsValid())
|
|
{
|
|
// Hardcoding output 0 as we don't have the UI to specify any other output
|
|
const int32 OutputIndex = 0;
|
|
// Get back into gamma corrected space, as DrawTile does not do this adjustment.
|
|
Ret = Compiler->Power(Compiler->Max(Expression->CompilePreview(Compiler, OutputIndex), Compiler->Constant(0)), Compiler->Constant(1.f / 2.2f));
|
|
}
|
|
else if (Property == MP_WorldPositionOffset)
|
|
{
|
|
//set to 0 to prevent off by 1 pixel errors
|
|
Ret = Compiler->Constant(0.0f);
|
|
}
|
|
else if (Property >= MP_CustomizedUVs0 && Property <= MP_CustomizedUVs7)
|
|
{
|
|
const int32 TextureCoordinateIndex = Property - MP_CustomizedUVs0;
|
|
Ret = Compiler->TextureCoordinate(TextureCoordinateIndex, false, false);
|
|
}
|
|
else
|
|
{
|
|
Ret = Compiler->Constant(1.0f);
|
|
}
|
|
|
|
// output should always be the right type for this property
|
|
return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property));
|
|
}
|
|
|
|
void FMatExpressionPreview::NotifyCompilationFinished()
|
|
{
|
|
if (Expression.IsValid() && Expression->GraphNode)
|
|
{
|
|
CastChecked<UMaterialGraphNode>(Expression->GraphNode)->bPreviewNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/////////////////////
|
|
// FMaterialEditor //
|
|
/////////////////////
|
|
|
|
void FMaterialEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->RegisterTabSpawner( PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) )
|
|
.SetDisplayName( LOCTEXT("ViewportTab", "Viewport") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner( GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_GraphCanvas) )
|
|
.SetDisplayName( LOCTEXT("GraphCanvasTab", "Graph") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x"));
|
|
|
|
InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) )
|
|
.SetDisplayName( LOCTEXT("DetailsTab", "Details") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner( PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) )
|
|
.SetDisplayName( LOCTEXT("PaletteTab", "Palette") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Palette"));
|
|
|
|
InTabManager->RegisterTabSpawner( StatsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Stats) )
|
|
.SetDisplayName( LOCTEXT("StatsTab", "Stats") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.StatsViewer"));
|
|
|
|
InTabManager->RegisterTabSpawner(FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find))
|
|
.SetDisplayName(LOCTEXT("FindTab", "Find Results"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.FindResults"));
|
|
|
|
InTabManager->RegisterTabSpawner( HLSLCodeTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_HLSLCode) )
|
|
.SetDisplayName( LOCTEXT("HLSLCodeTab", "HLSL Code") )
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "MaterialEditor.Tabs.HLSLCode"));
|
|
|
|
OnRegisterTabSpawners().Broadcast(InTabManager);
|
|
}
|
|
|
|
|
|
void FMaterialEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->UnregisterTabSpawner( PreviewTabId );
|
|
InTabManager->UnregisterTabSpawner( GraphCanvasTabId );
|
|
InTabManager->UnregisterTabSpawner( PropertiesTabId );
|
|
InTabManager->UnregisterTabSpawner( PaletteTabId );
|
|
InTabManager->UnregisterTabSpawner( StatsTabId );
|
|
InTabManager->UnregisterTabSpawner( FindTabId );
|
|
InTabManager->UnregisterTabSpawner( HLSLCodeTabId );
|
|
|
|
OnUnregisterTabSpawners().Broadcast(InTabManager);
|
|
}
|
|
|
|
void FMaterialEditor::InitEditorForMaterial(UMaterial* InMaterial)
|
|
{
|
|
check(InMaterial);
|
|
|
|
OriginalMaterial = InMaterial;
|
|
MaterialFunction = NULL;
|
|
OriginalMaterialObject = InMaterial;
|
|
|
|
ExpressionPreviewMaterial = NULL;
|
|
|
|
// Create a copy of the material for preview usage (duplicating to a different class than original!)
|
|
// Propagate all object flags except for RF_Standalone, otherwise the preview material won't GC once
|
|
// the material editor releases the reference.
|
|
Material = (UMaterial*)StaticDuplicateObject(OriginalMaterial, GetTransientPackage(), NAME_None, ~RF_Standalone, UPreviewMaterial::StaticClass());
|
|
|
|
Material->CancelOutstandingCompilation(); //The material is compiled later on anyway so no need to do it in Duplication/PostLoad.
|
|
//I'm hackily canceling the jobs here but we should really not add the jobs in the first place. <<--- TODO
|
|
|
|
Material->bAllowDevelopmentShaderCompile = CVarMaterialEdUseDevShaders.GetValueOnGameThread();
|
|
|
|
// Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid
|
|
// This can happen if an expression class was removed
|
|
for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
if (!Material->Expressions[ExpressionIndex])
|
|
{
|
|
Material->Expressions.RemoveAt(ExpressionIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit )
|
|
{
|
|
EditorOptions = NULL;
|
|
bMaterialDirty = false;
|
|
bStatsFromPreviewMaterial = false;
|
|
ColorPickerObject = NULL;
|
|
|
|
// Support undo/redo
|
|
Material->SetFlags(RF_Transactional);
|
|
|
|
GEditor->RegisterForUndo(this);
|
|
|
|
if (!Material->MaterialGraph)
|
|
{
|
|
Material->MaterialGraph = CastChecked<UMaterialGraph>(FBlueprintEditorUtils::CreateNewGraph(Material, NAME_None, UMaterialGraph::StaticClass(), UMaterialGraphSchema::StaticClass()));
|
|
}
|
|
Material->MaterialGraph->Material = Material;
|
|
Material->MaterialGraph->MaterialFunction = MaterialFunction;
|
|
Material->MaterialGraph->RealtimeDelegate.BindSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked);
|
|
Material->MaterialGraph->MaterialDirtyDelegate.BindSP(this, &FMaterialEditor::SetMaterialDirty);
|
|
Material->MaterialGraph->ToggleCollapsedDelegate.BindSP(this, &FMaterialEditor::ToggleCollapsed);
|
|
|
|
// copy material usage
|
|
for( int32 Usage=0; Usage < MATUSAGE_MAX; Usage++ )
|
|
{
|
|
const EMaterialUsage UsageEnum = (EMaterialUsage)Usage;
|
|
if( OriginalMaterial->GetUsageByFlag(UsageEnum) )
|
|
{
|
|
bool bNeedsRecompile=false;
|
|
Material->SetMaterialUsage(bNeedsRecompile,UsageEnum);
|
|
}
|
|
}
|
|
// Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials
|
|
Material->bUsedAsSpecialEngineMaterial = OriginalMaterial->bUsedAsSpecialEngineMaterial;
|
|
|
|
// Register our commands. This will only register them if not previously registered
|
|
FGraphEditorCommands::Register();
|
|
FMaterialEditorCommands::Register();
|
|
FMaterialEditorSpawnNodeCommands::Register();
|
|
|
|
FEditorSupportDelegates::MaterialUsageFlagsChanged.AddRaw(this, &FMaterialEditor::OnMaterialUsageFlagsChanged);
|
|
FEditorSupportDelegates::VectorParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnVectorParameterDefaultChanged);
|
|
FEditorSupportDelegates::ScalarParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnScalarParameterDefaultChanged);
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
AssetRegistryModule.Get().OnAssetRenamed().AddSP( this, &FMaterialEditor::RenameAssetFromRegistry );
|
|
|
|
CreateInternalWidgets();
|
|
|
|
// Do setup previously done in SMaterialEditorCanvas
|
|
SetPreviewMaterial(Material);
|
|
Material->bIsPreviewMaterial = true;
|
|
FMaterialEditorUtilities::InitExpressions(Material);
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
|
|
BindCommands();
|
|
|
|
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_MaterialEditor_Layout_v5")
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.1f)
|
|
->SetHideTabWell( true )
|
|
->AddTab(GetToolbarTabId(), ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.9f)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.2f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell( true )
|
|
->AddTab( PreviewTabId, ETabState::OpenedTab )
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->AddTab( PropertiesTabId, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation( Orient_Vertical )
|
|
->SetSizeCoefficient(0.80f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.8f)
|
|
->SetHideTabWell( true )
|
|
->AddTab( GraphCanvasTabId, ETabState::OpenedTab )
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient( 0.20f )
|
|
->AddTab( StatsTabId, ETabState::ClosedTab )
|
|
->AddTab( FindTabId, ETabState::ClosedTab )
|
|
)
|
|
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.2f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->AddTab( PaletteTabId, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
const bool bCreateDefaultStandaloneMenu = true;
|
|
const bool bCreateDefaultToolbar = true;
|
|
|
|
// Add the preview material to the objects being edited, so that we can find this editor from the temporary material graph
|
|
TArray< UObject* > ObjectsToEdit;
|
|
ObjectsToEdit.Add(ObjectToEdit);
|
|
ObjectsToEdit.Add(Material);
|
|
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, MaterialEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, false );
|
|
|
|
AddMenuExtender(GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "MaterialEditor" );
|
|
AddMenuExtender(MaterialEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
ExtendToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
// @todo toolkit world centric editing
|
|
/*if( IsWorldCentricAssetEditor() )
|
|
{
|
|
SpawnToolkitTab(GetToolbarTabId(), FString(), EToolkitTabSpot::ToolBar);
|
|
SpawnToolkitTab(PreviewTabId, FString(), EToolkitTabSpot::Viewport);
|
|
SpawnToolkitTab(GraphCanvasTabId, FString(), EToolkitTabSpot::Document);
|
|
SpawnToolkitTab(PropertiesTabId, FString(), EToolkitTabSpot::Details);
|
|
}*/
|
|
|
|
|
|
// Load editor settings from disk.
|
|
LoadEditorSettings();
|
|
|
|
// Set the preview mesh for the material. This call must occur after the toolbar is initialized.
|
|
if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString()))
|
|
{
|
|
// The material preview mesh couldn't be found or isn't loaded. Default to the one of the primitive types.
|
|
SetPreviewAsset( GUnrealEd->GetThumbnailManager()->EditorSphere );
|
|
}
|
|
|
|
// Initialize expression previews.
|
|
if (MaterialFunction)
|
|
{
|
|
// Support undo/redo for the material function if it exists
|
|
MaterialFunction->SetFlags(RF_Transactional);
|
|
|
|
Material->Expressions = MaterialFunction->FunctionExpressions;
|
|
Material->EditorComments = MaterialFunction->FunctionEditorComments;
|
|
|
|
// Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid
|
|
// This can happen if an expression class was removed
|
|
for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
if (!Material->Expressions[ExpressionIndex])
|
|
{
|
|
Material->Expressions.RemoveAt(ExpressionIndex);
|
|
}
|
|
}
|
|
|
|
if (Material->Expressions.Num() == 0)
|
|
{
|
|
// If this is an empty functions, create an output by default and start previewing it
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
check(!bMaterialDirty);
|
|
UMaterialExpression* Expression = CreateNewMaterialExpression(UMaterialExpressionFunctionOutput::StaticClass(), FVector2D(200, 300), false, true);
|
|
SetPreviewExpression(Expression);
|
|
// This shouldn't count as having dirtied the material, so reset the flag
|
|
bMaterialDirty = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bSetPreviewExpression = false;
|
|
UMaterialExpressionFunctionOutput* FirstOutput = NULL;
|
|
for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
UMaterialExpression* Expression = Material->Expressions[ExpressionIndex];
|
|
|
|
// Setup the expression to be used with the preview material instead of the function
|
|
Expression->Function = NULL;
|
|
Expression->Material = Material;
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression);
|
|
if (FunctionOutput)
|
|
{
|
|
FirstOutput = FunctionOutput;
|
|
if (FunctionOutput->bLastPreviewed)
|
|
{
|
|
bSetPreviewExpression = true;
|
|
|
|
// Preview the last output previewed
|
|
SetPreviewExpression(FunctionOutput);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bSetPreviewExpression && FirstOutput)
|
|
{
|
|
SetPreviewExpression(FirstOutput);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store the name of this material (for the tutorial widget meta)
|
|
Material->MaterialGraph->OriginalMaterialFullName = OriginalMaterial->GetName();
|
|
Material->MaterialGraph->RebuildGraph();
|
|
RecenterEditor();
|
|
|
|
//Make sure the preview material is initialized.
|
|
UpdatePreviewMaterial(true);
|
|
RegenerateCodeView(true);
|
|
|
|
ForceRefreshExpressionPreviews();
|
|
}
|
|
|
|
FMaterialEditor::FMaterialEditor()
|
|
: bMaterialDirty(false)
|
|
, bStatsFromPreviewMaterial(false)
|
|
, Material(NULL)
|
|
, OriginalMaterial(NULL)
|
|
, ExpressionPreviewMaterial(NULL)
|
|
, EmptyMaterial(NULL)
|
|
, PreviewExpression(NULL)
|
|
, MaterialFunction(NULL)
|
|
, OriginalMaterialObject(NULL)
|
|
, EditorOptions(NULL)
|
|
, ScopedTransaction(NULL)
|
|
, bAlwaysRefreshAllPreviews(false)
|
|
, bHideUnusedConnectors(false)
|
|
, bLivePreview(true)
|
|
, bIsRealtime(false)
|
|
, bShowStats(true)
|
|
, bShowBuiltinStats(false)
|
|
, bShowMobileStats(false)
|
|
, MenuExtensibilityManager(new FExtensibilityManager)
|
|
, ToolBarExtensibilityManager(new FExtensibilityManager)
|
|
{
|
|
}
|
|
|
|
FMaterialEditor::~FMaterialEditor()
|
|
{
|
|
// Broadcast that this editor is going down to all listeners
|
|
OnMaterialEditorClosed().Broadcast();
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < OverriddenVectorParametersToRevert.Num(); ParameterIndex++)
|
|
{
|
|
SetVectorParameterDefaultOnDependentMaterials(OverriddenVectorParametersToRevert[ParameterIndex], FLinearColor::Black, false);
|
|
}
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < OverriddenScalarParametersToRevert.Num(); ParameterIndex++)
|
|
{
|
|
SetScalarParameterDefaultOnDependentMaterials(OverriddenScalarParametersToRevert[ParameterIndex], 0, false);
|
|
}
|
|
|
|
// Unregister this delegate
|
|
FEditorSupportDelegates::MaterialUsageFlagsChanged.RemoveAll(this);
|
|
FEditorSupportDelegates::VectorParameterDefaultChanged.RemoveAll(this);
|
|
FEditorSupportDelegates::ScalarParameterDefaultChanged.RemoveAll(this);
|
|
|
|
// Null out the expression preview material so they can be GC'ed
|
|
ExpressionPreviewMaterial = NULL;
|
|
|
|
// Save editor settings to disk.
|
|
SaveEditorSettings();
|
|
|
|
MaterialDetailsView.Reset();
|
|
|
|
{
|
|
SCOPED_SUSPEND_RENDERING_THREAD(true);
|
|
|
|
ExpressionPreviews.Empty();
|
|
}
|
|
|
|
check( !ScopedTransaction );
|
|
|
|
GEditor->UnregisterForUndo( this );
|
|
}
|
|
|
|
void FMaterialEditor::GetAllMaterialExpressionGroups(TArray<FString>* OutGroups)
|
|
{
|
|
for (int32 MaterialExpressionIndex = 0; MaterialExpressionIndex < Material->Expressions.Num(); ++MaterialExpressionIndex)
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ];
|
|
UMaterialExpressionParameter *Switch = Cast<UMaterialExpressionParameter>(MaterialExpression);
|
|
UMaterialExpressionTextureSampleParameter *TextureS = Cast<UMaterialExpressionTextureSampleParameter>(MaterialExpression);
|
|
UMaterialExpressionFontSampleParameter *FontS = Cast<UMaterialExpressionFontSampleParameter>(MaterialExpression);
|
|
if(Switch)
|
|
{
|
|
OutGroups->AddUnique(Switch->Group.ToString());
|
|
}
|
|
if(TextureS)
|
|
{
|
|
OutGroups->AddUnique(TextureS->Group.ToString());
|
|
}
|
|
if(FontS)
|
|
{
|
|
OutGroups->AddUnique(FontS->Group.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdatePreviewViewportsVisibility()
|
|
{
|
|
if( Material->IsUIMaterial() )
|
|
{
|
|
PreviewViewport->SetVisibility(EVisibility::Collapsed);
|
|
PreviewUIViewport->SetVisibility(EVisibility::Visible);
|
|
}
|
|
else
|
|
{
|
|
PreviewViewport->SetVisibility(EVisibility::Visible);
|
|
PreviewUIViewport->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FMaterialEditor::CreateInternalWidgets()
|
|
{
|
|
PreviewViewport = SNew(SMaterialEditor3DPreviewViewport)
|
|
.MaterialEditor(SharedThis(this));
|
|
|
|
PreviewUIViewport = SNew(SMaterialEditorUIPreviewViewport, Material);
|
|
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
|
|
|
|
GraphEditor = CreateGraphEditorWidget();
|
|
// Manually set zoom level to avoid deferred zooming
|
|
GraphEditor->SetViewLocation(FVector2D::ZeroVector, 1);
|
|
|
|
const FDetailsViewArgs DetailsViewArgs( false, false, true, FDetailsViewArgs::HideNameArea, true, this );
|
|
MaterialDetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs );
|
|
|
|
FOnGetDetailCustomizationInstance LayoutExpressionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(
|
|
&FMaterialExpressionParameterDetails::MakeInstance, FOnCollectParameterGroups::CreateSP(this, &FMaterialEditor::GetAllMaterialExpressionGroups) );
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionParameter::StaticClass(),
|
|
LayoutExpressionParameterDetails
|
|
);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionFontSampleParameter::StaticClass(),
|
|
LayoutExpressionParameterDetails
|
|
);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionTextureSampleParameter::StaticClass(),
|
|
LayoutExpressionParameterDetails
|
|
);
|
|
|
|
FOnGetDetailCustomizationInstance LayoutCollectionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCollectionParameterDetails::MakeInstance);
|
|
|
|
MaterialDetailsView->RegisterInstancedCustomPropertyLayout(
|
|
UMaterialExpressionCollectionParameter::StaticClass(),
|
|
LayoutCollectionParameterDetails
|
|
);
|
|
|
|
PropertyEditorModule.RegisterCustomClassLayout( UMaterial::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialDetailCustomization::MakeInstance ) );
|
|
|
|
Palette = SNew(SMaterialPalette, SharedThis(this));
|
|
|
|
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
|
FMessageLogInitializationOptions LogOptions;
|
|
// Show Pages so that user is never allowed to clear log messages
|
|
LogOptions.bShowPages = false;
|
|
LogOptions.bShowFilters = false; //TODO - Provide custom filters? E.g. "Critical Errors" vs "Errors" needed for materials?
|
|
LogOptions.bAllowClear = false;
|
|
LogOptions.MaxPageCount = 1;
|
|
StatsListing = MessageLogModule.CreateLogListing( "MaterialEditorStats", LogOptions );
|
|
|
|
Stats = MessageLogModule.CreateLogListingWidget( StatsListing.ToSharedRef() );
|
|
|
|
FindResults =
|
|
SNew(SFindInMaterial, SharedThis(this));
|
|
|
|
CodeViewUtility =
|
|
SNew(SVerticalBox)
|
|
// Copy Button
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(SButton)
|
|
.Text( LOCTEXT("CopyHLSLButton", "Copy") )
|
|
.ToolTipText( LOCTEXT("CopyHLSLButtonToolTip", "Copies all HLSL code to the clipboard.") )
|
|
.ContentPadding(3)
|
|
.OnClicked(this, &FMaterialEditor::CopyCodeViewTextToClipboard)
|
|
]
|
|
]
|
|
// Separator
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
SNew(SSeparator)
|
|
];
|
|
|
|
CodeView =
|
|
SNew(SScrollBox)
|
|
+SScrollBox::Slot() .Padding(5)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &FMaterialEditor::GetCodeViewText)
|
|
];
|
|
|
|
RegenerateCodeView();
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
FName FMaterialEditor::GetToolkitFName() const
|
|
{
|
|
return FName("MaterialEditor");
|
|
}
|
|
|
|
FText FMaterialEditor::GetBaseToolkitName() const
|
|
{
|
|
return LOCTEXT("AppLabel", "Material Editor");
|
|
}
|
|
|
|
FText FMaterialEditor::GetToolkitName() const
|
|
{
|
|
const UObject* EditingObject = GetEditingObjects()[0];
|
|
|
|
const bool bDirtyState = EditingObject->GetOutermost()->IsDirty();
|
|
|
|
// Overridden to accommodate editing of multiple objects (original and preview materials)
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("ObjectName"), FText::FromString( EditingObject->GetName() ) );
|
|
Args.Add( TEXT("DirtyState"), bDirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() );
|
|
return FText::Format( LOCTEXT("MaterialEditorAppLabel", "{ObjectName}{DirtyState}"), Args );
|
|
}
|
|
|
|
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();
|
|
UpdateGraphNodeStates();
|
|
}
|
|
|
|
TStatId FMaterialEditor::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FMaterialEditor, STATGROUP_Tickables);
|
|
}
|
|
|
|
void FMaterialEditor::UpdateThumbnailInfoPreviewMesh(UMaterialInterface* MatInterface)
|
|
{
|
|
if ( MatInterface )
|
|
{
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( MatInterface->GetClass() );
|
|
if ( AssetTypeActions.IsValid() )
|
|
{
|
|
USceneThumbnailInfoWithPrimitive* OriginalThumbnailInfo = Cast<USceneThumbnailInfoWithPrimitive>(AssetTypeActions.Pin()->GetThumbnailInfo(MatInterface));
|
|
if ( OriginalThumbnailInfo )
|
|
{
|
|
OriginalThumbnailInfo->PreviewMesh = MatInterface->PreviewMesh;
|
|
MatInterface->PostEditChange();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::ExtendToolbar()
|
|
{
|
|
struct Local
|
|
{
|
|
static void FillToolbar(FToolBarBuilder& ToolbarBuilder)
|
|
{
|
|
ToolbarBuilder.BeginSection("Apply");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().Apply);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Search");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().FindInMaterial);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Graph");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CameraHome);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CleanUnusedExpressions);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ShowHideConnectors);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleLivePreview);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleRealtimeExpressions);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats);
|
|
ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMobileStats);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
}
|
|
};
|
|
|
|
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar )
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
AddToolbarExtender(GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked<IMaterialEditorModule>( "MaterialEditor" );
|
|
AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
|
|
UMaterialInterface* FMaterialEditor::GetMaterialInterface() const
|
|
{
|
|
return Material;
|
|
}
|
|
|
|
bool FMaterialEditor::ApproveSetPreviewAsset(UObject* InAsset)
|
|
{
|
|
bool bApproved = true;
|
|
|
|
// Only permit the use of a skeletal mesh if the material has bUsedWithSkeltalMesh.
|
|
if (USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(InAsset))
|
|
{
|
|
if (!Material->bUsedWithSkeletalMesh)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_MaterialEditor_CantPreviewOnSkelMesh", "Can't preview on the specified skeletal mesh because the material has not been compiled with bUsedWithSkeletalMesh."));
|
|
bApproved = false;
|
|
}
|
|
}
|
|
|
|
return bApproved;
|
|
}
|
|
|
|
void FMaterialEditor::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());
|
|
|
|
if (bMaterialDirty)
|
|
{
|
|
UpdateOriginalMaterial();
|
|
}
|
|
|
|
IMaterialEditor::SaveAsset_Execute();
|
|
}
|
|
|
|
void FMaterialEditor::SaveAssetAs_Execute()
|
|
{
|
|
UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName());
|
|
|
|
if (bMaterialDirty)
|
|
{
|
|
UpdateOriginalMaterial();
|
|
}
|
|
|
|
IMaterialEditor::SaveAssetAs_Execute();
|
|
}
|
|
|
|
bool FMaterialEditor::OnRequestClose()
|
|
{
|
|
DestroyColorPicker();
|
|
|
|
if (bMaterialDirty)
|
|
{
|
|
// find out the user wants to do with this dirty material
|
|
EAppReturnType::Type YesNoCancelReply = FMessageDialog::Open(EAppMsgType::YesNoCancel,
|
|
FText::Format(
|
|
NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorClose", "Would you like to apply changes to this material to the original material?\n{0}\n(No will lose all changes!)"),
|
|
FText::FromString(OriginalMaterialObject->GetPathName()) ));
|
|
|
|
// act on it
|
|
switch (YesNoCancelReply)
|
|
{
|
|
case EAppReturnType::Yes:
|
|
// update material and exit
|
|
UpdateOriginalMaterial();
|
|
break;
|
|
|
|
case EAppReturnType::No:
|
|
// exit
|
|
bMaterialDirty = false;
|
|
break;
|
|
|
|
case EAppReturnType::Cancel:
|
|
// don't exit
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FMaterialEditor::DrawMaterialInfoStrings(
|
|
FCanvas* Canvas,
|
|
const UMaterial* Material,
|
|
const FMaterialResource* MaterialResource,
|
|
const TArray<FString>& CompileErrors,
|
|
int32 &DrawPositionY,
|
|
bool bDrawInstructions)
|
|
{
|
|
check(Material && MaterialResource);
|
|
|
|
ERHIFeatureLevel::Type FeatureLevel = MaterialResource->GetFeatureLevel();
|
|
FString FeatureLevelName;
|
|
GetFeatureLevelName(FeatureLevel,FeatureLevelName);
|
|
|
|
// The font to use when displaying info strings
|
|
UFont* FontToUse = GEngine->GetTinyFont();
|
|
const int32 SpacingBetweenLines = 13;
|
|
|
|
if (bDrawInstructions)
|
|
{
|
|
// Display any errors and messages in the upper left corner of the viewport.
|
|
TArray<FString> Descriptions;
|
|
TArray<int32> InstructionCounts;
|
|
MaterialResource->GetRepresentativeInstructionCounts(Descriptions, InstructionCounts);
|
|
|
|
for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++)
|
|
{
|
|
FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"),*Descriptions[InstructionIndex],InstructionCounts[InstructionIndex]);
|
|
Canvas->DrawShadowedString(5, DrawPositionY, *InstructionCountString, FontToUse, FLinearColor(1, 1, 0));
|
|
DrawPositionY += SpacingBetweenLines;
|
|
}
|
|
|
|
// Display the number of samplers used by the material.
|
|
const int32 SamplersUsed = MaterialResource->GetSamplerUsage();
|
|
|
|
if (SamplersUsed >= 0)
|
|
{
|
|
int32 MaxSamplers = 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;
|
|
}
|
|
}
|
|
|
|
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, 25);
|
|
|
|
const FColor PreviewColor( 70,100,200 );
|
|
const FColor FontColor( 255,255,128 );
|
|
|
|
UFont* FontToUse = GEditor->EditorFont;
|
|
|
|
Canvas->DrawTile( 0.0f, 0.0f, TileSize.X, TileSize.Y, 0.0f, 0.0f, 0.0f, 0.0f, PreviewColor );
|
|
|
|
int32 XL, YL;
|
|
StringSize( FontToUse, XL, YL, *Name );
|
|
if( XL > TileSize.X )
|
|
{
|
|
// There isn't enough room to show the preview expression name
|
|
Name = TEXT("Previewing");
|
|
StringSize( FontToUse, XL, YL, *Name );
|
|
}
|
|
|
|
// Center the string in the middle of the tile.
|
|
const FIntPoint StringPos( (TileSize.X-XL)/2, ((TileSize.Y-YL)/2)+1 );
|
|
// Draw the preview message
|
|
Canvas->DrawShadowedString( StringPos.X, StringPos.Y, *Name, FontToUse, FontColor );
|
|
|
|
Canvas->PopTransform();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RecenterEditor()
|
|
{
|
|
UEdGraphNode* FocusNode = NULL;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
bool bSetPreviewExpression = false;
|
|
UMaterialExpressionFunctionOutput* FirstOutput = NULL;
|
|
for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
|
|
{
|
|
UMaterialExpression* Expression = Material->Expressions[ExpressionIndex];
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression);
|
|
if (FunctionOutput)
|
|
{
|
|
FirstOutput = FunctionOutput;
|
|
if (FunctionOutput->bLastPreviewed)
|
|
{
|
|
bSetPreviewExpression = true;
|
|
FocusNode = FunctionOutput->GraphNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bSetPreviewExpression && FirstOutput)
|
|
{
|
|
FocusNode = FirstOutput->GraphNode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FocusNode = Material->MaterialGraph->RootNode;
|
|
}
|
|
|
|
if (FocusNode)
|
|
{
|
|
JumpToNode(FocusNode);
|
|
}
|
|
else
|
|
{
|
|
// Get current view location so that we don't change the zoom amount
|
|
FVector2D CurrLocation;
|
|
float CurrZoomLevel;
|
|
GraphEditor->GetViewLocation(CurrLocation, CurrZoomLevel);
|
|
GraphEditor->SetViewLocation(FVector2D::ZeroVector, CurrZoomLevel);
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::SetPreviewAsset(UObject* InAsset)
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
return PreviewViewport->SetPreviewAsset(InAsset);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FMaterialEditor::SetPreviewAssetByName(const TCHAR* InAssetName)
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
return PreviewViewport->SetPreviewAssetByName(InAssetName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::SetPreviewMaterial(UMaterialInterface* InMaterialInterface)
|
|
{
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
if (PreviewUIViewport.IsValid())
|
|
{
|
|
PreviewUIViewport->SetPreviewMaterial(InMaterialInterface);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->SetPreviewMaterial(InMaterialInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RefreshPreviewViewport()
|
|
{
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::LoadEditorSettings()
|
|
{
|
|
EditorOptions = NewObject<UMaterialEditorOptions>();
|
|
|
|
if (EditorOptions->bHideUnusedConnectors) {OnShowConnectors();}
|
|
if (bLivePreview != EditorOptions->bLivePreviewUpdate)
|
|
{
|
|
ToggleLivePreview();
|
|
}
|
|
if (EditorOptions->bAlwaysRefreshAllPreviews) {OnAlwaysRefreshAllPreviews();}
|
|
if (EditorOptions->bRealtimeExpressionViewport) {ToggleRealTimeExpressions();}
|
|
|
|
if ( PreviewViewport.IsValid() )
|
|
{
|
|
if (EditorOptions->bShowGrid)
|
|
{
|
|
PreviewViewport->TogglePreviewGrid();
|
|
}
|
|
|
|
if (EditorOptions->bShowBackground)
|
|
{
|
|
PreviewViewport->TogglePreviewBackground();
|
|
}
|
|
|
|
if (EditorOptions->bRealtimeMaterialViewport)
|
|
{
|
|
PreviewViewport->OnToggleRealtime();
|
|
}
|
|
|
|
// Load the preview scene
|
|
PreviewViewport->PreviewScene.LoadSettings(TEXT("MaterialEditor"));
|
|
}
|
|
|
|
if (EditorOptions->bShowMobileStats)
|
|
{
|
|
ToggleMobileStats();
|
|
}
|
|
|
|
|
|
// 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() );
|
|
PreviewViewport->PreviewScene.SaveSettings(TEXT("MaterialEditor"));
|
|
|
|
if ( EditorOptions )
|
|
{
|
|
EditorOptions->bShowGrid = PreviewViewport->IsTogglePreviewGridChecked();
|
|
EditorOptions->bShowBackground = PreviewViewport->IsTogglePreviewBackgroundChecked();
|
|
EditorOptions->bRealtimeMaterialViewport = PreviewViewport->IsRealtime();
|
|
EditorOptions->bShowMobileStats = bShowMobileStats;
|
|
EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked();
|
|
EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews();
|
|
EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked();
|
|
EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked();
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
|
|
GConfig->SetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PreviewViewport->PreviewPrimType, GEditorPerProjectIni);
|
|
}
|
|
|
|
FText FMaterialEditor::GetCodeViewText() const
|
|
{
|
|
return FText::FromString(HLSLCode);
|
|
}
|
|
|
|
FReply FMaterialEditor::CopyCodeViewTextToClipboard()
|
|
{
|
|
FPlatformMisc::ClipboardCopy(*HLSLCode);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FMaterialEditor::RegenerateCodeView(bool bForce)
|
|
{
|
|
#define MARKTAG TEXT("/*MARK_")
|
|
#define MARKTAGLEN 7
|
|
|
|
HLSLCode = TEXT("");
|
|
|
|
if (!CodeTab.IsValid() || (!bLivePreview && !bForce))
|
|
{
|
|
//When bLivePreview is false then the source can be out of date.
|
|
return;
|
|
}
|
|
|
|
FString MarkupSource;
|
|
if (Material->GetMaterialResource(GMaxRHIFeatureLevel)->GetMaterialExpressionSource(MarkupSource))
|
|
{
|
|
// Remove line-feeds and leave just CRs so the character counts match the selection ranges.
|
|
MarkupSource.ReplaceInline(TEXT("\r"), TEXT(""));
|
|
|
|
// Improve formatting: Convert tab to 4 spaces since STextBlock (currently) doesn't show tab characters
|
|
MarkupSource.ReplaceInline(TEXT("\t"), TEXT(" "));
|
|
|
|
// Extract highlight ranges from markup tags
|
|
|
|
// Make a copy so we can insert null terminators.
|
|
TCHAR* MarkupSourceCopy = new TCHAR[MarkupSource.Len()+1];
|
|
FCString::Strcpy(MarkupSourceCopy, MarkupSource.Len()+1, *MarkupSource);
|
|
|
|
TCHAR* Ptr = MarkupSourceCopy;
|
|
while( Ptr && *Ptr != '\0' )
|
|
{
|
|
TCHAR* NextTag = FCString::Strstr( Ptr, MARKTAG );
|
|
if( !NextTag )
|
|
{
|
|
// No more tags, so we're done!
|
|
HLSLCode += Ptr;
|
|
break;
|
|
}
|
|
|
|
// Copy the text up to the tag.
|
|
*NextTag = '\0';
|
|
HLSLCode += Ptr;
|
|
|
|
// Advance past the markup tag to see what type it is (beginning or end)
|
|
NextTag += MARKTAGLEN;
|
|
int32 TagNumber = FCString::Atoi(NextTag+1);
|
|
Ptr = FCString::Strstr(NextTag, TEXT("*/")) + 2;
|
|
}
|
|
|
|
delete[] MarkupSourceCopy;
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdatePreviewMaterial( bool bForce )
|
|
{
|
|
if (!bLivePreview && !bForce)
|
|
{
|
|
//Don't update the preview material
|
|
return;
|
|
}
|
|
|
|
bStatsFromPreviewMaterial = true;
|
|
|
|
if( PreviewExpression && ExpressionPreviewMaterial )
|
|
{
|
|
PreviewExpression->ConnectToPreviewMaterial(ExpressionPreviewMaterial,0);
|
|
}
|
|
|
|
if(PreviewExpression)
|
|
{
|
|
check(ExpressionPreviewMaterial);
|
|
|
|
// The preview material's expressions array must stay up to date before recompiling
|
|
// So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated
|
|
ExpressionPreviewMaterial->Expressions = Material->Expressions;
|
|
|
|
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread);
|
|
UpdateContext.AddMaterial(ExpressionPreviewMaterial);
|
|
|
|
// If we are previewing an expression, update the expression preview material
|
|
ExpressionPreviewMaterial->PreEditChange( NULL );
|
|
ExpressionPreviewMaterial->PostEditChange();
|
|
}
|
|
else
|
|
{
|
|
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread);
|
|
UpdateContext.AddMaterial(Material);
|
|
|
|
// Update the regular preview material when not previewing an expression.
|
|
Material->PreEditChange( NULL );
|
|
Material->PostEditChange();
|
|
|
|
UpdateStatsMaterials();
|
|
|
|
// Null out the expression preview material so they can be GC'ed
|
|
ExpressionPreviewMaterial = NULL;
|
|
}
|
|
|
|
|
|
// Reregister all components that use the preview material, since UMaterial::PEC does not reregister components using a bIsPreviewMaterial=true material
|
|
RefreshPreviewViewport();
|
|
}
|
|
|
|
void FMaterialEditor::RebuildMaterialInstanceEditors(UMaterialInstance * MatInst)
|
|
{
|
|
FAssetEditorManager& AssetEditorManager = FAssetEditorManager::Get();
|
|
TArray<UObject*> EditedAssets = AssetEditorManager.GetAllEditedAssets();
|
|
|
|
for (int32 AssetIdx = 0; AssetIdx < EditedAssets.Num(); AssetIdx++)
|
|
{
|
|
UObject* EditedAsset = EditedAssets[AssetIdx];
|
|
|
|
UMaterialInstance* SourceInstance = Cast<UMaterialInstance>(EditedAsset);
|
|
|
|
if(!SourceInstance)
|
|
{
|
|
// Check to see if the EditedAssets are from material instance editor
|
|
UMaterialEditorInstanceConstant* EditorInstance = Cast<UMaterialEditorInstanceConstant>(EditedAsset);
|
|
if(EditorInstance && EditorInstance->SourceInstance)
|
|
{
|
|
SourceInstance = Cast<UMaterialInstance>(EditorInstance->SourceInstance);
|
|
}
|
|
}
|
|
|
|
// Ensure the material instance is valid and not a UMaterialInstanceDynamic, as that doesn't use FMaterialInstanceEditor as its editor
|
|
if ( SourceInstance != NULL && !SourceInstance->IsA(UMaterialInstanceDynamic::StaticClass()))
|
|
{
|
|
UMaterial * MICOriginalMaterial = SourceInstance->GetMaterial();
|
|
if (MICOriginalMaterial == OriginalMaterial)
|
|
{
|
|
IAssetEditorInstance* EditorInstance = AssetEditorManager.FindEditorForAsset(EditedAsset, false);
|
|
if ( EditorInstance != NULL )
|
|
{
|
|
FMaterialInstanceEditor* OtherEditor = static_cast<FMaterialInstanceEditor*>(EditorInstance);
|
|
OtherEditor->RebuildMaterialInstanceEditor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdateOriginalMaterial()
|
|
{
|
|
// If the Material has compilation errors, warn the user
|
|
for (int32 i = ERHIFeatureLevel::SM5; i >= 0; --i)
|
|
{
|
|
ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)i;
|
|
if( Material->GetMaterialResource(FeatureLevel)->GetCompileErrors().Num() > 0 )
|
|
{
|
|
FString FeatureLevelName;
|
|
GetFeatureLevelName(FeatureLevel, FeatureLevelName);
|
|
FSuppressableWarningDialog::FSetupInfo Info(
|
|
FText::Format(NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial", "The current material has compilation errors, so it will not render correctly in feature level {0}.\nAre you sure you wish to continue?"),FText::FromString(*FeatureLevelName)),
|
|
NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial_Title", "Warning: Compilation errors in this Material" ), "Warning_CompileErrorsInMaterial");
|
|
Info.ConfirmText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialConfirm", "Continue");
|
|
Info.CancelText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialCancel", "Abort");
|
|
|
|
FSuppressableWarningDialog CompileErrorsWarning( Info );
|
|
if( CompileErrorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure any graph position changes that might not have been copied are taken into account
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
|
|
//remove any memory copies of shader files, so they will be reloaded from disk
|
|
//this way the material editor can be used for quick shader iteration
|
|
FlushShaderFileCache();
|
|
|
|
//recompile and refresh the preview material so it will be updated if there was a shader change
|
|
//Force it even if bLivePreview is false.
|
|
UpdatePreviewMaterial(true);
|
|
RegenerateCodeView(true);
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
const FText LocalizedMaterialEditorApply = NSLOCTEXT("UnrealEd", "ToolTip_MaterialEditorApply", "Apply changes to original material and its use in the world.");
|
|
GWarn->BeginSlowTask( LocalizedMaterialEditorApply, true );
|
|
GWarn->StatusUpdate( 1, 1, LocalizedMaterialEditorApply );
|
|
|
|
// Handle propagation of the material function being edited
|
|
if (MaterialFunction)
|
|
{
|
|
// Copy the expressions back from the preview material
|
|
MaterialFunction->FunctionExpressions = Material->Expressions;
|
|
MaterialFunction->FunctionEditorComments = Material->EditorComments;
|
|
|
|
// Preserve the thumbnail info
|
|
UThumbnailInfo* OriginalThumbnailInfo = MaterialFunction->ParentFunction->ThumbnailInfo;
|
|
UThumbnailInfo* ThumbnailInfo = MaterialFunction->ThumbnailInfo;
|
|
MaterialFunction->ParentFunction->ThumbnailInfo = NULL;
|
|
MaterialFunction->ThumbnailInfo = NULL;
|
|
|
|
// overwrite the original material function in place by constructing a new one with the same name
|
|
MaterialFunction->ParentFunction = (UMaterialFunction*)StaticDuplicateObject(
|
|
MaterialFunction,
|
|
MaterialFunction->ParentFunction->GetOuter(),
|
|
MaterialFunction->ParentFunction->GetFName(),
|
|
RF_AllFlags,
|
|
MaterialFunction->ParentFunction->GetClass());
|
|
|
|
// Restore the thumbnail info
|
|
MaterialFunction->ParentFunction->ThumbnailInfo = OriginalThumbnailInfo;
|
|
MaterialFunction->ThumbnailInfo = ThumbnailInfo;
|
|
|
|
// Restore RF_Standalone on the original material function, as it had been removed from the preview material so that it could be GC'd.
|
|
MaterialFunction->ParentFunction->SetFlags( RF_Standalone );
|
|
|
|
for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionExpressions.Num(); ExpressionIndex++)
|
|
{
|
|
UMaterialExpression* CurrentExpression = MaterialFunction->ParentFunction->FunctionExpressions[ExpressionIndex];
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid expression at index [%i] whilst saving material function."), ExpressionIndex);
|
|
|
|
// Link the expressions back to their function
|
|
if (CurrentExpression)
|
|
{
|
|
CurrentExpression->Material = NULL;
|
|
CurrentExpression->Function = MaterialFunction->ParentFunction;
|
|
}
|
|
}
|
|
for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionEditorComments.Num(); ExpressionIndex++)
|
|
{
|
|
UMaterialExpressionComment* CurrentExpression = MaterialFunction->ParentFunction->FunctionEditorComments[ExpressionIndex];
|
|
ensureMsgf(CurrentExpression, TEXT("Invalid comment at index [%i] whilst saving material function."), ExpressionIndex);
|
|
|
|
// Link the expressions back to their function
|
|
if (CurrentExpression)
|
|
{
|
|
CurrentExpression->Material = NULL;
|
|
CurrentExpression->Function = MaterialFunction->ParentFunction;
|
|
}
|
|
}
|
|
|
|
// mark the parent function as changed
|
|
MaterialFunction->ParentFunction->PreEditChange(NULL);
|
|
MaterialFunction->ParentFunction->PostEditChange();
|
|
MaterialFunction->ParentFunction->MarkPackageDirty();
|
|
|
|
// clear the dirty flag
|
|
bMaterialDirty = false;
|
|
bStatsFromPreviewMaterial = false;
|
|
|
|
// Create a material update context so we can safely update materials using this function.
|
|
{
|
|
FMaterialUpdateContext UpdateContext;
|
|
|
|
// Go through all materials in memory and recompile them if they use this material function
|
|
for (TObjectIterator<UMaterial> It; It; ++It)
|
|
{
|
|
UMaterial* CurrentMaterial = *It;
|
|
if (CurrentMaterial != Material)
|
|
{
|
|
bool bRecompile = false;
|
|
|
|
// Preview materials often use expressions for rendering that are not in their Expressions array,
|
|
// And therefore their MaterialFunctionInfos are not up to date.
|
|
// However we don't want to trigger this if the Material is a preview material itself. This can now be the case with thumbnail preview materials for material functions.
|
|
if (CurrentMaterial->bIsPreviewMaterial && !Material->bIsPreviewMaterial)
|
|
{
|
|
bRecompile = true;
|
|
}
|
|
else
|
|
{
|
|
for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++)
|
|
{
|
|
if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction)
|
|
{
|
|
bRecompile = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bRecompile)
|
|
{
|
|
UpdateContext.AddMaterial(CurrentMaterial);
|
|
|
|
// Propagate the function change to this material
|
|
CurrentMaterial->PreEditChange(NULL);
|
|
CurrentMaterial->PostEditChange();
|
|
CurrentMaterial->MarkPackageDirty();
|
|
|
|
if (CurrentMaterial->MaterialGraph)
|
|
{
|
|
CurrentMaterial->MaterialGraph->RebuildGraph();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update the world's viewports
|
|
FEditorDelegates::RefreshEditor.Broadcast();
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
}
|
|
// Handle propagation of the material being edited
|
|
else
|
|
{
|
|
FNavigationLockContext NavUpdateLock(ENavigationLockReason::MaterialUpdate);
|
|
|
|
// Create a material update context so we can safely update materials.
|
|
{
|
|
FMaterialUpdateContext UpdateContext;
|
|
UpdateContext.AddMaterial(OriginalMaterial);
|
|
|
|
// ensure the original copy of the material is removed from the editor's selection set
|
|
// or it will end up containing a stale, invalid entry
|
|
if ( OriginalMaterial->IsSelected() )
|
|
{
|
|
GEditor->GetSelectedObjects()->Deselect( OriginalMaterial );
|
|
}
|
|
|
|
// Preserve the thumbnail info
|
|
UThumbnailInfo* OriginalThumbnailInfo = OriginalMaterial->ThumbnailInfo;
|
|
UThumbnailInfo* ThumbnailInfo = Material->ThumbnailInfo;
|
|
OriginalMaterial->ThumbnailInfo = NULL;
|
|
Material->ThumbnailInfo = NULL;
|
|
|
|
// A bit hacky, but disable material compilation in post load when we duplicate the material.
|
|
UMaterial::ForceNoCompilationInPostLoad(true);
|
|
|
|
// overwrite the original material in place by constructing a new one with the same name
|
|
OriginalMaterial = (UMaterial*)StaticDuplicateObject( Material, OriginalMaterial->GetOuter(), OriginalMaterial->GetFName(),
|
|
RF_AllFlags,
|
|
OriginalMaterial->GetClass());
|
|
|
|
// Post load has been called, allow materials to be compiled in PostLoad.
|
|
UMaterial::ForceNoCompilationInPostLoad(false);
|
|
|
|
// Restore the thumbnail info
|
|
OriginalMaterial->ThumbnailInfo = OriginalThumbnailInfo;
|
|
Material->ThumbnailInfo = ThumbnailInfo;
|
|
|
|
// Change the original material object to the new original material
|
|
OriginalMaterialObject = OriginalMaterial;
|
|
|
|
// Restore RF_Standalone on the original material, as it had been removed from the preview material so that it could be GC'd.
|
|
OriginalMaterial->SetFlags( RF_Standalone );
|
|
|
|
// Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials
|
|
OriginalMaterial->bUsedAsSpecialEngineMaterial = Material->bUsedAsSpecialEngineMaterial;
|
|
|
|
// If we are showing stats for mobile materials, compile the full material for ES2 here. That way we can see if permutations
|
|
// not used for preview materials fail to compile.
|
|
if (bShowMobileStats)
|
|
{
|
|
OriginalMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,true);
|
|
}
|
|
|
|
// let the material update itself if necessary
|
|
OriginalMaterial->PreEditChange(NULL);
|
|
|
|
OriginalMaterial->PostEditChange();
|
|
|
|
OriginalMaterial->MarkPackageDirty();
|
|
|
|
// clear the dirty flag
|
|
bMaterialDirty = false;
|
|
bStatsFromPreviewMaterial = false;
|
|
|
|
// update the world's viewports
|
|
FEditorDelegates::RefreshEditor.Broadcast();
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
|
|
// Force particle components to update their view relevance.
|
|
for (TObjectIterator<UParticleSystemComponent> It; It; ++It)
|
|
{
|
|
It->bIsViewRelevanceDirty = true;
|
|
}
|
|
|
|
// Update parameter names on any child material instances
|
|
for (TObjectIterator<UMaterialInstance> It; It; ++It)
|
|
{
|
|
if (It->Parent == OriginalMaterial)
|
|
{
|
|
It->UpdateParameterNames();
|
|
}
|
|
}
|
|
|
|
// Leaving this scope will update all dependent material instances.
|
|
}
|
|
|
|
RebuildMaterialInstanceEditors(NULL);
|
|
|
|
FMaterialEditorUtilities::BuildTextureStreamingData(OriginalMaterial);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void FMaterialEditor::UpdateMaterialInfoList(bool bForceDisplay)
|
|
{
|
|
TArray< TSharedRef<class FTokenizedMessage> > Messages;
|
|
|
|
TArray<TSharedPtr<FMaterialInfo>> TempMaterialInfoList;
|
|
|
|
ERHIFeatureLevel::Type FeatureLevelsToDisplay[2];
|
|
int32 NumFeatureLevels = 0;
|
|
// Always show basic features so that errors aren't hidden
|
|
FeatureLevelsToDisplay[NumFeatureLevels++] = GMaxRHIFeatureLevel;
|
|
if (bShowMobileStats)
|
|
{
|
|
FeatureLevelsToDisplay[NumFeatureLevels++] = ERHIFeatureLevel::ES2;
|
|
}
|
|
|
|
if (NumFeatureLevels > 0)
|
|
{
|
|
UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial;
|
|
|
|
for (int32 i = 0; i < NumFeatureLevels; ++i)
|
|
{
|
|
TArray<FString> CompileErrors;
|
|
ERHIFeatureLevel::Type FeatureLevel = FeatureLevelsToDisplay[i];
|
|
const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(FeatureLevel);
|
|
|
|
if (MaterialFunction && ExpressionPreviewMaterial)
|
|
{
|
|
// Add a compile error message for functions missing an output
|
|
CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel)->GetCompileErrors();
|
|
|
|
bool bFoundFunctionOutput = false;
|
|
for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++)
|
|
{
|
|
if (Material->Expressions[ExpressionIndex]->IsA(UMaterialExpressionFunctionOutput::StaticClass()))
|
|
{
|
|
bFoundFunctionOutput = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFoundFunctionOutput)
|
|
{
|
|
CompileErrors.Add(TEXT("Missing a function output"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CompileErrors = MaterialResource->GetCompileErrors();
|
|
}
|
|
|
|
// Only show general info if stats enabled
|
|
if (!MaterialFunction && bShowStats)
|
|
{
|
|
// Display any errors and messages in the upper left corner of the viewport.
|
|
TArray<FString> Descriptions;
|
|
TArray<int32> InstructionCounts;
|
|
TArray<FString> EmptyDescriptions;
|
|
TArray<int32> EmptyInstructionCounts;
|
|
|
|
MaterialResource->GetRepresentativeInstructionCounts(Descriptions, InstructionCounts);
|
|
|
|
//Built in stats is no longer exposed to the UI but may still be useful so they're still in the code.
|
|
bool bBuiltinStats = false;
|
|
const FMaterialResource* EmptyMaterialResource = EmptyMaterial ? EmptyMaterial->GetMaterialResource(FeatureLevel) : NULL;
|
|
if (bShowBuiltinStats && bStatsFromPreviewMaterial && EmptyMaterialResource && InstructionCounts.Num() > 0)
|
|
{
|
|
EmptyMaterialResource->GetRepresentativeInstructionCounts(EmptyDescriptions, EmptyInstructionCounts);
|
|
|
|
if (EmptyInstructionCounts.Num() > 0)
|
|
{
|
|
//The instruction counts should match. If not, the preview material has been changed without the EmptyMaterial being updated to match.
|
|
if (ensure(InstructionCounts.Num() == EmptyInstructionCounts.Num()))
|
|
{
|
|
bBuiltinStats = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++)
|
|
{
|
|
FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"),*Descriptions[InstructionIndex], InstructionCounts[InstructionIndex]);
|
|
if (bBuiltinStats)
|
|
{
|
|
InstructionCountString += FString::Printf(TEXT(" - Built-in instructions: %u"), EmptyInstructionCounts[InstructionIndex]);
|
|
}
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InstructionCountString, FLinearColor::Yellow)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
|
|
Line->AddToken(FTextToken::Create(FText::FromString(InstructionCountString)));
|
|
Messages.Add(Line);
|
|
}
|
|
|
|
// Display the number of samplers used by the material.
|
|
const int32 SamplersUsed = MaterialResource->GetSamplerUsage();
|
|
|
|
if (SamplersUsed >= 0)
|
|
{
|
|
int32 MaxSamplers = 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);
|
|
}
|
|
}
|
|
|
|
FString FeatureLevelName;
|
|
GetFeatureLevelName(FeatureLevel,FeatureLevelName);
|
|
for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++)
|
|
{
|
|
FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]);
|
|
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red)));
|
|
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create( EMessageSeverity::Error );
|
|
Line->AddToken( FTextToken::Create( FText::FromString( ErrorString ) ) );
|
|
Messages.Add(Line);
|
|
bForceDisplay = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bNeedsRefresh = false;
|
|
if (TempMaterialInfoList.Num() != MaterialInfoList.Num())
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
for (int32 Index = 0; !bNeedsRefresh && Index < TempMaterialInfoList.Num(); ++Index)
|
|
{
|
|
if (TempMaterialInfoList[Index]->Color != MaterialInfoList[Index]->Color)
|
|
{
|
|
bNeedsRefresh = true;
|
|
break;
|
|
}
|
|
|
|
if (TempMaterialInfoList[Index]->Text != MaterialInfoList[Index]->Text)
|
|
{
|
|
bNeedsRefresh = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bNeedsRefresh)
|
|
{
|
|
MaterialInfoList = TempMaterialInfoList;
|
|
/*TSharedPtr<SWidget> TitleBar = GraphEditor->GetTitleBar();
|
|
if (TitleBar.IsValid())
|
|
{
|
|
StaticCastSharedPtr<SMaterialEditorTitleBar>(TitleBar)->RequestRefresh();
|
|
}*/
|
|
|
|
StatsListing->ClearMessages();
|
|
StatsListing->AddMessages(Messages);
|
|
|
|
if (bForceDisplay)
|
|
{
|
|
TabManager->InvokeTab(StatsTabId);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::UpdateGraphNodeStates()
|
|
{
|
|
const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel);
|
|
const FMaterialResource* ErrorMaterialResourceES2 = NULL;
|
|
if (bShowMobileStats)
|
|
{
|
|
ErrorMaterialResourceES2 = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(ERHIFeatureLevel::ES2) : Material->GetMaterialResource(ERHIFeatureLevel::ES2);
|
|
}
|
|
|
|
bool bUpdatedErrorState = false;
|
|
|
|
// Have to loop through everything here as there's no way to be notified when the material resource updates
|
|
for (int32 Index = 0; Index < Material->MaterialGraph->Nodes.Num(); ++Index)
|
|
{
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Material->MaterialGraph->Nodes[Index]);
|
|
if (MaterialNode)
|
|
{
|
|
MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression);
|
|
MaterialNode->bIsErrorExpression = (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE)
|
|
|| (ErrorMaterialResourceES2 && ErrorMaterialResourceES2->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE);
|
|
|
|
if (MaterialNode->bIsErrorExpression && !MaterialNode->bHasCompilerMessage)
|
|
{
|
|
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 (bUpdatedErrorState)
|
|
{
|
|
// Rebuild the SGraphNodes to display/hide error block
|
|
GraphEditor->NotifyGraphChanged();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
Collector.AddReferencedObject( EditorOptions );
|
|
Collector.AddReferencedObject( Material );
|
|
Collector.AddReferencedObject( OriginalMaterial );
|
|
Collector.AddReferencedObject( MaterialFunction );
|
|
Collector.AddReferencedObject( ExpressionPreviewMaterial );
|
|
Collector.AddReferencedObject( EmptyMaterial );
|
|
}
|
|
|
|
void FMaterialEditor::BindCommands()
|
|
{
|
|
const FMaterialEditorCommands& Commands = FMaterialEditorCommands::Get();
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.Apply,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::OnApply ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::OnApplyEnabled ) );
|
|
|
|
ToolkitCommands->MapAction(
|
|
FEditorViewportCommands::Get().ToggleRealTime,
|
|
FExecuteAction::CreateSP( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::OnToggleRealtime ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::IsRealtime ) );
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::UndoGraphAction));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::RedoGraphAction));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.CameraHome,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnCameraHome),
|
|
FCanExecuteAction() );
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.CleanUnusedExpressions,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::CleanUnusedExpressions),
|
|
FCanExecuteAction() );
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ShowHideConnectors,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnShowConnectors),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnShowConnectorsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleLivePreview,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleLivePreview),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleRealtimeExpressions,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleRealTimeExpressions),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AlwaysRefreshAllPreviews,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlwaysRefreshAllPreviews),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnAlwaysRefreshAllPreviews));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleMaterialStats,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleStats),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleStatsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleMobileStats,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleMobileStats),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleMobileStatsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.UseCurrentTexture,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertObjects,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertToTextureObjects,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertToTextureSamples,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertToConstant,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.StopPreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.StartPreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.EnableRealtimePreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DisableRealtimePreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SelectDownstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownsteamNodes));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SelectUpstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpsteamNodes));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RemoveFromFavorites,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AddToFavorites,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ForceRefreshPreviews,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.FindInMaterial,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnFindInMaterial));
|
|
}
|
|
|
|
void FMaterialEditor::OnApply()
|
|
{
|
|
UE_LOG(LogMaterialEditor, Log, TEXT("Applying material %s"), *GetEditingObjects()[0]->GetName());
|
|
|
|
UpdateOriginalMaterial();
|
|
}
|
|
|
|
bool FMaterialEditor::OnApplyEnabled() const
|
|
{
|
|
return bMaterialDirty == true;
|
|
}
|
|
|
|
void FMaterialEditor::OnCameraHome()
|
|
{
|
|
RecenterEditor();
|
|
}
|
|
|
|
void FMaterialEditor::OnShowConnectors()
|
|
{
|
|
bHideUnusedConnectors = !bHideUnusedConnectors;
|
|
GraphEditor->SetPinVisibility(bHideUnusedConnectors ? SGraphEditor::Pin_HideNoConnection : SGraphEditor::Pin_Show);
|
|
}
|
|
|
|
bool FMaterialEditor::IsOnShowConnectorsChecked() const
|
|
{
|
|
return bHideUnusedConnectors == false;
|
|
}
|
|
|
|
void FMaterialEditor::ToggleLivePreview()
|
|
{
|
|
bLivePreview = !bLivePreview;
|
|
if (bLivePreview)
|
|
{
|
|
UpdatePreviewMaterial();
|
|
RegenerateCodeView();
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::IsToggleLivePreviewChecked() const
|
|
{
|
|
return bLivePreview;
|
|
}
|
|
|
|
void FMaterialEditor::ToggleRealTimeExpressions()
|
|
{
|
|
bIsRealtime = !bIsRealtime;
|
|
}
|
|
|
|
bool FMaterialEditor::IsToggleRealTimeExpressionsChecked() const
|
|
{
|
|
return bIsRealtime == true;
|
|
}
|
|
|
|
void FMaterialEditor::OnAlwaysRefreshAllPreviews()
|
|
{
|
|
bAlwaysRefreshAllPreviews = !bAlwaysRefreshAllPreviews;
|
|
if ( bAlwaysRefreshAllPreviews )
|
|
{
|
|
RefreshExpressionPreviews();
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::IsOnAlwaysRefreshAllPreviews() const
|
|
{
|
|
return bAlwaysRefreshAllPreviews == true;
|
|
}
|
|
|
|
void FMaterialEditor::ToggleStats()
|
|
{
|
|
// Toggle the showing of material stats each time the user presses the show stats button
|
|
bShowStats = !bShowStats;
|
|
UpdateMaterialInfoList(bShowStats);
|
|
}
|
|
|
|
bool FMaterialEditor::IsToggleStatsChecked() const
|
|
{
|
|
return bShowStats == true;
|
|
}
|
|
|
|
void FMaterialEditor::ToggleMobileStats()
|
|
{
|
|
// Toggle the showing of material stats each time the user presses the show stats button
|
|
bShowMobileStats = !bShowMobileStats;
|
|
UPreviewMaterial* PreviewMaterial = Cast<UPreviewMaterial>(Material);
|
|
if (PreviewMaterial)
|
|
{
|
|
{
|
|
// Sync with the rendering thread but don't reregister components. We will manually do so.
|
|
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread);
|
|
UpdateContext.AddMaterial(PreviewMaterial);
|
|
PreviewMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,bShowMobileStats);
|
|
PreviewMaterial->ForceRecompileForRendering();
|
|
if (!bStatsFromPreviewMaterial)
|
|
{
|
|
OriginalMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2,bShowMobileStats);
|
|
OriginalMaterial->ForceRecompileForRendering();
|
|
}
|
|
}
|
|
UpdateStatsMaterials();
|
|
RefreshPreviewViewport();
|
|
}
|
|
UpdateMaterialInfoList(bShowMobileStats);
|
|
}
|
|
|
|
bool FMaterialEditor::IsToggleMobileStatsChecked() const
|
|
{
|
|
return bShowMobileStats == true;
|
|
}
|
|
|
|
void FMaterialEditor::OnUseCurrentTexture()
|
|
{
|
|
// Set the currently selected texture in the generic browser
|
|
// as the texture to use in all selected texture sample expressions.
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop<UTexture>();
|
|
if ( SelectedTexture )
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "UseCurrentTexture", "Use Current Texture") );
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode && GraphNode->MaterialExpression->IsA(UMaterialExpressionTextureBase::StaticClass()) )
|
|
{
|
|
UMaterialExpressionTextureBase* TextureBase = static_cast<UMaterialExpressionTextureBase*>(GraphNode->MaterialExpression);
|
|
TextureBase->Modify();
|
|
TextureBase->Texture = SelectedTexture;
|
|
TextureBase->AutoSetSampleType();
|
|
}
|
|
}
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
RegenerateCodeView();
|
|
RefreshExpressionPreviews();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertObjects()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvert", "Material Editor: Convert") );
|
|
Material->Modify();
|
|
Material->MaterialGraph->Modify();
|
|
TArray<class UEdGraphNode*> NodesToDelete;
|
|
TArray<class UEdGraphNode*> NodesToSelect;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
// Look for the supported classes to convert from
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionConstant* Constant1Expression = Cast<UMaterialExpressionConstant>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant2Vector* Constant2Expression = Cast<UMaterialExpressionConstant2Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant3Vector* Constant3Expression = Cast<UMaterialExpressionConstant3Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionConstant4Vector* Constant4Expression = Cast<UMaterialExpressionConstant4Vector>(CurrentSelectedExpression);
|
|
UMaterialExpressionTextureSample* TextureSampleExpression = Cast<UMaterialExpressionTextureSample>(CurrentSelectedExpression);
|
|
UMaterialExpressionComponentMask* ComponentMaskExpression = Cast<UMaterialExpressionComponentMask>(CurrentSelectedExpression);
|
|
UMaterialExpressionParticleSubUV* ParticleSubUVExpression = Cast<UMaterialExpressionParticleSubUV>(CurrentSelectedExpression);
|
|
UMaterialExpressionScalarParameter* ScalarParameterExpression = Cast<UMaterialExpressionScalarParameter>(CurrentSelectedExpression);
|
|
UMaterialExpressionVectorParameter* VectorParameterExpression = Cast<UMaterialExpressionVectorParameter>(CurrentSelectedExpression);
|
|
|
|
// Setup the class to convert to
|
|
UClass* ClassToCreate = NULL;
|
|
if (Constant1Expression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionScalarParameter::StaticClass();
|
|
}
|
|
else if (Constant2Expression || Constant3Expression || Constant4Expression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionVectorParameter::StaticClass();
|
|
}
|
|
else if (ParticleSubUVExpression) // Has to come before the TextureSample comparison...
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameterSubUV::StaticClass();
|
|
}
|
|
else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCube::StaticClass()))
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameterCube::StaticClass();
|
|
}
|
|
else if (TextureSampleExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSampleParameter2D::StaticClass();
|
|
}
|
|
else if (ComponentMaskExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionStaticComponentMaskParameter::StaticClass();
|
|
}
|
|
else if (ScalarParameterExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionConstant::StaticClass();
|
|
}
|
|
else if (VectorParameterExpression)
|
|
{
|
|
// Technically should be a constant 4 but UMaterialExpressionVectorParameter has an rgb pin, so using Constant3 to avoid a compile error
|
|
ClassToCreate = UMaterialExpressionConstant3Vector::StaticClass();
|
|
}
|
|
|
|
if (ClassToCreate)
|
|
{
|
|
UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), false, true );
|
|
if (NewExpression)
|
|
{
|
|
UMaterialGraphNode* NewGraphNode = CastChecked<UMaterialGraphNode>(NewExpression->GraphNode);
|
|
NewGraphNode->ReplaceNode(GraphNode);
|
|
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Copy over 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)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureSampleParameter* NewTextureExpr = CastChecked<UMaterialExpressionTextureSampleParameter>(NewExpression);
|
|
NewTextureExpr->Texture = TextureSampleExpression->Texture;
|
|
NewTextureExpr->Coordinates = TextureSampleExpression->Coordinates;
|
|
NewTextureExpr->AutoSetSampleType();
|
|
NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture;
|
|
NewTextureExpr->TextureObject = TextureSampleExpression->TextureObject;
|
|
NewTextureExpr->MipValue = TextureSampleExpression->MipValue;
|
|
NewTextureExpr->CoordinatesDX = TextureSampleExpression->CoordinatesDX;
|
|
NewTextureExpr->CoordinatesDY = TextureSampleExpression->CoordinatesDY;
|
|
NewTextureExpr->MipValueMode = TextureSampleExpression->MipValueMode;
|
|
NewGraphNode->ReconstructNode();
|
|
}
|
|
else if (ComponentMaskExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionStaticComponentMaskParameter* ComponentMask = CastChecked<UMaterialExpressionStaticComponentMaskParameter>(NewExpression);
|
|
ComponentMask->DefaultR = ComponentMaskExpression->R;
|
|
ComponentMask->DefaultG = ComponentMaskExpression->G;
|
|
ComponentMask->DefaultB = ComponentMaskExpression->B;
|
|
ComponentMask->DefaultA = ComponentMaskExpression->A;
|
|
}
|
|
else if (ParticleSubUVExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionTextureSampleParameterSubUV>(NewExpression)->Texture = ParticleSubUVExpression->Texture;
|
|
}
|
|
else if (ScalarParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionConstant>(NewExpression)->R = ScalarParameterExpression->DefaultValue;
|
|
}
|
|
else if (VectorParameterExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
CastChecked<UMaterialExpressionConstant3Vector>(NewExpression)->Constant = VectorParameterExpression->DefaultValue;
|
|
}
|
|
|
|
if (bNeedsRefresh)
|
|
{
|
|
// Refresh the expression preview if we changed its properties after it was created
|
|
NewExpression->bNeedToUpdatePreview = true;
|
|
RefreshExpressionPreview( NewExpression, true );
|
|
}
|
|
|
|
NodesToDelete.AddUnique(GraphNode);
|
|
NodesToSelect.Add(NewGraphNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the replaced nodes
|
|
DeleteNodes(NodesToDelete);
|
|
|
|
// Select each of the newly converted expressions
|
|
for ( TArray<UEdGraphNode*>::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter )
|
|
{
|
|
GraphEditor->SetNodeSelection(*NodeIter, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnConvertTextures()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvertTexture", "Material Editor: Convert to Texture") );
|
|
Material->Modify();
|
|
Material->MaterialGraph->Modify();
|
|
TArray<class UEdGraphNode*> NodesToDelete;
|
|
TArray<class UEdGraphNode*> NodesToSelect;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
// Look for the supported classes to convert from
|
|
UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression;
|
|
UMaterialExpressionTextureSample* TextureSampleExpression = Cast<UMaterialExpressionTextureSample>(CurrentSelectedExpression);
|
|
UMaterialExpressionTextureObject* TextureObjectExpression = Cast<UMaterialExpressionTextureObject>(CurrentSelectedExpression);
|
|
|
|
// Setup the class to convert to
|
|
UClass* ClassToCreate = NULL;
|
|
if (TextureSampleExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureObject::StaticClass();
|
|
}
|
|
else if (TextureObjectExpression)
|
|
{
|
|
ClassToCreate = UMaterialExpressionTextureSample::StaticClass();
|
|
}
|
|
|
|
if (ClassToCreate)
|
|
{
|
|
UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), false, true);
|
|
if (NewExpression)
|
|
{
|
|
UMaterialGraphNode* NewGraphNode = CastChecked<UMaterialGraphNode>(NewExpression->GraphNode);
|
|
NewGraphNode->ReplaceNode(GraphNode);
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Copy over expression-specific values
|
|
if (TextureSampleExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureObject* NewTextureExpr = CastChecked<UMaterialExpressionTextureObject>(NewExpression);
|
|
NewTextureExpr->Texture = TextureSampleExpression->Texture;
|
|
NewTextureExpr->AutoSetSampleType();
|
|
NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture;
|
|
}
|
|
else if (TextureObjectExpression)
|
|
{
|
|
bNeedsRefresh = true;
|
|
UMaterialExpressionTextureSample* NewTextureExpr = CastChecked<UMaterialExpressionTextureSample>(NewExpression);
|
|
NewTextureExpr->Texture = TextureObjectExpression->Texture;
|
|
NewTextureExpr->AutoSetSampleType();
|
|
NewTextureExpr->IsDefaultMeshpaintTexture = TextureObjectExpression->IsDefaultMeshpaintTexture;
|
|
NewTextureExpr->MipValueMode = TMVM_None;
|
|
}
|
|
|
|
if (bNeedsRefresh)
|
|
{
|
|
// Refresh the expression preview if we changed its properties after it was created
|
|
NewExpression->bNeedToUpdatePreview = true;
|
|
RefreshExpressionPreview( NewExpression, true );
|
|
}
|
|
|
|
NodesToDelete.AddUnique(GraphNode);
|
|
NodesToSelect.Add(NewGraphNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the replaced nodes
|
|
DeleteNodes(NodesToDelete);
|
|
|
|
// Select each of the newly converted expressions
|
|
for ( TArray<UEdGraphNode*>::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter )
|
|
{
|
|
GraphEditor->SetNodeSelection(*NodeIter, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnPreviewNode()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
GraphEditor->NotifyGraphChanged();
|
|
SetPreviewExpression(GraphNode->MaterialExpression);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnToggleRealtimePreview()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpression* SelectedExpression = GraphNode->MaterialExpression;
|
|
SelectedExpression->bRealtimePreview = !SelectedExpression->bRealtimePreview;
|
|
|
|
if (SelectedExpression->bRealtimePreview)
|
|
{
|
|
SelectedExpression->bCollapsed = false;
|
|
}
|
|
|
|
RefreshExpressionPreviews();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectDownsteamNodes()
|
|
{
|
|
TArray<UMaterialGraphNode*> NodesToCheck;
|
|
TArray<UMaterialGraphNode*> CheckedNodes;
|
|
TArray<UMaterialGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
NodesToCheck.Add(GraphNode);
|
|
}
|
|
}
|
|
|
|
while (NodesToCheck.Num() > 0)
|
|
{
|
|
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
|
|
TArray<UEdGraphPin*> OutputPins;
|
|
CurrentNode->GetOutputPins(OutputPins);
|
|
|
|
for (int32 Index = 0; Index < OutputPins.Num(); ++Index)
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < OutputPins[Index]->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(OutputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode());
|
|
if (LinkedNode)
|
|
{
|
|
int32 FoundIndex = -1;
|
|
CheckedNodes.Find(LinkedNode, FoundIndex);
|
|
|
|
if (FoundIndex < 0)
|
|
{
|
|
NodesToSelect.Add(LinkedNode);
|
|
NodesToCheck.Add(LinkedNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This graph node has now been examined
|
|
CheckedNodes.Add(CurrentNode);
|
|
NodesToCheck.Remove(CurrentNode);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
|
|
{
|
|
GraphEditor->SetNodeSelection(NodesToSelect[Index], true);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectUpsteamNodes()
|
|
{
|
|
TArray<UMaterialGraphNode*> NodesToCheck;
|
|
TArray<UMaterialGraphNode*> CheckedNodes;
|
|
TArray<UMaterialGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
if (GraphNode)
|
|
{
|
|
NodesToCheck.Add(GraphNode);
|
|
}
|
|
}
|
|
|
|
while (NodesToCheck.Num() > 0)
|
|
{
|
|
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
|
|
TArray<UEdGraphPin*> InputPins;
|
|
CurrentNode->GetInputPins(InputPins);
|
|
|
|
for (int32 Index = 0; Index < InputPins.Num(); ++Index)
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < InputPins[Index]->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(InputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode());
|
|
if (LinkedNode)
|
|
{
|
|
int32 FoundIndex = -1;
|
|
CheckedNodes.Find(LinkedNode, FoundIndex);
|
|
|
|
if (FoundIndex < 0)
|
|
{
|
|
NodesToSelect.Add(LinkedNode);
|
|
NodesToCheck.Add(LinkedNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This graph node has now been examined
|
|
CheckedNodes.Add(CurrentNode);
|
|
NodesToCheck.Remove(CurrentNode);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
|
|
{
|
|
GraphEditor->SetNodeSelection(NodesToSelect[Index], true);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnForceRefreshPreviews()
|
|
{
|
|
ForceRefreshExpressionPreviews();
|
|
RefreshPreviewViewport();
|
|
}
|
|
|
|
void FMaterialEditor::OnCreateComment()
|
|
{
|
|
CreateNewMaterialExpressionComment(GraphEditor->GetPasteLocation());
|
|
}
|
|
|
|
void FMaterialEditor::OnCreateComponentMaskNode()
|
|
{
|
|
CreateNewMaterialExpression(UMaterialExpressionComponentMask::StaticClass(), GraphEditor->GetPasteLocation(), true, false);
|
|
}
|
|
|
|
void FMaterialEditor::OnFindInMaterial()
|
|
{
|
|
TabManager->InvokeTab(FindTabId);
|
|
FindResults->FocusForUse();
|
|
}
|
|
|
|
FString FMaterialEditor::GetDocLinkForSelectedNode()
|
|
{
|
|
FString DocumentationLink;
|
|
|
|
TArray<UObject*> SelectedNodes = GraphEditor->GetSelectedNodes().Array();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
UMaterialGraphNode* SelectedGraphNode = Cast<UMaterialGraphNode>(SelectedNodes[0]);
|
|
if (SelectedGraphNode != NULL)
|
|
{
|
|
FString DocLink = SelectedGraphNode->GetDocumentationLink();
|
|
FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName();
|
|
|
|
DocumentationLink = FEditorClassUtils::GetDocumentationLinkFromExcerpt(DocLink, DocExcerpt);
|
|
}
|
|
}
|
|
|
|
return DocumentationLink;
|
|
}
|
|
|
|
void FMaterialEditor::OnGoToDocumentation()
|
|
{
|
|
FString DocumentationLink = GetDocLinkForSelectedNode();
|
|
if (!DocumentationLink.IsEmpty())
|
|
{
|
|
IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("rightclick_matnode")));
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanGoToDocumentation()
|
|
{
|
|
FString DocumentationLink = GetDocLinkForSelectedNode();
|
|
return !DocumentationLink.IsEmpty();
|
|
}
|
|
|
|
void FMaterialEditor::RenameAssetFromRegistry(const FAssetData& InAddedAssetData, const FString& InNewName)
|
|
{
|
|
// Grab the asset class, it will be checked for being a material function.
|
|
UClass* Asset = FindObject<UClass>(ANY_PACKAGE, *InAddedAssetData.AssetClass.ToString());
|
|
|
|
if(Asset->IsChildOf(UMaterialFunction::StaticClass()))
|
|
{
|
|
ForceRefreshExpressionPreviews();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnMaterialUsageFlagsChanged(UMaterial* MaterialThatChanged, int32 FlagThatChanged)
|
|
{
|
|
EMaterialUsage Flag = static_cast<EMaterialUsage>(FlagThatChanged);
|
|
if(MaterialThatChanged == OriginalMaterial)
|
|
{
|
|
bool bNeedsRecompile = false;
|
|
Material->SetMaterialUsage(bNeedsRecompile, Flag, MaterialThatChanged->GetUsageByFlag(Flag));
|
|
UpdateStatsMaterials();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SetVectorParameterDefaultOnDependentMaterials(FName ParameterName, const FLinearColor& Value, bool bOverride)
|
|
{
|
|
TArray<UMaterial*> MaterialsToOverride;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
// Find all materials that reference this function
|
|
for (TObjectIterator<UMaterial> It; It; ++It)
|
|
{
|
|
UMaterial* CurrentMaterial = *It;
|
|
|
|
if (CurrentMaterial != Material)
|
|
{
|
|
bool bUpdate = false;
|
|
|
|
for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++)
|
|
{
|
|
if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction)
|
|
{
|
|
bUpdate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bUpdate)
|
|
{
|
|
MaterialsToOverride.Add(CurrentMaterial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MaterialsToOverride.Add(OriginalMaterial);
|
|
}
|
|
|
|
const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel;
|
|
|
|
for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++)
|
|
{
|
|
UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex];
|
|
|
|
CurrentMaterial->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel);
|
|
}
|
|
|
|
// Update MI's that reference any of the materials affected
|
|
for (TObjectIterator<UMaterialInstance> It; It; ++It)
|
|
{
|
|
UMaterialInstance* CurrentMaterialInstance = *It;
|
|
|
|
// Only care about MI's with static parameters, because we are overriding parameter defaults,
|
|
// And only MI's with static parameters contain uniform expressions, which contain parameter defaults
|
|
if (CurrentMaterialInstance->bHasStaticPermutationResource)
|
|
{
|
|
UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial();
|
|
|
|
if (MaterialsToOverride.Contains(BaseMaterial))
|
|
{
|
|
CurrentMaterialInstance->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnVectorParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, const FLinearColor& Value)
|
|
{
|
|
check(Expression);
|
|
|
|
if (Expression->Material == Material && OriginalMaterial)
|
|
{
|
|
SetVectorParameterDefaultOnDependentMaterials(ParameterName, Value, true);
|
|
|
|
OverriddenVectorParametersToRevert.AddUnique(ParameterName);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::SetScalarParameterDefaultOnDependentMaterials(FName ParameterName, float Value, bool bOverride)
|
|
{
|
|
TArray<UMaterial*> MaterialsToOverride;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
// Find all materials that reference this function
|
|
for (TObjectIterator<UMaterial> It; It; ++It)
|
|
{
|
|
UMaterial* CurrentMaterial = *It;
|
|
|
|
if (CurrentMaterial != Material)
|
|
{
|
|
bool bUpdate = false;
|
|
|
|
for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++)
|
|
{
|
|
if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction)
|
|
{
|
|
bUpdate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bUpdate)
|
|
{
|
|
MaterialsToOverride.Add(CurrentMaterial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MaterialsToOverride.Add(OriginalMaterial);
|
|
}
|
|
|
|
const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel;
|
|
|
|
for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++)
|
|
{
|
|
UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex];
|
|
|
|
CurrentMaterial->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel);
|
|
}
|
|
|
|
// Update MI's that reference any of the materials affected
|
|
for (TObjectIterator<UMaterialInstance> It; It; ++It)
|
|
{
|
|
UMaterialInstance* CurrentMaterialInstance = *It;
|
|
|
|
// Only care about MI's with static parameters, because we are overriding parameter defaults,
|
|
// And only MI's with static parameters contain uniform expressions, which contain parameter defaults
|
|
if (CurrentMaterialInstance->bHasStaticPermutationResource)
|
|
{
|
|
UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial();
|
|
|
|
if (MaterialsToOverride.Contains(BaseMaterial))
|
|
{
|
|
CurrentMaterialInstance->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnScalarParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, float Value)
|
|
{
|
|
check(Expression);
|
|
|
|
if (Expression->Material == Material && OriginalMaterial)
|
|
{
|
|
SetScalarParameterDefaultOnDependentMaterials(ParameterName, Value, true);
|
|
|
|
OverriddenScalarParametersToRevert.AddUnique(ParameterName);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Preview(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab =
|
|
SNew(SDockTab)
|
|
.Label(LOCTEXT("ViewportTabTitle", "Viewport"))
|
|
[
|
|
SNew( SOverlay )
|
|
+ SOverlay::Slot()
|
|
[
|
|
PreviewViewport.ToSharedRef()
|
|
]
|
|
+ SOverlay::Slot()
|
|
[
|
|
PreviewUIViewport.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
PreviewViewport->OnAddedToTab( SpawnedTab );
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("GraphCanvasTitle", "Graph"));
|
|
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
SpawnedTab->SetContent(GraphEditor.ToSharedRef());
|
|
}
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_MaterialProperties(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon( FEditorStyle::GetBrush("LevelEditor.Tabs.Details") )
|
|
.Label( LOCTEXT("MaterialDetailsTitle", "Details") )
|
|
[
|
|
MaterialDetailsView.ToSharedRef()
|
|
];
|
|
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
// Since we're initialising, make sure nothing is selected
|
|
GraphEditor->ClearSelectionSet();
|
|
}
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_HLSLCode(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("HLSLCodeTitle", "HLSL Code"))
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
CodeViewUtility.ToSharedRef()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
CodeView.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
CodeTab = SpawnedTab;
|
|
|
|
RegenerateCodeView();
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args)
|
|
{
|
|
check( Args.GetTabId() == PaletteTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.Palette"))
|
|
.Label(LOCTEXT("MaterialPaletteTitle", "Palette"))
|
|
[
|
|
SNew( SBox )
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialPalette")))
|
|
[
|
|
Palette.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Stats(const FSpawnTabArgs& Args)
|
|
{
|
|
check( Args.GetTabId() == StatsTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.CompilerResults"))
|
|
.Label(LOCTEXT("MaterialStatsTitle", "Stats"))
|
|
[
|
|
SNew( SBox )
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialStats")))
|
|
[
|
|
Stats.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FMaterialEditor::SpawnTab_Find(const FSpawnTabArgs& Args)
|
|
{
|
|
check(Args.GetTabId() == FindTabId);
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("Kismet.Tabs.FindResults"))
|
|
.Label(LOCTEXT("MaterialFindTitle", "Find Results"))
|
|
[
|
|
SNew(SBox)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialFind")))
|
|
[
|
|
FindResults.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
void FMaterialEditor::SetPreviewExpression(UMaterialExpression* NewPreviewExpression)
|
|
{
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(NewPreviewExpression);
|
|
|
|
if (!NewPreviewExpression || PreviewExpression == NewPreviewExpression)
|
|
{
|
|
if (FunctionOutput)
|
|
{
|
|
FunctionOutput->bLastPreviewed = false;
|
|
}
|
|
// If we are already previewing the selected expression toggle previewing off
|
|
PreviewExpression = NULL;
|
|
ExpressionPreviewMaterial->Expressions.Empty();
|
|
SetPreviewMaterial( Material );
|
|
// Recompile the preview material to get changes that might have been made during previewing
|
|
UpdatePreviewMaterial();
|
|
}
|
|
else if (NewPreviewExpression)
|
|
{
|
|
if( ExpressionPreviewMaterial == NULL )
|
|
{
|
|
// Create the expression preview material if it hasnt already been created
|
|
ExpressionPreviewMaterial = NewObject<UPreviewMaterial>(GetTransientPackage(), NAME_None, RF_Public);
|
|
ExpressionPreviewMaterial->bIsPreviewMaterial = true;
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
|
|
}
|
|
}
|
|
|
|
if (FunctionOutput)
|
|
{
|
|
FunctionOutput->bLastPreviewed = true;
|
|
}
|
|
else
|
|
{
|
|
//Hooking up the output of the break expression doesn't make much sense, preview the expression feeding it instead.
|
|
UMaterialExpressionBreakMaterialAttributes* BreakExpr = Cast<UMaterialExpressionBreakMaterialAttributes>(NewPreviewExpression);
|
|
if( BreakExpr && BreakExpr->GetInput(0) && BreakExpr->GetInput(0)->Expression )
|
|
{
|
|
NewPreviewExpression = BreakExpr->GetInput(0)->Expression;
|
|
}
|
|
}
|
|
|
|
// The expression preview material's expressions array must stay up to date before recompiling
|
|
// So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated
|
|
ExpressionPreviewMaterial->Expressions = Material->Expressions;
|
|
|
|
// The preview window should now show the expression preview material
|
|
SetPreviewMaterial( ExpressionPreviewMaterial );
|
|
|
|
// Set the preview expression
|
|
PreviewExpression = NewPreviewExpression;
|
|
|
|
// Recompile the preview material
|
|
UpdatePreviewMaterial();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::JumpToNode(const UEdGraphNode* Node)
|
|
{
|
|
GraphEditor->JumpToNode(Node, false);
|
|
}
|
|
|
|
UMaterialExpression* FMaterialEditor::CreateNewMaterialExpression(UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource)
|
|
{
|
|
check( NewExpressionClass->IsChildOf(UMaterialExpression::StaticClass()) );
|
|
|
|
if (!IsAllowedExpressionType(NewExpressionClass, MaterialFunction != NULL))
|
|
{
|
|
// Disallowed types should not be visible to the ui to be placed, so we don't need a warning here
|
|
return NULL;
|
|
}
|
|
|
|
// Clear the selection
|
|
if ( bAutoSelect )
|
|
{
|
|
GraphEditor->ClearSelectionSet();
|
|
}
|
|
|
|
// Create the new expression.
|
|
UMaterialExpression* NewExpression = NULL;
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorNewExpression", "Material Editor: New Expression") );
|
|
Material->Modify();
|
|
|
|
UObject* ExpressionOuter = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
ExpressionOuter = MaterialFunction;
|
|
}
|
|
|
|
NewExpression = NewObject<UMaterialExpression>(ExpressionOuter, NewExpressionClass, NAME_None, RF_Transactional);
|
|
Material->Expressions.Add( NewExpression );
|
|
NewExpression->Material = Material;
|
|
|
|
// Set the expression location.
|
|
NewExpression->MaterialExpressionEditorX = NodePos.X;
|
|
NewExpression->MaterialExpressionEditorY = NodePos.Y;
|
|
|
|
// Create a GUID for the node
|
|
NewExpression->UpdateMaterialExpressionGuid(true, true);
|
|
|
|
if (bAutoAssignResource)
|
|
{
|
|
// If the user is adding a texture, automatically assign the currently selected texture to it.
|
|
UMaterialExpressionTextureBase* METextureBase = Cast<UMaterialExpressionTextureBase>( NewExpression );
|
|
if( METextureBase )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
if( UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop<UTexture>() )
|
|
{
|
|
METextureBase->Texture = SelectedTexture;
|
|
}
|
|
METextureBase->AutoSetSampleType();
|
|
}
|
|
|
|
UMaterialExpressionMaterialFunctionCall* MEMaterialFunction = Cast<UMaterialExpressionMaterialFunctionCall>( NewExpression );
|
|
if( MEMaterialFunction )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
MEMaterialFunction->SetMaterialFunction(MaterialFunction, NULL, GEditor->GetSelectedObjects()->GetTop<UMaterialFunction>());
|
|
}
|
|
|
|
UMaterialExpressionCollectionParameter* MECollectionParameter = Cast<UMaterialExpressionCollectionParameter>( NewExpression );
|
|
if( MECollectionParameter )
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
MECollectionParameter->Collection = GEditor->GetSelectedObjects()->GetTop<UMaterialParameterCollection>();
|
|
}
|
|
}
|
|
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>( NewExpression );
|
|
if( FunctionInput )
|
|
{
|
|
FunctionInput->ConditionallyGenerateId(true);
|
|
FunctionInput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>( NewExpression );
|
|
if( FunctionOutput )
|
|
{
|
|
FunctionOutput->ConditionallyGenerateId(true);
|
|
FunctionOutput->ValidateName();
|
|
}
|
|
|
|
NewExpression->UpdateParameterGuid(true, true);
|
|
|
|
UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast<UMaterialExpressionTextureSampleParameter>( NewExpression );
|
|
if( (TextureParameterExpression != nullptr) && TextureParameterExpression->CanRenameNode() )
|
|
{
|
|
// Change the parameter's name on creation to mirror the object's name; this avoids issues of having colliding parameter
|
|
// names and having the name left as "None"
|
|
TextureParameterExpression->ParameterName = TextureParameterExpression->GetFName();
|
|
}
|
|
|
|
UMaterialExpressionComponentMask* ComponentMaskExpression = Cast<UMaterialExpressionComponentMask>( NewExpression );
|
|
// Setup defaults for the most likely use case
|
|
// Can't change default properties as that will affect existing content
|
|
if( ComponentMaskExpression )
|
|
{
|
|
ComponentMaskExpression->R = true;
|
|
ComponentMaskExpression->G = true;
|
|
}
|
|
|
|
UMaterialExpressionStaticComponentMaskParameter* StaticComponentMaskExpression = Cast<UMaterialExpressionStaticComponentMaskParameter>( NewExpression );
|
|
// Setup defaults for the most likely use case
|
|
// Can't change default properties as that will affect existing content
|
|
if( StaticComponentMaskExpression )
|
|
{
|
|
StaticComponentMaskExpression->DefaultR = true;
|
|
}
|
|
|
|
// Setup defaults for the most likely use case
|
|
// Can't change default properties as that will affect existing content
|
|
UMaterialExpressionTransformPosition* PositionTransform = Cast<UMaterialExpressionTransformPosition>(NewExpression);
|
|
if (PositionTransform)
|
|
{
|
|
PositionTransform->TransformSourceType = TRANSFORMPOSSOURCE_Local;
|
|
PositionTransform->TransformType = TRANSFORMPOSSOURCE_World;
|
|
}
|
|
|
|
// Make sure the dynamic parameters are named based on existing ones
|
|
UMaterialExpressionDynamicParameter* DynamicExpression = Cast<UMaterialExpressionDynamicParameter>(NewExpression);
|
|
if (DynamicExpression)
|
|
{
|
|
DynamicExpression->UpdateDynamicParameterProperties();
|
|
}
|
|
|
|
Material->AddExpressionParameter(NewExpression, Material->EditorParameters);
|
|
|
|
if (NewExpression)
|
|
{
|
|
Material->MaterialGraph->AddExpression(NewExpression);
|
|
|
|
// Select the new node.
|
|
if ( bAutoSelect )
|
|
{
|
|
GraphEditor->SetNodeSelection(NewExpression->GraphNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
RegenerateCodeView();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
|
|
RefreshExpressionPreviews();
|
|
GraphEditor->NotifyGraphChanged();
|
|
SetMaterialDirty();
|
|
return NewExpression;
|
|
}
|
|
|
|
UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos)
|
|
{
|
|
UMaterialExpressionComment* NewComment = NULL;
|
|
{
|
|
Material->Modify();
|
|
|
|
UObject* ExpressionOuter = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
ExpressionOuter = MaterialFunction;
|
|
}
|
|
|
|
NewComment = NewObject<UMaterialExpressionComment>(ExpressionOuter, NAME_None, RF_Transactional);
|
|
|
|
// Add to the list of comments associated with this material.
|
|
Material->EditorComments.Add( NewComment );
|
|
|
|
FSlateRect Bounds;
|
|
if (GraphEditor->GetBoundsForSelectedNodes(Bounds, 50.0f))
|
|
{
|
|
NewComment->MaterialExpressionEditorX = Bounds.Left;
|
|
NewComment->MaterialExpressionEditorY = Bounds.Top;
|
|
|
|
FVector2D Size = Bounds.GetSize();
|
|
NewComment->SizeX = Size.X;
|
|
NewComment->SizeY = Size.Y;
|
|
}
|
|
else
|
|
{
|
|
|
|
NewComment->MaterialExpressionEditorX = NodePos.X;
|
|
NewComment->MaterialExpressionEditorY = NodePos.Y;
|
|
NewComment->SizeX = 400;
|
|
NewComment->SizeY = 100;
|
|
}
|
|
|
|
NewComment->Text = NSLOCTEXT("K2Node", "CommentBlock_NewEmptyComment", "Comment").ToString();
|
|
}
|
|
|
|
if (NewComment)
|
|
{
|
|
Material->MaterialGraph->AddComment(NewComment, true);
|
|
|
|
// Select the new comment.
|
|
GraphEditor->ClearSelectionSet();
|
|
GraphEditor->SetNodeSelection(NewComment->GraphNode, true);
|
|
}
|
|
|
|
Material->MarkPackageDirty();
|
|
GraphEditor->NotifyGraphChanged();
|
|
SetMaterialDirty();
|
|
return NewComment;
|
|
}
|
|
|
|
void FMaterialEditor::ForceRefreshExpressionPreviews()
|
|
{
|
|
// Initialize expression previews.
|
|
const bool bOldAlwaysRefreshAllPreviews = bAlwaysRefreshAllPreviews;
|
|
bAlwaysRefreshAllPreviews = true;
|
|
RefreshExpressionPreviews();
|
|
bAlwaysRefreshAllPreviews = bOldAlwaysRefreshAllPreviews;
|
|
}
|
|
|
|
void FMaterialEditor::AddToSelection(UMaterialExpression* Expression)
|
|
{
|
|
GraphEditor->SetNodeSelection(Expression->GraphNode, true);
|
|
}
|
|
|
|
void FMaterialEditor::SelectAllNodes()
|
|
{
|
|
GraphEditor->SelectAllNodes();
|
|
}
|
|
|
|
bool FMaterialEditor::CanSelectAllNodes() const
|
|
{
|
|
return GraphEditor.IsValid();
|
|
}
|
|
|
|
void FMaterialEditor::DeleteSelectedNodes()
|
|
{
|
|
TArray<UEdGraphNode*> NodesToDelete;
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
NodesToDelete.Add(CastChecked<UEdGraphNode>(*NodeIt));
|
|
}
|
|
|
|
DeleteNodes(NodesToDelete);
|
|
}
|
|
|
|
void FMaterialEditor::DeleteNodes(const TArray<UEdGraphNode*>& NodesToDelete)
|
|
{
|
|
if (NodesToDelete.Num() > 0)
|
|
{
|
|
if (!CheckExpressionRemovalWarnings(NodesToDelete))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If we are previewing an expression and the expression being previewed was deleted
|
|
bool bHaveExpressionsToDelete = false;
|
|
bool bPreviewExpressionDeleted = false;
|
|
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorDelete", "Material Editor: Delete") );
|
|
Material->Modify();
|
|
|
|
for (int32 Index = 0; Index < NodesToDelete.Num(); ++Index)
|
|
{
|
|
if (NodesToDelete[Index]->CanUserDeleteNode())
|
|
{
|
|
// Break all node links first so that we don't update the material before deleting
|
|
NodesToDelete[Index]->BreakAllNodeLinks();
|
|
|
|
FBlueprintEditorUtils::RemoveNode(NULL, NodesToDelete[Index], true);
|
|
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(NodesToDelete[Index]))
|
|
{
|
|
UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression;
|
|
|
|
bHaveExpressionsToDelete = true;
|
|
|
|
DestroyColorPicker();
|
|
|
|
if( PreviewExpression == MaterialExpression )
|
|
{
|
|
// The expression being previewed is also being deleted
|
|
bPreviewExpressionDeleted = true;
|
|
}
|
|
|
|
MaterialExpression->Modify();
|
|
Material->Expressions.Remove( MaterialExpression );
|
|
Material->RemoveExpressionParameter(MaterialExpression);
|
|
// Make sure the deleted expression is caught by gc
|
|
MaterialExpression->MarkPendingKill();
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(NodesToDelete[Index]))
|
|
{
|
|
CommentNode->MaterialExpressionComment->Modify();
|
|
Material->EditorComments.Remove( CommentNode->MaterialExpressionComment );
|
|
}
|
|
}
|
|
}
|
|
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
} // ScopedTransaction
|
|
|
|
// Deselect all expressions and comments.
|
|
GraphEditor->ClearSelectionSet();
|
|
GraphEditor->NotifyGraphChanged();
|
|
|
|
if ( bHaveExpressionsToDelete )
|
|
{
|
|
if( bPreviewExpressionDeleted )
|
|
{
|
|
// The preview expression was deleted. Null out our reference to it and reset to the normal preview material
|
|
PreviewExpression = NULL;
|
|
SetPreviewMaterial( Material );
|
|
}
|
|
RegenerateCodeView();
|
|
}
|
|
UpdatePreviewMaterial();
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
|
|
if ( bHaveExpressionsToDelete )
|
|
{
|
|
RefreshExpressionPreviews();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanDeleteNodes() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (Cast<UMaterialGraphNode_Root>(*NodeIt))
|
|
{
|
|
// Return false if only root node is selected, as it can't be deleted
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SelectedNodes.Num() > 0;
|
|
}
|
|
|
|
void FMaterialEditor::DeleteSelectedDuplicatableNodes()
|
|
{
|
|
// Cache off the old selection
|
|
const FGraphPanelSelectionSet OldSelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
// Clear the selection and only select the nodes that can be duplicated
|
|
FGraphPanelSelectionSet RemainingNodes;
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != NULL) && Node->CanDuplicateNode())
|
|
{
|
|
GraphEditor->SetNodeSelection(Node, true);
|
|
}
|
|
else
|
|
{
|
|
RemainingNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Delete the duplicatable nodes
|
|
DeleteSelectedNodes();
|
|
|
|
// Reselect whatever's left from the original selection after the deletion
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
|
|
{
|
|
GraphEditor->SetNodeSelection(Node, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::CopySelectedNodes()
|
|
{
|
|
// Export the selected nodes and place the text on the clipboard
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
FString ExportedText;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if(UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
|
|
{
|
|
Node->PrepareForCopying();
|
|
}
|
|
}
|
|
|
|
FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText);
|
|
FPlatformMisc::ClipboardCopy(*ExportedText);
|
|
|
|
// Make sure Material remains the owner of the copied nodes
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if (UMaterialGraphNode* Node = Cast<UMaterialGraphNode>(*SelectedIter))
|
|
{
|
|
Node->PostCopyNode();
|
|
}
|
|
else if (UMaterialGraphNode_Comment* Comment = Cast<UMaterialGraphNode_Comment>(*SelectedIter))
|
|
{
|
|
Comment->PostCopyNode();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CanCopyNodes() const
|
|
{
|
|
// If any of the nodes can be duplicated then we should allow copying
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != NULL) && Node->CanDuplicateNode())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FMaterialEditor::PasteNodes()
|
|
{
|
|
PasteNodesHere(GraphEditor->GetPasteLocation());
|
|
}
|
|
|
|
void FMaterialEditor::PasteNodesHere(const FVector2D& Location)
|
|
{
|
|
// Undo/Redo support
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorPaste", "Material Editor: Paste") );
|
|
Material->MaterialGraph->Modify();
|
|
Material->Modify();
|
|
|
|
// Clear the selection set (newly pasted stuff will be selected)
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
// Grab the text to paste from the clipboard.
|
|
FString TextToImport;
|
|
FPlatformMisc::ClipboardPaste(TextToImport);
|
|
|
|
// Import the nodes
|
|
TSet<UEdGraphNode*> PastedNodes;
|
|
FEdGraphUtilities::ImportNodesFromText(Material->MaterialGraph, TextToImport, /*out*/ PastedNodes);
|
|
|
|
//Average position of nodes so we can move them while still maintaining relative distances to each other
|
|
FVector2D AvgNodePosition(0.0f,0.0f);
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
AvgNodePosition.X += Node->NodePosX;
|
|
AvgNodePosition.Y += Node->NodePosY;
|
|
}
|
|
|
|
if ( PastedNodes.Num() > 0 )
|
|
{
|
|
float InvNumNodes = 1.0f/float(PastedNodes.Num());
|
|
AvgNodePosition.X *= InvNumNodes;
|
|
AvgNodePosition.Y *= InvNumNodes;
|
|
}
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(Node))
|
|
{
|
|
// These are not copied and we must account for expressions pasted between different materials anyway
|
|
GraphNode->RealtimeDelegate = Material->MaterialGraph->RealtimeDelegate;
|
|
GraphNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate;
|
|
GraphNode->bPreviewNeedsUpdate = false;
|
|
|
|
UMaterialExpression* NewExpression = GraphNode->MaterialExpression;
|
|
NewExpression->Material = Material;
|
|
NewExpression->Function = MaterialFunction;
|
|
Material->Expressions.Add(NewExpression);
|
|
|
|
// There can be only one default mesh paint texture.
|
|
UMaterialExpressionTextureBase* TextureSample = Cast<UMaterialExpressionTextureBase>( NewExpression );
|
|
if( TextureSample )
|
|
{
|
|
TextureSample->IsDefaultMeshpaintTexture = false;
|
|
}
|
|
|
|
NewExpression->UpdateParameterGuid(true, true);
|
|
Material->AddExpressionParameter(NewExpression, Material->EditorParameters);
|
|
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>( NewExpression );
|
|
if( FunctionInput )
|
|
{
|
|
FunctionInput->ConditionallyGenerateId(true);
|
|
FunctionInput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>( NewExpression );
|
|
if( FunctionOutput )
|
|
{
|
|
FunctionOutput->ConditionallyGenerateId(true);
|
|
FunctionOutput->ValidateName();
|
|
}
|
|
|
|
UMaterialExpressionMaterialFunctionCall* FunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>( NewExpression );
|
|
if( FunctionCall )
|
|
{
|
|
// When pasting new nodes, we don't want to break all node links as this information is used by UpdateMaterialAfterGraphChange() below,
|
|
// to recreate all the connections in the pasted group.
|
|
// Just update the function input/outputs here.
|
|
const bool bRecreateAndLinkNode = false;
|
|
FunctionCall->UpdateFromFunctionResource(bRecreateAndLinkNode);
|
|
|
|
// If an unknown material function has been pasted, remove the graph node pins (as the expression will also have had its inputs/outputs removed).
|
|
// This will be displayed as an orphaned "Unspecified Function" node.
|
|
if (FunctionCall->MaterialFunction == nullptr &&
|
|
FunctionCall->FunctionInputs.Num() == 0 &&
|
|
FunctionCall->FunctionOutputs.Num() == 0)
|
|
{
|
|
GraphNode->Pins.Empty();
|
|
}
|
|
}
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(Node))
|
|
{
|
|
CommentNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate;
|
|
CommentNode->MaterialExpressionComment->Material = Material;
|
|
CommentNode->MaterialExpressionComment->Function = MaterialFunction;
|
|
Material->EditorComments.Add(CommentNode->MaterialExpressionComment);
|
|
}
|
|
|
|
// Select the newly pasted stuff
|
|
GraphEditor->SetNodeSelection(Node, true);
|
|
|
|
Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X ;
|
|
Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y ;
|
|
|
|
Node->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
// Give new node a different Guid from the old one
|
|
Node->CreateNewGuid();
|
|
}
|
|
|
|
UpdateMaterialAfterGraphChange();
|
|
|
|
// Update UI
|
|
GraphEditor->NotifyGraphChanged();
|
|
}
|
|
|
|
bool FMaterialEditor::CanPasteNodes() const
|
|
{
|
|
FString ClipboardContent;
|
|
FPlatformMisc::ClipboardPaste(ClipboardContent);
|
|
|
|
return FEdGraphUtilities::CanImportNodesFromText(Material->MaterialGraph, ClipboardContent);
|
|
}
|
|
|
|
void FMaterialEditor::CutSelectedNodes()
|
|
{
|
|
CopySelectedNodes();
|
|
// Cut should only delete nodes that can be duplicated
|
|
DeleteSelectedDuplicatableNodes();
|
|
}
|
|
|
|
bool FMaterialEditor::CanCutNodes() const
|
|
{
|
|
return CanCopyNodes() && CanDeleteNodes();
|
|
}
|
|
|
|
void FMaterialEditor::DuplicateNodes()
|
|
{
|
|
// Copy and paste current selection
|
|
CopySelectedNodes();
|
|
PasteNodes();
|
|
}
|
|
|
|
bool FMaterialEditor::CanDuplicateNodes() const
|
|
{
|
|
return CanCopyNodes();
|
|
}
|
|
|
|
FText FMaterialEditor::GetOriginalObjectName() const
|
|
{
|
|
return FText::FromString(GetEditingObjects()[0]->GetName());
|
|
}
|
|
|
|
void FMaterialEditor::UpdateMaterialAfterGraphChange()
|
|
{
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
|
|
Material->MarkPackageDirty();
|
|
RegenerateCodeView();
|
|
RefreshExpressionPreviews();
|
|
SetMaterialDirty();
|
|
}
|
|
|
|
int32 FMaterialEditor::GetNumberOfSelectedNodes() const
|
|
{
|
|
return GraphEditor->GetSelectedNodes().Num();
|
|
}
|
|
|
|
FMaterialRenderProxy* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression)
|
|
{
|
|
bool bNewlyCreated;
|
|
return GetExpressionPreview(InExpression, bNewlyCreated);
|
|
}
|
|
|
|
void FMaterialEditor::UndoGraphAction()
|
|
{
|
|
int32 NumExpressions = Material->Expressions.Num();
|
|
GEditor->UndoTransaction();
|
|
|
|
if(NumExpressions != Material->Expressions.Num())
|
|
{
|
|
Material->BuildEditorParameterList();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RedoGraphAction()
|
|
{
|
|
// Clear selection, to avoid holding refs to nodes that go away
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
int32 NumExpressions = Material->Expressions.Num();
|
|
GEditor->RedoTransaction();
|
|
|
|
if(NumExpressions != Material->Expressions.Num())
|
|
{
|
|
Material->BuildEditorParameterList();
|
|
}
|
|
|
|
}
|
|
|
|
void FMaterialEditor::PostUndo(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
Material->BuildEditorParameterList();
|
|
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
|
|
RefreshExpressionPreviews();
|
|
GraphEditor->NotifyGraphChanged();
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::NotifyPreChange(UProperty* PropertyAboutToChange)
|
|
{
|
|
check( !ScopedTransaction );
|
|
ScopedTransaction = new FScopedTransaction( NSLOCTEXT("UnrealEd", "MaterialEditorEditProperties", "Material Editor: Edit Properties") );
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged)
|
|
{
|
|
check( ScopedTransaction );
|
|
|
|
if ( PropertyThatChanged )
|
|
{
|
|
const FName NameOfPropertyThatChanged( *PropertyThatChanged->GetName() );
|
|
if ((NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialInterface, PreviewMesh)) ||
|
|
(NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, bUsedWithSkeletalMesh)))
|
|
{
|
|
// SetPreviewMesh will return false if the material has bUsedWithSkeletalMesh and
|
|
// a skeleton was requested, in which case revert to a sphere static mesh.
|
|
if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString()))
|
|
{
|
|
SetPreviewAsset(GUnrealEd->GetThumbnailManager()->EditorSphere);
|
|
}
|
|
}
|
|
else if( NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, MaterialDomain) ||
|
|
NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, ShadingModel) )
|
|
{
|
|
Material->MaterialGraph->RebuildGraph();
|
|
TArray<TWeakObjectPtr<UObject>> SelectedObjects = MaterialDetailsView->GetSelectedObjects();
|
|
MaterialDetailsView->SetObjects( SelectedObjects, true );
|
|
|
|
if (ExpressionPreviewMaterial)
|
|
{
|
|
if (Material->IsUIMaterial())
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
|
|
}
|
|
else
|
|
{
|
|
ExpressionPreviewMaterial->MaterialDomain = MD_Surface;
|
|
}
|
|
|
|
SetPreviewMaterial(ExpressionPreviewMaterial);
|
|
}
|
|
|
|
UpdatePreviewViewportsVisibility();
|
|
}
|
|
|
|
FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UMaterialGraphNode* SelectedNode = Cast<UMaterialGraphNode>(*NodeIt);
|
|
|
|
if (SelectedNode && SelectedNode->MaterialExpression)
|
|
{
|
|
if(NameOfPropertyThatChanged == FName(TEXT("ParameterName")))
|
|
{
|
|
Material->UpdateExpressionParameterName(SelectedNode->MaterialExpression);
|
|
}
|
|
else if (SelectedNode->MaterialExpression->IsA(UMaterialExpressionDynamicParameter::StaticClass()))
|
|
{
|
|
Material->UpdateExpressionDynamicParameters(SelectedNode->MaterialExpression);
|
|
}
|
|
else
|
|
{
|
|
Material->PropagateExpressionParameterChanges(SelectedNode->MaterialExpression);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prevent constant recompilation of materials while properties are being interacted with
|
|
if( PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive )
|
|
{
|
|
// Also prevent recompilation when properties have no effect on material output
|
|
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
|
|
if (PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, Text)
|
|
&& PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, CommentColor)
|
|
&& PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpression, Desc))
|
|
{
|
|
// Update the current preview material.
|
|
UpdatePreviewMaterial();
|
|
RefreshExpressionPreviews();
|
|
RegenerateCodeView();
|
|
}
|
|
|
|
GetDefault<UMaterialGraphSchema>()->ForceVisualizationCacheClear();
|
|
}
|
|
|
|
delete ScopedTransaction;
|
|
ScopedTransaction = NULL;
|
|
|
|
Material->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
}
|
|
|
|
void FMaterialEditor::ToggleCollapsed(UMaterialExpression* MaterialExpression)
|
|
{
|
|
check( MaterialExpression );
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorToggleCollapsed", "Material Editor: Toggle Collapsed") );
|
|
MaterialExpression->Modify();
|
|
MaterialExpression->bCollapsed = !MaterialExpression->bCollapsed;
|
|
}
|
|
MaterialExpression->PreEditChange( NULL );
|
|
MaterialExpression->PostEditChange();
|
|
MaterialExpression->MarkPackageDirty();
|
|
SetMaterialDirty();
|
|
|
|
// Update the preview.
|
|
RefreshExpressionPreview( MaterialExpression, true );
|
|
RefreshPreviewViewport();
|
|
}
|
|
|
|
void FMaterialEditor::RefreshExpressionPreviews()
|
|
{
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
if ( bAlwaysRefreshAllPreviews )
|
|
{
|
|
// we need to make sure the rendering thread isn't drawing these tiles
|
|
SCOPED_SUSPEND_RENDERING_THREAD(true);
|
|
|
|
// Refresh all expression previews.
|
|
ExpressionPreviews.Empty();
|
|
}
|
|
else
|
|
{
|
|
// Only refresh expressions that are marked for realtime update.
|
|
for ( int32 ExpressionIndex = 0 ; ExpressionIndex < Material->Expressions.Num() ; ++ExpressionIndex )
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ];
|
|
RefreshExpressionPreview( MaterialExpression, false );
|
|
}
|
|
}
|
|
|
|
TArray<FMatExpressionPreview*> ExpressionPreviewsBeingCompiled;
|
|
ExpressionPreviewsBeingCompiled.Empty(50);
|
|
|
|
// Go through all expression previews and create new ones as needed, and maintain a list of previews that are being compiled
|
|
for( int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ++ExpressionIndex )
|
|
{
|
|
UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ];
|
|
if (MaterialExpression && !MaterialExpression->IsA(UMaterialExpressionComment::StaticClass()) )
|
|
{
|
|
bool bNewlyCreated;
|
|
FMatExpressionPreview* Preview = GetExpressionPreview( MaterialExpression, bNewlyCreated );
|
|
if (bNewlyCreated && Preview)
|
|
{
|
|
ExpressionPreviewsBeingCompiled.Add(Preview);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::RefreshExpressionPreview(UMaterialExpression* MaterialExpression, bool bRecompile)
|
|
{
|
|
if ( (MaterialExpression->bRealtimePreview || MaterialExpression->bNeedToUpdatePreview) && !MaterialExpression->bCollapsed )
|
|
{
|
|
for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex )
|
|
{
|
|
FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex];
|
|
if( ExpressionPreview.GetExpression() == MaterialExpression )
|
|
{
|
|
// we need to make sure the rendering thread isn't drawing this tile
|
|
SCOPED_SUSPEND_RENDERING_THREAD(true);
|
|
ExpressionPreviews.RemoveAt( PreviewIndex );
|
|
MaterialExpression->bNeedToUpdatePreview = false;
|
|
if (bRecompile)
|
|
{
|
|
bool bNewlyCreated;
|
|
GetExpressionPreview(MaterialExpression, bNewlyCreated);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* MaterialExpression, bool& bNewlyCreated)
|
|
{
|
|
bNewlyCreated = false;
|
|
if (!MaterialExpression->bHidePreviewWindow && !MaterialExpression->bCollapsed)
|
|
{
|
|
FMatExpressionPreview* Preview = NULL;
|
|
for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex )
|
|
{
|
|
FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex];
|
|
if( ExpressionPreview.GetExpression() == MaterialExpression )
|
|
{
|
|
Preview = &ExpressionPreviews[PreviewIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !Preview )
|
|
{
|
|
bNewlyCreated = true;
|
|
Preview = new(ExpressionPreviews) FMatExpressionPreview(MaterialExpression);
|
|
Preview->CacheShaders(GMaxRHIShaderPlatform, true);
|
|
}
|
|
return Preview;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void FMaterialEditor::PreColorPickerCommit(FLinearColor LinearColor)
|
|
{
|
|
// Begin a property edit transaction.
|
|
if ( GEditor )
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT("ModifyColorPicker", "Modify Color Picker Value") );
|
|
}
|
|
|
|
NotifyPreChange(NULL);
|
|
|
|
UObject* Object = ColorPickerObject.Get(false);
|
|
if( Object )
|
|
{
|
|
Object->PreEditChange(NULL);
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnColorPickerCommitted(FLinearColor LinearColor)
|
|
{
|
|
UObject* Object = ColorPickerObject.Get(false);
|
|
if( Object )
|
|
{
|
|
Object->MarkPackageDirty();
|
|
FPropertyChangedEvent Event(ColorPickerProperty.Get(false));
|
|
Object->PostEditChangeProperty(Event);
|
|
}
|
|
|
|
NotifyPostChange(NULL,NULL);
|
|
|
|
if ( GEditor )
|
|
{
|
|
GEditor->EndTransaction();
|
|
}
|
|
|
|
RefreshExpressionPreviews();
|
|
}
|
|
|
|
TSharedRef<SGraphEditor> FMaterialEditor::CreateGraphEditorWidget()
|
|
{
|
|
GraphEditorCommands = MakeShareable( new FUICommandList );
|
|
{
|
|
// Editing commands
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::SelectAllNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanSelectAllNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Delete,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::DeleteSelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDeleteNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Copy,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::CopySelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCopyNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Paste,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::PasteNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanPasteNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Cut,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::CutSelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCutNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::DuplicateNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDuplicateNodes )
|
|
);
|
|
|
|
// Graph Editor Commands
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment,
|
|
FExecuteAction::CreateSP( this, &FMaterialEditor::OnCreateComment )
|
|
);
|
|
|
|
// Material specific commands
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().UseCurrentTexture,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertObjects,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureObjects,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureSamples,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToConstant,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StopPreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StartPreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().EnableRealtimePreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().DisableRealtimePreviewNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectDownstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownsteamNodes)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectUpstreamNodes,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpsteamNodes)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().RemoveFromFavorites,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().AddToFavorites,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ForceRefreshPreviews,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().CreateComponentMaskNode,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnCreateComponentMaskNode)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().GoToDocumentation,
|
|
FExecuteAction::CreateSP(this, &FMaterialEditor::OnGoToDocumentation),
|
|
FCanExecuteAction::CreateSP(this, &FMaterialEditor::CanGoToDocumentation)
|
|
);
|
|
|
|
}
|
|
|
|
FGraphAppearanceInfo AppearanceInfo;
|
|
|
|
if (MaterialFunction)
|
|
{
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialFunction", "MATERIAL FUNCTION");
|
|
}
|
|
else
|
|
{
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Material", "MATERIAL");
|
|
}
|
|
|
|
SGraphEditor::FGraphEditorEvents InEvents;
|
|
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FMaterialEditor::OnSelectedNodesChanged);
|
|
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FMaterialEditor::OnNodeDoubleClicked);
|
|
InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FMaterialEditor::OnNodeTitleCommitted);
|
|
InEvents.OnVerifyTextCommit = FOnNodeVerifyTextCommit::CreateSP(this, &FMaterialEditor::OnVerifyNodeTextCommit);
|
|
InEvents.OnSpawnNodeByShortcut = SGraphEditor::FOnSpawnNodeByShortcut::CreateSP(this, &FMaterialEditor::OnSpawnGraphNodeByShortcut, CastChecked<UEdGraph>(Material->MaterialGraph));
|
|
|
|
// Create the title bar widget
|
|
TSharedPtr<SWidget> TitleBarWidget = SNew(SMaterialEditorTitleBar)
|
|
.TitleText(this, &FMaterialEditor::GetOriginalObjectName);
|
|
//.MaterialInfoList(&MaterialInfoList);
|
|
|
|
return SNew(SGraphEditor)
|
|
.AdditionalCommands(GraphEditorCommands)
|
|
.IsEditable(true)
|
|
.TitleBar(TitleBarWidget)
|
|
.Appearance(AppearanceInfo)
|
|
.GraphToEdit(Material->MaterialGraph)
|
|
.GraphEvents(InEvents)
|
|
.ShowGraphStateOverlay(false);
|
|
}
|
|
|
|
void FMaterialEditor::CleanUnusedExpressions()
|
|
{
|
|
TArray<UEdGraphNode*> UnusedNodes;
|
|
|
|
Material->MaterialGraph->GetUnusedExpressions(UnusedNodes);
|
|
|
|
if (UnusedNodes.Num() > 0 && CheckExpressionRemovalWarnings(UnusedNodes))
|
|
{
|
|
{
|
|
// Kill off expressions referenced by the material that aren't reachable.
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorCleanUnusedExpressions", "Material Editor: Clean Unused Expressions") );
|
|
|
|
Material->Modify();
|
|
Material->MaterialGraph->Modify();
|
|
|
|
for (int32 Index = 0; Index < UnusedNodes.Num(); ++Index)
|
|
{
|
|
UMaterialGraphNode* GraphNode = CastChecked<UMaterialGraphNode>(UnusedNodes[Index]);
|
|
UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression;
|
|
|
|
FBlueprintEditorUtils::RemoveNode(NULL, GraphNode, true);
|
|
|
|
if (PreviewExpression == MaterialExpression)
|
|
{
|
|
SetPreviewExpression(NULL);
|
|
}
|
|
|
|
MaterialExpression->Modify();
|
|
Material->Expressions.Remove(MaterialExpression);
|
|
Material->RemoveExpressionParameter(MaterialExpression);
|
|
// Make sure the deleted expression is caught by gc
|
|
MaterialExpression->MarkPendingKill();
|
|
}
|
|
|
|
Material->MaterialGraph->LinkMaterialExpressionsFromGraph();
|
|
} // ScopedTransaction
|
|
|
|
GraphEditor->ClearSelectionSet();
|
|
GraphEditor->NotifyGraphChanged();
|
|
|
|
SetMaterialDirty();
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::CheckExpressionRemovalWarnings(const TArray<UEdGraphNode*>& NodesToRemove)
|
|
{
|
|
FString FunctionWarningString;
|
|
bool bFirstExpression = true;
|
|
for (int32 Index = 0; Index < NodesToRemove.Num(); ++Index)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(NodesToRemove[Index]))
|
|
{
|
|
UMaterialExpressionFunctionInput* FunctionInput = Cast<UMaterialExpressionFunctionInput>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(GraphNode->MaterialExpression);
|
|
|
|
if (FunctionInput)
|
|
{
|
|
if (!bFirstExpression)
|
|
{
|
|
FunctionWarningString += TEXT(", ");
|
|
}
|
|
bFirstExpression = false;
|
|
FunctionWarningString += FunctionInput->InputName;
|
|
}
|
|
|
|
if (FunctionOutput)
|
|
{
|
|
if (!bFirstExpression)
|
|
{
|
|
FunctionWarningString += TEXT(", ");
|
|
}
|
|
bFirstExpression = false;
|
|
FunctionWarningString += FunctionOutput->OutputName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FunctionWarningString.Len() > 0)
|
|
{
|
|
if (EAppReturnType::Yes != FMessageDialog::Open( EAppMsgType::YesNo,
|
|
FText::Format(
|
|
NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorDeleteFunctionInputs", "Delete function inputs or outputs \"{0}\"?\nAny materials which use this function will lose their connections to these function inputs or outputs once deleted."),
|
|
FText::FromString(FunctionWarningString) )))
|
|
{
|
|
// User said don't delete
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FMaterialEditor::RemoveSelectedExpressionFromFavorites()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt))
|
|
{
|
|
MaterialExpressionClasses::Get()->RemoveMaterialExpressionFromFavorites(GraphNode->MaterialExpression->GetClass());
|
|
EditorOptions->FavoriteExpressions.Remove(GraphNode->MaterialExpression->GetClass()->GetName());
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::AddSelectedExpressionToFavorites()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt))
|
|
{
|
|
MaterialExpressionClasses::Get()->AddMaterialExpressionToFavorites(GraphNode->MaterialExpression->GetClass());
|
|
EditorOptions->FavoriteExpressions.AddUnique(GraphNode->MaterialExpression->GetClass()->GetName());
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
|
|
{
|
|
TArray<UObject*> SelectedObjects;
|
|
|
|
UObject* EditObject = Material;
|
|
if (MaterialFunction)
|
|
{
|
|
EditObject = MaterialFunction;
|
|
}
|
|
|
|
if( NewSelection.Num() == 0 )
|
|
{
|
|
SelectedObjects.Add(EditObject);
|
|
}
|
|
else
|
|
{
|
|
for(TSet<class UObject*>::TConstIterator SetIt(NewSelection);SetIt;++SetIt)
|
|
{
|
|
if (UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*SetIt))
|
|
{
|
|
SelectedObjects.Add(GraphNode->MaterialExpression);
|
|
}
|
|
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(*SetIt))
|
|
{
|
|
SelectedObjects.Add(CommentNode->MaterialExpressionComment);
|
|
}
|
|
else
|
|
{
|
|
SelectedObjects.Add(EditObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
GetDetailView()->SetObjects( SelectedObjects, true );
|
|
}
|
|
|
|
void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(Node);
|
|
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpressionConstant3Vector* Constant3Expression = Cast<UMaterialExpressionConstant3Vector>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionConstant4Vector* Constant4Expression = Cast<UMaterialExpressionConstant4Vector>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionFunctionInput* InputExpression = Cast<UMaterialExpressionFunctionInput>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionVectorParameter* VectorExpression = Cast<UMaterialExpressionVectorParameter>(GraphNode->MaterialExpression);
|
|
|
|
FColorChannels ChannelEditStruct;
|
|
|
|
// Reset to default
|
|
ColorPickerProperty = NULL;
|
|
|
|
if( Constant3Expression )
|
|
{
|
|
ChannelEditStruct.Red = &Constant3Expression->Constant.R;
|
|
ChannelEditStruct.Green = &Constant3Expression->Constant.G;
|
|
ChannelEditStruct.Blue = &Constant3Expression->Constant.B;
|
|
}
|
|
else if( Constant4Expression )
|
|
{
|
|
ChannelEditStruct.Red = &Constant4Expression->Constant.R;
|
|
ChannelEditStruct.Green = &Constant4Expression->Constant.G;
|
|
ChannelEditStruct.Blue = &Constant4Expression->Constant.B;
|
|
ChannelEditStruct.Alpha = &Constant4Expression->Constant.A;
|
|
}
|
|
else if (InputExpression)
|
|
{
|
|
ChannelEditStruct.Red = &InputExpression->PreviewValue.X;
|
|
ChannelEditStruct.Green = &InputExpression->PreviewValue.Y;
|
|
ChannelEditStruct.Blue = &InputExpression->PreviewValue.Z;
|
|
ChannelEditStruct.Alpha = &InputExpression->PreviewValue.W;
|
|
}
|
|
else if (VectorExpression)
|
|
{
|
|
ChannelEditStruct.Red = &VectorExpression->DefaultValue.R;
|
|
ChannelEditStruct.Green = &VectorExpression->DefaultValue.G;
|
|
ChannelEditStruct.Blue = &VectorExpression->DefaultValue.B;
|
|
ChannelEditStruct.Alpha = &VectorExpression->DefaultValue.A;
|
|
static FName DefaultValueName = FName(TEXT("DefaultValue"));
|
|
// Store off the property the color picker will be manipulating, so we can construct a useful PostEditChangeProperty later
|
|
ColorPickerProperty = VectorExpression->GetClass()->FindPropertyByName(DefaultValueName);
|
|
}
|
|
|
|
if (ChannelEditStruct.Red || ChannelEditStruct.Green || ChannelEditStruct.Blue || ChannelEditStruct.Alpha)
|
|
{
|
|
TArray<FColorChannels> Channels;
|
|
Channels.Add(ChannelEditStruct);
|
|
|
|
ColorPickerObject = GraphNode->MaterialExpression;
|
|
|
|
// Open a color picker that only sends updates when Ok is clicked,
|
|
// Since it is too slow to recompile preview expressions as the user is picking different colors
|
|
FColorPickerArgs PickerArgs;
|
|
PickerArgs.ParentWidget = GraphEditor;//AsShared();
|
|
PickerArgs.bUseAlpha = Constant4Expression != NULL || VectorExpression != NULL;
|
|
PickerArgs.bOnlyRefreshOnOk = 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);
|
|
|
|
OpenColorPicker(PickerArgs);
|
|
}
|
|
|
|
UMaterialExpressionTextureSample* TextureExpression = Cast<UMaterialExpressionTextureSample>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast<UMaterialExpressionTextureSampleParameter>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionMaterialFunctionCall* FunctionExpression = Cast<UMaterialExpressionMaterialFunctionCall>(GraphNode->MaterialExpression);
|
|
UMaterialExpressionCollectionParameter* CollectionParameter = Cast<UMaterialExpressionCollectionParameter>(GraphNode->MaterialExpression);
|
|
|
|
TArray<UObject*> ObjectsToView;
|
|
UObject* ObjectToEdit = NULL;
|
|
|
|
if (TextureExpression && TextureExpression->Texture)
|
|
{
|
|
ObjectsToView.Add(TextureExpression->Texture);
|
|
}
|
|
else if (TextureParameterExpression && TextureParameterExpression->Texture)
|
|
{
|
|
ObjectsToView.Add(TextureParameterExpression->Texture);
|
|
}
|
|
else if (FunctionExpression && FunctionExpression->MaterialFunction)
|
|
{
|
|
ObjectToEdit = FunctionExpression->MaterialFunction;
|
|
}
|
|
else if (CollectionParameter && CollectionParameter->Collection)
|
|
{
|
|
ObjectToEdit = CollectionParameter->Collection;
|
|
}
|
|
|
|
if (ObjectsToView.Num() > 0)
|
|
{
|
|
GEditor->SyncBrowserToObjects(ObjectsToView);
|
|
}
|
|
if (ObjectToEdit)
|
|
{
|
|
FAssetEditorManager::Get().OpenEditorForAsset(ObjectToEdit);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged)
|
|
{
|
|
if (NodeBeingChanged)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "RenameNode", "Rename Node" ) );
|
|
NodeBeingChanged->Modify();
|
|
NodeBeingChanged->OnRenameNode(NewText.ToString());
|
|
}
|
|
}
|
|
|
|
bool FMaterialEditor::OnVerifyNodeTextCommit(const FText& NewText, UEdGraphNode* NodeBeingChanged, FText& OutErrorMessage)
|
|
{
|
|
bool bValid = true;
|
|
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(NodeBeingChanged);
|
|
if( MaterialNode && MaterialNode->MaterialExpression && MaterialNode->MaterialExpression->IsA<UMaterialExpressionParameter>() )
|
|
{
|
|
if( NewText.ToString().Len() > NAME_SIZE )
|
|
{
|
|
OutErrorMessage = FText::Format( LOCTEXT("MaterialEditorExpressionError_NameTooLong", "Parameter names must be less than {0} characters"), FText::AsNumber(NAME_SIZE));
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
FReply FMaterialEditor::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2D& InPosition, UEdGraph* InGraph)
|
|
{
|
|
UEdGraph* Graph = InGraph;
|
|
|
|
TSharedPtr< FEdGraphSchemaAction > Action = FMaterialEditorSpawnNodeCommands::Get().GetGraphActionByChord(InChord, InGraph);
|
|
|
|
if(Action.IsValid())
|
|
{
|
|
TArray<UEdGraphPin*> DummyPins;
|
|
Action->PerformAction(Graph, DummyPins, InPosition);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void FMaterialEditor::UpdateStatsMaterials()
|
|
{
|
|
if (bShowBuiltinStats && bStatsFromPreviewMaterial)
|
|
{
|
|
UMaterial* StatsMaterial = Material;
|
|
FString EmptyMaterialName = FString(TEXT("MEStatsMaterial_Empty_")) + Material->GetName();
|
|
EmptyMaterial = (UMaterial*)StaticDuplicateObject(Material, GetTransientPackage(), *EmptyMaterialName, ~RF_Standalone, UPreviewMaterial::StaticClass());
|
|
|
|
EmptyMaterial->SetFeatureLevelToCompile(ERHIFeatureLevel::ES2, bShowMobileStats);
|
|
|
|
EmptyMaterial->Expressions.Empty();
|
|
|
|
//Disconnect all properties from the expressions
|
|
for (int32 PropIdx = 0; PropIdx < MP_MAX; ++PropIdx)
|
|
{
|
|
FExpressionInput* ExpInput = EmptyMaterial->GetExpressionInputForProperty((EMaterialProperty)PropIdx);
|
|
if(ExpInput)
|
|
{
|
|
ExpInput->Expression = NULL;
|
|
}
|
|
}
|
|
EmptyMaterial->bAllowDevelopmentShaderCompile = Material->bAllowDevelopmentShaderCompile;
|
|
EmptyMaterial->PreEditChange(NULL);
|
|
EmptyMaterial->PostEditChange();
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|