Files
UnrealEngineUWP/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp
Matt Kuhlenschmidt 1da24c4701 Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3050373)
#lockdown Nick.Penwarden
#rb none
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2973846 on 2016/05/11 by Jamie.Dale

	Exposed FConfigValue::ExpandValue and added FConfigValue::CollapseValue

	These are both static and can be used to expand or collapse the macros used in our config files (mostly when dealing with paths), in code that has to deal with the config system, but isn't internal to the config system (mostly things that deal with default configs outside of UObjects).

	The old non-static version of FConfigValue::ExpandValue is now FConfigValue::ExpandValueInternal, which just calls FConfigValue::ExpandValue on SavedValue and ExpandedValue.

	This also changes some code that was using FString.Replace to use FString.ReplaceInline. This reduces allocations, and also allows us to avoid another string comparison to see whether the strings are identical (as ReplaceInline returns the number of replacements that were made).

Change 2973847 on 2016/05/11 by Jamie.Dale

	Changing the loading phase in the localization dashboard now writes to the default config

	#jira UE-30482

Change 2973866 on 2016/05/11 by Jamie.Dale

	Deprecated some functions that were taking an unused position.

	These unused parameters caused confusion and lead to UE-30276. The old versions have been deprecated, and new versions without those parameters have been added. Existing code has been updated to call the non-deprecated version.

	- FViewportFrame::ResizeFrame
	- FSceneViewport::ResizeFrame
	- FSceneViewport::ResizeViewport

Change 2974505 on 2016/05/11 by Nick.Darnell

	PR #2309: Added Combobox styling (Contributed by Chris528)

Change 2975241 on 2016/05/12 by Richard.TalbotWatkin

	Made sRGB Preview the default in the Color Picker.

Change 2975390 on 2016/05/12 by Jamie.Dale

	Made sure that en-US-POSIX is in our list of available cultures

	Some people use machine tags as their native text, so they need an invariant machine like culture to use as their native culture. en-US-POSIX is perfect for this.

Change 2975411 on 2016/05/12 by Jamie.Dale

	PR #2237: Fixed formatting of Error_TooManyMaterials message (Contributed by pfranz)

Change 2975559 on 2016/05/12 by Jamie.Dale

	Dialogue Wave VO direction can now be localized

	This is gathered as editor-only data.

	#jira UE-28715

Change 2975710 on 2016/05/12 by Jamie.Dale

	Implemented UObject::IsLocalizedResource to test whether the object belongs to a localized package

Change 2975728 on 2016/05/12 by Jamie.Dale

	Exported dialogue scripts now include a column that says whether they have a localized recording of that line of dialogue

	#jira UETOOL-794

Change 2975763 on 2016/05/12 by Jamie.Dale

	We no longer warn if asked to check out a UNC path when running the GatherText commandlets

	#jira UE-25833

Change 2975766 on 2016/05/12 by Jamie.Dale

	Resolved some loc key conflicts

	#jira UE-25833

Change 2975774 on 2016/05/12 by Jamie.Dale

	PO files now only contain a single entry in the case of a native translation being exported

	They used to contain the original entry, as well as an entry for the native translation, however the original entry would never be used.

	This change also cleans up some directory walking code that was looking for archive files, and replaces it with code to load the specific archive file.

Change 2975776 on 2016/05/12 by Jamie.Dale

	Downgraded a PO file import warning that isn't really an issue

	#jira UE-25833

Change 2976675 on 2016/05/13 by Jamie.Dale

	Fixed some more fallout from changes to use the window position when changing the game viewport mode

	- FSceneViewport::ResizeFrame:
	  - Fixed the HMD monitor info setting the wrong variables.
	  - Fixed SetWindowMode and ResizeViewport potentially being passed two different modes.
	  - We now only move the window if we need to (this avoids issues with WindowedFullscreen window positioning).

	- FWindowsWindow::MoveWindowTo:
	  - Now treats the screen space position it's given as relative to the top-left of the window, rather than the top-left of the windows' client area.

	- FWindowsApplication:
	  - WM_MOVE was passing a screen space position relative to the top-left of the windows' client area, rather than its window area like Slate expected.

	#jira UE-30276
	#jira UE-30677

Change 2976804 on 2016/05/13 by Jamie.Dale

	Slight optimization to FICUInternationalization::FindOrMakeCulture to avoid hitting the filesystem until we know we need to

Change 2976967 on 2016/05/13 by Alexis.Matte

	#jira UE-30687 Cannot import a skeletal mesh scale to zero

Change 2977042 on 2016/05/13 by Alexis.Matte

	#jira UE-29952 log a warning if fbx exceed the maximum number of LOD.

	#2326 Github PR

	#code review matt.kuhlenschmidt

Change 2977074 on 2016/05/13 by Jamie.Dale

	Follow up to CL# 2976804 to avoid a potential change in behavior

Change 2977076 on 2016/05/13 by Jamie.Dale

	Some tidy up and optimization to SCulturePicker

Change 2977327 on 2016/05/13 by Alex.Delesky

	Now deleting the Redirector package on Redirector Fix Up rather than simply removing it from the Content Browser.

	#jira UE-30423

Change 2977499 on 2016/05/13 by Alexis.Matte

	#jira UE-29475
	Enable UStruct child property to be favorite

Change 2978415 on 2016/05/16 by Jamie.Dale

	We now pre-load all the culture data when starting the editor to avoid a UI hitch later

Change 2978517 on 2016/05/16 by Alex.Delesky

	#jira UE-29406

	Creating a static mesh from a geometry brush and then attempting to reimport the mesh will no longer crash the editor.

Change 2978518 on 2016/05/16 by Alex.Delesky

	#jira UE-28210

	The FBX Importer no longer runs cleanup upon failing to import an FBX file and won't crash the engine the next time an FBX is imported within the same editor session.

Change 2978556 on 2016/05/16 by Alexis.Matte

	Fbx tests automation
	#jira UE-29635

Change 2978797 on 2016/05/16 by Alexis.Matte

	#jira UE-30774
	- prevent baking the pivot if we transform the vertex with the absolute transform.
	- Also make sure we set the identity for the Max puivot in case we dont bake the pivot and we dont transform the vertex with the absolute transform.

	#code review matt.kuhlenschmidt

Change 2978965 on 2016/05/16 by Alexis.Matte

	FBX importer, fix the socket rotation.
	#jira UE-30094

Change 2980613 on 2016/05/17 by Jamie.Dale

	Moved the XLOC UAT localization provider to be publicly accessible

Change 2980614 on 2016/05/17 by Jamie.Dale

	Reference update for project move

Change 2980633 on 2016/05/17 by Jamie.Dale

	Made the culture mapping used between XLOC and UE4 configurable on a per-project basis

	You can now override GetEpicCultureToXLocLanguageId in your custom localization provider in order to change the default mappings.

Change 2980836 on 2016/05/17 by Jamie.Dale

	Added -LocalizationSteps flag to allow you to only run a subset of the UAT "Localise" command

	You can pass any of the following steps: Download, Gather, Import, Export, Compile, GenerateReports, Upload

Change 2982700 on 2016/05/18 by Jamie.Dale

	Fixed the loc package gather potentially adding the same source location multiple times

Change 2983906 on 2016/05/19 by Jamie.Dale

	Slight cleanup of the way we register localization gatherer callbacks

Change 2984356 on 2016/05/19 by Chris.Wood

	Removed temporary analytics API change needed for earlier hot fix
	[UE-31005] - Undo temp Hardware Survey API change from 4.10 - CL 2782817

Change 2986679 on 2016/05/23 by Alex.Delesky

	#jira UE-24747 - Importing FBX files that contain meshes that do not have non-degenerate triangles will no longer crash the editor on import, and will warn the user that the meshes are bad.

Change 2986798 on 2016/05/23 by Alex.Delesky

	#jira UE-31136 - Chord Input fields will no longer display the blinking edit cursor if they do not have focus.

Change 2987106 on 2016/05/23 by Alexis.Matte

	Fbx importer, fail import must not create a package in the content browser
	#jira UE-31154

Change 2987563 on 2016/05/23 by Alex.Delesky

	#jira UE-30988 - Changed the default window mode when launching a game from the .uproject file to Windowed

Change 2987564 on 2016/05/23 by Alex.Delesky

	#jira UE-28856 - Fixed a crash that could potentially occur when starting up PIE while dragging objects like widgets in the editor.

Change 2988321 on 2016/05/24 by Jamie.Dale

	Added a way to backup and restore the selection state of a level (its actors and components) in a way that can be reapplied even if the level is reloaded

Change 2988708 on 2016/05/24 by Jamie.Dale

	Fix for crash when missing the fallback/last resort font

Change 2988782 on 2016/05/24 by Jamie.Dale

	Added the ability to version each localized string individually when loaded into the localization manager

	The single 32-bit global history has now been replaced with two 16-bit histories. One is global, and is updated whenever the culture is changed (or a LocRes file is loaded), and the other is local to each string, and is updated if the display string is changed outside of a culture update (to handle cases where the display string is changed, but the key is preserved). Changing the global history will reset all local histories.

	Because of the change from an int32 to a uint16, 0, rather than INDEX_NONE, is now considered the "unset" value for a history.

Change 2988856 on 2016/05/24 by Jamie.Dale

	Added a way to get the package(s) of the object(s) being edited by a property panel

	Typically the package is just the outermost of the object being edited, however there are some cases where this may not be the case:
	  - UMG widgets edit a transient copy of the real data, so we use the SetObjectPackageOverrides to override the package these objects should use to be the real asset package.
	  - Structs (UDS, Data Table, etc) don't have a way to get to their package, so you have to specify it on their FStructOnScope instance (see FStructOnScope::GetPackage and FStructOnScope::SetPackage). This has been hooked up for the UDS and Data Table editors.

Change 2988955 on 2016/05/24 by Alex.Delesky

	#jira UE-30645 - Adding in support for splash images to support .png and .jpg files. In general, this adds multi-extension support for external image references and external image picker modules.

	Git Request #2376

Change 2989418 on 2016/05/25 by Jamie.Dale

	Added a way to count text references within a package that match the given search criteria

	This can be used to detect whether a localization ID is unique within its package.

	The following search modes are available:
	  - MatchId: Detect a reference if it matches the given ID (ignoring the source text)
	  - MatchSource: Detect a reference if it matches the given ID and source string
	  - MismatchSource: Detect a reference if it matches the given ID but has a different source string

Change 2989436 on 2016/05/25 by Jamie.Dale

	Added "root-level" meta-data (meta-data associated with the package rather than an object within it)

Change 2989471 on 2016/05/25 by Alexis.Matte

	Fbx scene importer, fix naming clash when creating package we now also look in memory to find existing package not just on disk

Change 2989639 on 2016/05/25 by Jamie.Dale

	Added static version of FName::IsValidXName

	This allows you to verify name-like strings without having to convert them to an FName (and thus add them to the name table)

Change 2989716 on 2016/05/25 by Alex.Delesky

	#jira UE-30828 - The Standalone Session Frontend will now render the names of automation tests correctly instead of as solid white blocks.

Change 2990100 on 2016/05/25 by Alexis.Matte

	Fix crash when reimporting a mesh that originaly exceed the maximum number of LOD

	#jira UE-30907

Change 2991442 on 2016/05/26 by Bob.Tellez

	#UE4 Fix components in world not rendering when saved without a physics scene.

Change 2991736 on 2016/05/26 by Bob.Tellez

	#UE4 Fix duplicated worlds not being initialized when inactive. Re-enabled duplication of worlds in the content browser.

Change 2991942 on 2016/05/26 by Alex.Delesky

	#jira UE-31012 - Setting a Decimal Grid Interval value to 0 and using it will no longer crash the editor or cause an editor crash on startup.

Change 2991994 on 2016/05/26 by Alex.Delesky

	#jira UE-31177 - Attempting to export an entire level as an object file and choosing to export all materials as images will no longer crash the editor.

Change 2994037 on 2016/05/30 by Alexis.Matte

	Add Fbx Automation Tests
	- static mesh import reimport (sections and materials)
	- skeletal mesh import and reimport (sections and materials also bone position)
	- static/skeletal mesh LODs (import, add, reimport)
	- rigid mesh (import, reimport)

Change 2994253 on 2016/05/31 by Alexis.Matte

	Mikkt crash when computing the normals if there is more vertex then the number of wedge

	#jira UE-29143

Change 2994260 on 2016/05/31 by Alexis.Matte

	Make sure we cannot modify fbx test plan when json file is read only

Change 2994431 on 2016/05/31 by Alex.Delesky

	#jira UE-21900 - The scale widget should now render all axes when using an orthographic camera.

Change 2994432 on 2016/05/31 by Alex.Delesky

	#jira UE-31328 - New objects dragged into the scene will now comply with the Surface Snapping option in the viewport, and will not use the Surface Offset if snapping is disabled.

Change 2994537 on 2016/05/31 by Richard.TalbotWatkin

	Fixed potential crash in the Mesh Paint tool when non-transactable actors are in the SelectedActors list following a Redo.
	#jira UE-31172 - Crash related to Vertex Painting - MeshPaint!CastChecked<AActor,UObject>()

Change 2994983 on 2016/05/31 by Richard.TalbotWatkin

	Added some guard code to protect against a crash when editing geometry. Repro currently unknown, ensure was added in order to try to get more information.
	#jira UE-30820 - UT EDITOR: CRASH: Crash in Public Release CL#2973693

Change 2995022 on 2016/05/31 by Jamie.Dale

	PR #2428: Added missing END_OPTIMIZATION macro to SOutputLog (Contributed by MatzeOGH)

Change 2995027 on 2016/05/31 by Jamie.Dale

	PR #2409: fixed a small typo in GraphEditor.h (Contributed by MatzeOGH)

Change 2995963 on 2016/06/01 by Alex.Delesky

	#jira UE-31317 -  The transform gizmo will no longer block the placement of a material onto a mesh.

Change 2997002 on 2016/06/01 by Cody.Albert

	Fix to ensure ActiveTopLevelWindow is properly set after a window is destroyed

	#jira UE-31448

Change 2998013 on 2016/06/02 by Alexis.Matte

	Prevent static mesh materials array to grow when using the reset button in the staticmesh editor.

	#jira UE-12931

Change 2998370 on 2016/06/02 by Alexis.Matte

	Fbx Automation, add some import LOD test in case the options are not ok

Change 2999709 on 2016/06/03 by Jamie.Dale

	Fixed some issues with gathering text from BP bytecode

	Bytecode in Blueprints is very volatile, and can only be safely gathered after it's been compiled (which is not guaranteed to have happened by the time we save the package). This change avoids caching any assets that contain scripts (non-data-only Blueprints), and instead will always load them to perform a gather (which will ensure the Blueprint bytecode is up-to-date due to compile-on-load).

Change 2999755 on 2016/06/03 by Richard.TalbotWatkin

	Fixes to Spline Mesh collision generation.
	- Fixed a serious issue with DDC ID generation, in that the static mesh wasn't forming a part of the key, hence any two spline meshes with identical properties but different meshes would yield the same cache entry.
	- Fixed how different collision boxes are transformed when rebuilding physics meshes. Convex collision transforms are now correctly taken into account, and spherical and capsule collision now gets correctly translated when a scale is applied to the start or end of the spline mesh.
	- Optimized physics rebuilding.  A new BodySetup object is now only created when needed, otherwise it is reused.

	#jira UE-31361 - Splines handle box collision and collision from other shapes differently

Change 2999973 on 2016/06/03 by Jamie.Dale

	We now skip bulk data when detecting text references

	#jira UE-31596

Change 3000159 on 2016/06/03 by Alex.Delesky

	#jira UE-30244 - Added a safeguard against a potential crash when editing BSP brushes before placing another BSP brush into the level.

Change 3001814 on 2016/06/06 by Alexis.Matte

	Make sure the staticmesh Materials list dont grow when we reimport or override a LOD other then the base mesh.
	Add a fbx test to make sure the problem is flag by automation test

	#jira UE-1394

Change 3001820 on 2016/06/06 by Alex.Delesky

	#jira UE-19079 - Widget Blueprints should no longer crash when dragging widgets from one blueprint to a second and then compiling the second blueprint.

Change 3001915 on 2016/06/06 by Alexis.Matte

	Make sure we check attribute type before checking attribute unique ID in case of unique id clash.

	#jira UE-31214

Change 3002026 on 2016/06/06 by Alexis.Matte

	Importing morph target should not import textures like materials since the base mesh already import thoses.

	UDN Question:
	https://udn.unrealengine.com/questions/293973/does-importing-an-fbx-with-morph-targets-cause-a-m.html

Change 3002623 on 2016/06/06 by Jamie.Dale

	Fixing more loc conflicts

Change 3002883 on 2016/06/06 by Jamie.Dale

	Adding retry when dealing with OneSky

	This is attempting to compensate for some timeouts with OneSky, which were also noticed when testing UE-31413

Change 3003004 on 2016/06/06 by Trung.Le

	#jira UE-13101 - Make "Description" field for a BluePrint Function multiline

Change 3003859 on 2016/06/07 by Alexis.Matte

	#jira UE-30436 Refresh the property editor when a array element is added, remove, insert, delete and the property is favorite

Change 3004132 on 2016/06/07 by Jamie.Dale

	Fixed a hash conflict that could occur when both the case-sensitive and case-insensitive FName hashes were identical

	This resulted in the case-preserving FName being added to the head of the linked list for the bucket, which caused any subsequent name lookups to return that name index for the comparison index (since it matched an insensitive string comparison), rather than the name index of the first case-variant of that name that was added to the bucket.

	This change has new entries be inserted at the tail of the list, which ensures that enumeration for a case-insensitive name will always find the same entry in the bucket (the first one that was ever added) and will continue to compare correctly.

Change 3004286 on 2016/06/07 by Jamie.Dale

	Ensured that assignments that publish new names to the bucket are atomic

Change 3004310 on 2016/06/07 by Jamie.Dale

	Ensured FName internal hashes are returned as uint16

Change 3004381 on 2016/06/07 by Jamie.Dale

	FAsyncPackage now creates the meta-data before processing the remaining exports

	This matches the behavior of FLinkerLoad::LoadAllObjects, as other objects may depend on the meta-data being loaded before them.

Change 3004765 on 2016/06/07 by Alex.Delesky

	#jira UE-31498 - Material thumbnails will now render the full sphere rather than an extreme close-up of the material.

Change 3005754 on 2016/06/08 by Trung.Le

	Allow whitespace for meta class names
	#jira UE-31668

Change 3005755 on 2016/06/08 by Stephan.Jiang

	UMGSequencePlayer implements GetPlaybackContext() and return UserWidget->GetWorld() if it's valid

	#jira UE-31299

Change 3006512 on 2016/06/08 by Alex.Delesky

	#jira UE-31572 - The "All Classes" tab in the Modes panel will now refresh when a placeable asset is created, renamed, or deleted without needed to navigate away from the tab first.

Change 3006760 on 2016/06/08 by Jamie.Dale

	Added support for stable localization keys

	This feature adds support for preserving the existing key of an FText property when editing the source string, providing that it is the only reference to that string within the package. A side effect of this is that you're now able to specify custom keys for FText properties since we can now verify that the custom key won't cause an identity conflict.

	In order to limit the search domain for uniqueness to a single package, we've added the concept of a "localization namespace" to packages (stored in the meta-data). Each package is given a unique namespace, which is appended to the user-defined namespace of the text when it is modified, saved, or duplicated. This package namespace ensures that the same user-defined namespace and key may be used in different packages without causing an identity conflict.

	In order to access the package namespace within the Core code that hosts FText (which doesn't know about UPackage), FArchive now provides a GetLocalizationNamespace function to access the package namespace within the Core code, and a SetLocalizationNamespace function for CoreUObject and Engine code to pass down the package namespace from their packages.

	If you have an archive that handles duplicating objects into a different package, or duplicating packages themselves, then you'll want to make sure it's setting the package namespace correctly. FObjectReader and FObjectWriter have been updated to do this, and serve as a good example. FDuplicateDataReader (used by StaticDuplicateObject), and FCopyPropertiesArchiveObjectWriter (used when compiling Blueprints) have also been updated to set the package namespace, as they both handle copying objects between packages. TextNamespaceUtil provides a suite of functions for getting at (or setting) the namespace for a package.

	Keys will start to stabilize naturally over time once this feature is enabled, however the StabilizeLocalizationKeys commandlet may also be used to stabilize all the keys for a game at once. Running it for a game under source control would look something like this:
	  MyGame -run=StabilizeLocalizationKeys -IncludeGame -NativeCulture=en -EnableSCC

	This commandlet also updates your localization archives to use the new text identities, however you'll still need to run a localization gather and localization compile before the updated translations will be available for your game.

	Note: This feature is currently disabled via the USE_STABLE_LOCALIZATION_KEYS define. It will be enabled at a later date.

	#jira UETOOL-796

Change 3007501 on 2016/06/09 by Trung.Le

	#jira UE-31722
	Fix MaterialFunctions crash when editing text in Libraries Category Text field. Solution: Removed PredEdit and PostEdit from IEditableTextProperty, its derived types and other code that was calling them. The new SetText method already calls NotifyPreChange and NotifyPostChange to properly create/destroy ScopedTransaction.

Change 3007524 on 2016/06/09 by Jamie.Dale

	Added some additional checks to avoid re-keying text when duplicating for PIE

Change 3007564 on 2016/06/09 by Jamie.Dale

	PR #2401: DataTable import/export improvements (Contributed by bozaro)

Change 3007653 on 2016/06/09 by Jamie.Dale

	PR #2459: Generate JSON for nested structs in DataTable rows (Contributed by jorgenpt)

Change 3008019 on 2016/06/09 by Jamie.Dale

	Updated structs to export as JSON when displaying them in the Data Table editor

	This produces much cleaner results than using the text export method (which will use the internal names for user defined structs).

	This also cleans up the FDataTableExporterCSV and FDataTableExporterJSON APIs so that you don't need to pass in a UDataTable if you're not going to use it.

	#jira UE-29958

Change 3008052 on 2016/06/09 by Jamie.Dale

	Fixed bug importing an array inside a JSON Data Table

	This was noticed when testing a GitHub PR, but the JSON importer for a Data Table was appending the new data to the array rather than replacing it. It now clears the array prior to importing.

Change 3008875 on 2016/06/10 by Jamie.Dale

	PR #2406: Git plugin: Fix for Git diff not working in UE 4.12 (and master) (Contributed by SRombauts)

Change 3008879 on 2016/06/10 by Jamie.Dale

	PR #2484: Git Plugin: fix the Submit To Source Control menu broken by new "migrate" support in 4.12 (and master) (Contributed by SRombauts)

Change 3008990 on 2016/06/10 by Alex.Delesky

	#jira UE-15699 - Submitting to source control via the editor should now check for current asset status before prompting the user to submit their changes. This should prevent files that had been previously deleted from being readded to source.

Change 3008991 on 2016/06/10 by Alex.Delesky

	#jira UE-31688 - The Output Log will now automatically anchor to the bottom of the scroll bar when the user scrolls all the way down using the mouse wheel or clicking and dragging the content window.

Change 3010856 on 2016/06/13 by Alexis.Matte

	#jira UE-31713 Fix a serialize issue for skeletal mesh with apex cloth.

Change 3011736 on 2016/06/13 by Jamie.Dale

	Adding missing plurals.res file

	This is needed to get plural form information from ICU.

	#jira UETOOL-875

Change 3012387 on 2016/06/14 by Richard.TalbotWatkin

	Disabled the Paste context menu action if the property is marked as EditConst.
	#jira UE-27469 - User is able to paste values into a read-only setting

Change 3012971 on 2016/06/14 by Stephan.Jiang

	Editor Preferences->Widget Designer now have two options to toggle the visibilities of widgets created from Engine content folder and Developers folder.

	By default, visibility for engine content is off and developers is on

	#jira UE-31657

Change 3013111 on 2016/06/14 by Jamie.Dale

	Unified the number, percentage, and currency formatting between the ICU and Legacy text implementations

	Removed all the old legacy number formatting code, and removed the calls to the ICU specific number formatting. Everything is now using FastDecimalFormat as this will allow some optimizations later when formatting numbers in FText::Format.

Change 3015438 on 2016/06/15 by Cody.Albert

	Fixing ScrollBy function to calculate new scroll offset based on the current scroll offset and not the current desired scroll offset (which may not be the same during an animation)

	#jira UE-32082

Change 3016782 on 2016/06/16 by Richard.TalbotWatkin

	Corrected ConvexHull2D so that it returns an empty set of indices when passed an empty points array.

Change 3016949 on 2016/06/16 by Jamie.Dale

	Added FastDecimalFormat overloads to write into an existing string

	This helps avoid an extra allocation if you already have a pre-sized string that you're writing the number to (as is the case in FText::Format).

Change 3016952 on 2016/06/16 by Jamie.Dale

	Changed an Add for an Emplace to avoid moving a temporary

Change 3016954 on 2016/06/16 by Jamie.Dale

	Updated some FText code to avoid creating temporary objects just to move data through a hierarchy

	There was some code in FText and its internal types that were using pass-by-value as a marshaller to move data through a hierarchy. This resulted in temporary objects being created and destroyed to facilitate the movement of data.

	This change has all the internal FText code (private FText constructors, internal text data, and internal text history) take its movable types as an r-value reference. This avoids the temporary objects, but also makes it impossible to accidentally copy a construction argument when you meant to move it (you can still copy, but the copy must be explicit).

	In addition to this, FText::FromString and FText::AsCultureInvariant now have two overloads, const FString& and FString&&, to avoid them creating a temporary when you're invoking a move. FText::ChangeKey now takes its parameters by const& as their data wasn't being moved further down the chain, so the by-value copy was wasteful.

Change 3019021 on 2016/06/19 by Richard.TalbotWatkin

	When deleting a brush, ensure geometry is rebuilt before updating the details panel according to the selection change, so that the old Surface Properties don't continue to appear.
	#jira UE-8966 - Surface Properties of a BSP remain in the details panel after the BSP is deleted

Change 3019022 on 2016/06/19 by Richard.TalbotWatkin

	Fixed issue where the Surface Properties category in the Details panel doesn't appear after selecting a surface on a Brush which has just been placed.
	#jira UE-31916 - Selecting an edge of BSP geometry then a face does not show Surface Properties while in Place mode
	#jira UE-31915 - Selecting BSP face does not show Surface Properties in Details

Change 3019025 on 2016/06/19 by Richard.TalbotWatkin

	Fixed issue which was stopping 'Cancel' from correctly returning a 'Cancelled' result during P4 asynchronous ops.
	#jira UE-28595 - Submit to Source Control: "Checking for assets to check in..." cancel button does not cancel operation, editor becomes unresponsive

Change 3020050 on 2016/06/20 by Cody.Albert

	Changed window centering logic to correctly work when monitor 1 isn't set to primary monitor.

	#jira UE-32173

Change 3021145 on 2016/06/21 by Jamie.Dale

	Added support for text format argument modifiers

	These can be used to mutate a format argument before appending it to the resultant formatted string, and are applied to the preceding argument via a pipe, eg) "{Arg}|plural(one=is,other=are)".

	We provide a few of these by default:
	 - |plural(key=val,...)
	 - |ordinal(key=val,...)
	   Provides support for cardinal and ordinal plural forms, where key may be any of "one", "two", "few", "many", or "other", and val may be any optionally quoted string.
	 - |gender(masculine,feminine,[neuter])
	   Provides support for gender forms, where the 0th item is the masculine version, the 1st item is the feminine version, and the 2nd item is an optional neuter version. The values may be any optionally quoted string.
	 - |hpp(consonant,vowel)
	   Provides support for Hangul post-positions, where the 0th item is the consonant suffix, and the 1st item is the verb suffix. The values may be any optionally quoted string.

	Major changes:
	 - Exposed the ICU plural form handling via FCulture::GetPluralForm.
	 - Updated the FText formatting code to use an expression evaluator (to support the more complex expressions needed for the argument modifiers).
	 - Added FTextFormat to store a pre-compiled format expression. Re-using one of these if you're performing a lot of formats with the same FText will increase your performance (as around half of the FText::Format cost can be compilation, via an implicit construction of FTextFormat).
	 - Updated the FText::Format(...) family of functions to take their format string as FTextFormat, and take their arguments as FFormatArgumentValue. This allows us access to the real numeric types within the format code, but doesn't break the existing API as these types are implicitly constructible from the old parameters (FText).
	 - Converted text history to store their format string as an FTextFormat in-case they need to perform a re-format (this is still saved as an FText).

	Breaking changes:
	 - The rules for the escape token have been simplified, and there is an incredibly unlikely chance that this may affect some text:
	   - The ` character will now only escape a valid character (producing only the escaped character in the final string), or it will be ignored and inserted as a literal character, eg) "`{F" -> "{F", and "`F" -> "`F".
	   - Previously it would also remove the escape character when it followed { or }, eg) "{`" -> "{" and "}`" -> "}", rather than "{`" and "}`" like you might expect. It would also have previously removed a ` at the end of a string due to a parser bug.

Change 3021156 on 2016/06/21 by Jamie.Dale

	Updated LinuxToolChain to use the same output delegate for all of its actions when cross-compiling

	This avoids the compile and link actions being split into different batches.

Change 3021280 on 2016/06/21 by Richard.TalbotWatkin

	Fixed bug in parsing LOD in UStaticMeshComponent::ImportCustomProperties (thanks to Aurelien Cordonnier).
	#jira UE-31937 - UDN code submission for UStaticMeshComponent::ImportCustomProperties parsing bug

Change 3022949 on 2016/06/22 by Alex.Delesky

	#jira UE-31944 - Upgrading Subversion binaries to version 1.9.4.

Change 3023092 on 2016/06/22 by Jamie.Dale

	Downgraded some checks to ensures and added an early out

	#jira UE-32009

Change 3023154 on 2016/06/22 by Jamie.Dale

	Ported over CL# 3018771 to the UE automation

	This fixes an issue where a downloaded PO file smaller than the one already on disk leaving a mix of both files on disk (rather than the existing file on disk being truncated).

Change 3023579 on 2016/06/22 by Jamie.Dale

	Expanded the Blueprint FormatText node to support numeric and gender types

	These are needed to correctly support the new plural and gender forms that can be used in format strings, as these require actual numeric/enum data to be passed into the format arguments, rather than pre-formatted text.

	Major changes:
	 - The FormatText node for Blueprints now uses PC_Wildcard as its pin type for format arguments instead of PC_Text.
	   - Any existing literal text argument data in the pin is hoisted out into a "Make Literal Text" node which is then connected to the pin.
	 - FFormatArgumentData has been updated to be variant on the data needed by Blueprints. It's now a less comprehensive and non-unioned version of FFormatArgumentValue.
	 - The version of FText::Format taking FFormatArgumentData has been deprecated as its usage was internal to Blueprints and we have much better ways to format text in C++. Any existing C++ using that (of which we have none internally) should be updated to use FFormatArgumentValue instead.

Change 3023915 on 2016/06/22 by Jamie.Dale

	Cleaned up some of the UK2Node_FormatText expansion code to avoid unchecked literals

Change 3024813 on 2016/06/23 by Jamie.Dale

	Renamed FContext to FManifestContext to better reflect its purpose and avoid naming conflicts with other code

Change 3024852 on 2016/06/23 by Nick.Darnell

	FBX - Updating automation tests with the changes to chunk  and chunk index removal and them being merged with sections.

Change 3024994 on 2016/06/23 by Nick.Darnell

	UMG - Removing the DesignerWidgetTree, instead going to directly inject the widget tree into the partially constructed UUserWidget during design time, when refreshing the preview.  This avoids doing something a little dangerous and sketchy like updating the living class instance with a new designer tree that all new instances will begin biasing using.  Also making the preview widget explictly non-transactional as there's no reason to track changes to the preview, all the changes that need to be tracked should be on the template widget.  This should fix the crash in the widget designer when you Undo just after compiling the widget blueprint.

	#jira UE-31155

Change 3025194 on 2016/06/23 by Alex.Delesky

	#jira UE-31155 - Compilation error fix.

Change 3025255 on 2016/06/23 by Alex.Delesky

	#jira UE-21900 - Redoing changes done in CL 2994431 since it got stomped. Reinstates the grabber handles and ensures consistent scaling on the scale widget in orthographic viewports.

Change 3025460 on 2016/06/23 by Cody.Albert

	Fixed issue where widget components would misalign when aspect ratio was being constrained

	#jira UE-29637

Change 3025508 on 2016/06/23 by Cody.Albert

	Adding support for adjusting animation playback speed

	#jira UE-32222

Change 3026444 on 2016/06/24 by Jamie.Dale

	Fixed crash caused by bad access of shared this when closing an active IME context

	This was only needed to get the owner window, which we now cache when the IME context is created.

	#jira UE-32240

Change 3028358 on 2016/06/27 by Jamie.Dale

	Fixed IMEs not working due to no window being cached

	#jira UE-32240

Change 3028464 on 2016/06/27 by Alex.Delesky

	#jira UE-31873 - A single "Files need check-out" notification will now be shown instead of multiple notifications if multiple files need to be checked out, and updated as more files need to be checked out.

Change 3028524 on 2016/06/27 by Chris.Wood

	Switched off uploads to legacy Crash Report Receiver.
	[UE-31252] - Switch off deprecated CRR upload in Crash Report Client

	Also added CRC version string, added to crash context from CRC config

Change 3028840 on 2016/06/27 by Alexis.Matte

	#jira UE-32306 replace material bad name character by an underscore when doing a scen import.

Change 3028924 on 2016/06/27 by Alexis.Matte

	#jira UE-32125 Make sure we can add a plan when a fbx file is drop in the fbx automation test folder

Change 3029044 on 2016/06/27 by Alex.Delesky

	#jira UE-31944  - Updating SVN binaries for Mac to 1.9.4

Change 3029276 on 2016/06/27 by Alex.Delesky

	#jira UE-31531 - A user can now select the base class when creating a new physical material.

	PR #2462: added dialog, which enables picking base class for asset (Contributed by iniside)

Change 3029459 on 2016/06/27 by Alexis.Matte

	#jira UE-32354 Make sure we set all blueprint component to the correct mobility set in the scene import options.

Change 3030577 on 2016/06/28 by Nick.Darnell

	PR #2531: Git plugin: fix wrong status icons (Contributed by SRombauts)

Change 3030587 on 2016/06/28 by Alexis.Matte

	#jira UE-32251 add missing body setup variables when restoring the body setup value after a re-import of a staticmesh

Change 3030946 on 2016/06/28 by Alexis.Matte

	#jira UE-32515 prevent crash when re-import staticmesh userdata

Change 3031115 on 2016/06/28 by Jamie.Dale

	The DDC builder now gives the shader compile worker a chance to catch up when it pauses to run a GC pass

	This prevents an issue where the shader backlog could cause massive amounts of memory to be consumed.

Change 3031146 on 2016/06/28 by Jamie.Dale

	Fixed errors when building with USE_STABLE_LOCALIZATION_KEYS enabled caused by UEdGraphPin no longer being a UObject

Change 3031357 on 2016/06/28 by Nick.Darnell

	PR #2431: Add plugin support to the editor class wizard. (Contributed by Koderz)

Change 3031515 on 2016/06/28 by Jamie.Dale

	Fixed game targets not being able to depend on other game targets

Change 3031520 on 2016/06/28 by Jamie.Dale

	Localization compilation now specifies an ArchiveName to use

Change 3031671 on 2016/06/28 by Nick.Darnell

	Editor - Checking to see if a weak variable is valid before using it in the editor build window.

Change 3032013 on 2016/06/28 by Matt.Kuhlenschmidt

	Added ability to invert the Y axis in editor viewports for mouse look and orbit

Change 3032495 on 2016/06/29 by Jamie.Dale

	Fixed some measuring issues with bi-directional text within a right-flowed document

	There were three main issues:
	  1) Measuring blocks was measuring visual glyphs rather than logical glyphs (this caused bad measures/wrapping and overlapped rendering).
	  2) The text layout would consider blocks visually contiguous without making sure the block flow direction matched the line flow direction (this caused bad highlights).
	  3) The text layout would fail to compensate for a non-contiguous block that had a flow direction different to the line flow direction (it was hard-coded for RTL in LTR, so broke for LTR in RTL - this caused bad highlights).

	#jira UE-32526

Change 3032533 on 2016/06/29 by Nick.Darnell

	UMG - The widget component now extends from UMeshComponent, it can have a custom material applied to it, in order to achieve cooler effects - like ignoring the depth buffer.  Users who use this option are encouraged to start with the widget components default material and work from there.  The widget component now offers the ability to automatically size the render target to be the desired size of the widget - note that this can go real bad if your widget wants to be really big.

Change 3032855 on 2016/06/29 by Alexis.Matte

	#jira UE-32508 Remove the cachewindow from the FTextInputMethodContext constructor since it will be cache only when the IME is activated

	#test please re-test also UE-32240

Change 3033145 on 2016/06/29 by Alex.Delesky

	#jira UE-32239 - The PropertyEditorModule will no longer cause a crash on editor shutdown if a SDetailsView widget tries to force refresh itself when the Slate application is no longer initialized.

Change 3033147 on 2016/06/29 by Alex.Delesky

	#jira UE-32326 - Clicking on the "Install {compiler}" button when trying to create a new code class or code project will now not crash the engine if it fails to open the installation file for write, nor will it create multiple notifications if the button is pressed repeatedly.

	This also addresses a potential issue with static initialization order when it comes to adding TickableEditorObjects to its corresponding array, since it was wholly possible for a statically initialized TickableEditorObject to initialize itself and add itself to the tickable objects arra before the tickable objects array was initialized, causing that object to not get ticked at runtime and causing a crash when the editor was closed.

Change 3033162 on 2016/06/29 by Alex.Delesky

	#jira UE-31827 - Undo/redo now works in the Material function editor.

Change 3033391 on 2016/06/29 by Matt.Kuhlenschmidt

	Fix post process settings blendable picker not being readable in the details panel

Change 3033498 on 2016/06/29 by Matt.Kuhlenschmidt

	Fixed huge number of redundant calls to CanEditChange and
	DiffersFromDefault that were causing massive performance loss when
	thousands of objects are selected. CanEditChange and DiffersFromDefault
	are now cached each time a property value changes.

	Fixed redundant calls for getting visualizers for each selected
	object.  This is now cached on selection

Change 3033504 on 2016/06/29 by Matt.Kuhlenschmidt

	Fix Mass customization on the body instance not working with undo/redo or reset to default

Change 3034357 on 2016/06/30 by Alex.Delesky

	#jira UE-31184 - Renamed the multiple collision components in the cascade particle system to more accurately reflect what they represent.

Change 3035915 on 2016/07/01 by Richard.TalbotWatkin

	Fix to SListPanel so that those with horizontal arrangement (i.e. from STileView) use the number of desired items instead of the number of actual items in order to calculate the desired size of the geometry.  This fixes the case where an STileView is contained within an SScrollBox.
	#jira UE-32195 - STileView no longer works correctly when placed inside of a SScrollBox

Change 3035951 on 2016/07/01 by Richard.TalbotWatkin

	Fixed issue when importing a brush, so that the brush is always validated (relinked), whether it be a static or dynamic brush. This is because the process of rebuilding a dynamic brush sets the link indices to signify FBspSurf indices from the UModel instead of FPoly indices (the FPoly::iLink member is overloaded in its meaning). Always forcing a relink correctly sets the linked list of coplanars.
	#jira UE-32087 - Crash occurs when creating Static Mesh from Trigger Volume

Change 3036991 on 2016/07/04 by Alexis.Matte

	#jira UETOOL-901 Scene importer now support the rigid mesh animation

Change 3037037 on 2016/07/04 by Jamie.Dale

	Fixed regression in editable text box alignment

	Text was no longer vertically aligned center since SEditableText was converted to use a text layout. This vertical alignment is now handled by the outer SEditableTextBox instead.

Change 3037057 on 2016/07/04 by Richard.TalbotWatkin

	Fixed screenshots when running automation tests so that they are saved locally when a FAutomationWorkerScreenMessage is received.
	#jira UE-29815 - In-game screenshot isn't working under certain circumstances

Change 3037082 on 2016/07/04 by Chris.Wood

	Added detection of asserts and passing assert flag and crash type string to crash reports.
	[UE-30592] - Crash Reporter should determine crash type on client and pass string to server

	Reviewe by Steve with reservations about the static variable for setting asserted state. While not thread-aware, this is probably accurate enough for the purpose of crash reporting, certainly for now. I'm submitting it like this because the work required to add fully thread-aware fix is not necessary at this point.

Change 3037095 on 2016/07/04 by Alexis.Matte

	Fix the bone name when duplicating a socket.

Change 3037453 on 2016/07/05 by Stephan.Jiang

	Adding ability to animate the root wigdet #2
	FHierarchyRoot adds the preview widget instead of CDO to selectedobjects in widgetblueprint
	the properties are then migrated back to the CDO

	#UE 31810

Change 3037487 on 2016/07/05 by Jamie.Dale

	Fixed crash caused by stale BP pointer

	#jira UE-32325

Change 3037488 on 2016/07/05 by Jamie.Dale

	Fixed a crash that could occur when a class and a folder had the same name

Change 3037526 on 2016/07/05 by Jamie.Dale

	Speculative fix for a potential race condition when shutting down the editor while a "launch" was in progress

	The launch-thread could potentially queue up a request after the game-thread had requested it cancel, and cleared out any queued tasks. This change has the game-thread wait for the launch-thread to acknowledge its cancellation before continuing with editor shutdown.

	#jira UE-17688

Change 3037557 on 2016/07/05 by Alex.Delesky

	#jira UE-32424 - Added a safeguard to ensure that renaming a world that was duplicated from another world would not crash the editor if both worlds' lightmaps and shadowmaps were still active in memory, due to the editor attempting to rename identical textures from different packages to the same location.

	The actual fix to this issue was performed in an earlier CL, but this should prevent the editor from crashing if the issue returns.

Change 3037558 on 2016/07/05 by Alex.Delesky

	#jira UE-32285 - Importing assets to the Content Browser via drag and drop operations are no longer permitted while the UI file picker dialog is opened.

Change 3037559 on 2016/07/05 by Alex.Delesky

	#jira UE-32075 - The user can no longer attempt to import non-FBX and non-OBJ files when importing into a level.

Change 3037593 on 2016/07/05 by Stephan.Jiang

	GitHub #2549: Add function for setting the playback rate of UMG animations
	original code shelved in CL 3033449

	#UE-32653

Change 3037605 on 2016/07/05 by Jamie.Dale

	Fixed infinite recursion that could happen when gather loc from an object with a custom callback

	#jira UE-32670

Change 3037649 on 2016/07/05 by Nick.Darnell

	PR #2538: [WidgetBlueprintLibrary] GetAllWidgetsOfClass, Added META ~ DeterminesOutputType, DynamicOutputParam, removes the need for extra cast,   Rama (Contributed by EverNewJoy)

Change 3037652 on 2016/07/05 by Nick.Darnell

	Clean - Removing commented out code.

Change 3037658 on 2016/07/05 by Matt.Kuhlenschmidt

	Fix initial hitch when dragging around in a color picker opened from a material expression node.

Change 3037679 on 2016/07/05 by Nick.Darnell

	Engine - Texture2D no longer forces the MIP level to 0 for TextureGroup_UI textures.

Change 3037757 on 2016/07/05 by Nick.Darnell

	PR #2447: WebBrowser widget: Added GetUrl method and OnUrlChanged property (Contributed by nelbok)

Change 3037840 on 2016/07/05 by Nick.Darnell

	UMG - Now allowing for spirtes to be used just like textures and materials on UMG widgets anywhere that takes a brush, can now also take a Sprite.  There is now a ISlateTextureAtlasInterface interface that any UObject may now implement if it wishes to integrate with UMG to provide its atlas data in a form Slate can understand.

Change 3037924 on 2016/07/05 by Jamie.Dale

	Re-ordered variable initialization to appease a warning on Mac

Change 3037981 on 2016/07/05 by Jamie.Dale

	Fixed crash where FColorStructCustomization could call SetPerObjectValues with an empty array

	#jira UE-32639

Change 3038075 on 2016/07/05 by Cody.Albert

	Removed misleading error message in HandleCECommand

	#jira 28007

Change 3038231 on 2016/07/05 by Alexis.Matte

	#jira UE-30694 We set the section collision only if there is an imported collision or a generated one. If there is no collision we do not set the collision flag.

Change 3038275 on 2016/07/05 by Alex.Delesky

	#jira UE-32689 - "Game Gets Mouse Control" will now override the Capture Mouse on Launch setting when launching the game from within a Level Viewport (i.e., within the editor window itself).

Change 3039310 on 2016/07/06 by Trung.Le

	#jira UE-25005 Change PIE Key Bindings
	- Removed Shift+F1 and Esc from BaseInput.ini
	- Created new customizable key binding for
	   + Shift+F1: same functionality.
	   + Esc: now will pause the play session and bring back the mouse cursor. Clicking the mouse on the viewport should resume play session.
	   + Shift+Esc: now will stop the play session

Change 3039458 on 2016/07/06 by Trung.Le

	Removed unused code in StaticMeshLight.cpp

Change 3039827 on 2016/07/06 by Frank.Fella

	FString - Fix divide overload path concatenation for empty paths since there are several places in the engine that expect using that doing { path / "" } will append a / onto path.

	#jira UE-31959

Change 3041094 on 2016/07/07 by Nick.Darnell

	WebBrowser - Fixing an issue where the web browser widget plugin wasn't loading soon enough to be properly loaded in time if it was referenced by game nessesary content thatloads in the Default stage of the pipeline, so moving it to PreDefault.

	#jira UE-32694

Change 3041110 on 2016/07/07 by Matt.Kuhlenschmidt

	Fix visualizers on blueprint actors not working when the internal components are trashed and replaced

Change 3041302 on 2016/07/07 by Chris.Wood

	Increased buffer size for crash uploads.
	[UE-32151] - High number of crashes read from S3 by Crash Report Process are failing to unpack

	Trivial change in dev branch - no code review

Change 3041969 on 2016/07/07 by Nick.Darnell

	UMG - Input Key Selector now no longer adds a bogus Selected Key property to the details panel.

Change 3041971 on 2016/07/07 by Nick.Darnell

	UMG - Not using separate settings for the Engine/Developer folders visible in the UMG palette, now just using the same setting that powers the content browser.

Change 3042612 on 2016/07/08 by Trung.Le

	#jira UE-25005, set Shift+Esc defaults to toggle play/pause and Esc remains defaults to quit

Change 3042732 on 2016/07/08 by mitchell.wilson

	Adding test content for UMG Paper 2d Atlas test

Change 3042780 on 2016/07/08 by mitchell.wilson

	Updating UMG_Paper2d test content for UMG Paper 2d Atlas testing

Change 3042870 on 2016/07/08 by mitchell.wilson

	Renaming UMG_Paper2d to UMG_Sprite

Change 3044104 on 2016/07/10 by Nick.Darnell

	PR #2104: Improved widget input support (Contributed by projectgheist)

Change 3044107 on 2016/07/10 by Nick.Darnell

	Slate - Fixing the slider handle rendering to no longer run off the edge and get cut off.

	#jira UE-25750

Change 3044377 on 2016/07/11 by Chris.Wood

	Add Slack messaging module - Epic Friday

Change 3044536 on 2016/07/11 by Alex.Delesky

	#jira UE-7293 - Mouse locking to viewport is now determined off an enum instead of a boolean, to allow for more flexibility when upgrading with new features.

Change 3044922 on 2016/07/11 by Nick.Darnell

	Slate/UMG - Working on better support for VR interactions with Slate widgets.  This change fixes a lot of issues with the way interaction works with slate widgets rendered in the virtual world.  Breakages, direct mouse interaction with widgets in the virtual world is no longer supported.  Those kinds of interactions must all use the WidgetInteractionComponent now, which by default works similar to the lasers in VREditor for interaction.  However - you can disable automatic hittesting, and instead provide a custom hitresult instead if you want to use screen tracing and act like you're just a mouse cursor that is supported.  Menu anchors now properly function inside of widgets in the virtual world.  Performance improvements - the viewport no longer arranges all 3d widgets every frame.  Additionally, Widget Components now support a whole bunch of methods for reducing how often they redraw to help control performance, they also support manual refresh.  This automatically works in tandem with the widget interaction component to request refresh whenever the widget interaction component is interacting with the widget, thus giving you a simple way to only redraw widgets that the user is hovering on top of.  Unrelated - this change also fixes Stop navigation commands not working with Next/Prev navigation - Wrap is still unsupported.

Change 3045157 on 2016/07/11 by Nick.Darnell

	Slate - Always consume the bottom face button of the analog cursor, even if it's a repeat.

Change 3045355 on 2016/07/11 by Matt.Kuhlenschmidt

	Added logging for unreproducible top 10 crash in matinee when a track ends up not being able to add a keyframe

Change 3045358 on 2016/07/11 by Alex.Delesky

	#jira UE-31179 - The editor should now log additional information and hit an assertion if the editor tries to construct FObjectOrAssetData using invalid data. This doesn't stop the crash, but should help get some extra info when it does break.

Change 3045371 on 2016/07/11 by Matt.Kuhlenschmidt

	Enable the widget reflector from the editor console by typing "widgetreflector"

Change 3045387 on 2016/07/11 by Stephan.Jiang

	Stripping off 'b' in the propertyname so that "Is Enabled" is animated properly.

	#UE-31874

Change 3046093 on 2016/07/12 by Nick.Darnell

	UMG - The Slider now exposes the IsFocusable option from Slate.

	#jira UE-32960

Change 3046094 on 2016/07/12 by Alexis.Matte

	#jira UE-32807 scene re-import blueprint hierarchy kept some part of old blueprint component value.

Change 3046104 on 2016/07/12 by Stephan.Jiang

	typo "Syc" causing the "Sync" button doesn't show Slateicon

	#UE-31409

Change 3046142 on 2016/07/12 by Nick.Darnell

	Orion - Upgrading more code to use the new input mode functions and not the deprecated ones.

Change 3046165 on 2016/07/12 by Nick.Darnell

	UMG - Fixing a crash on the widget component if the render target is null when reapplied through widget component data.

	#jira UE-32844

Change 3046255 on 2016/07/12 by Nick.Darnell

	UT - More build warning fixes for the new Input Mode methods.

Change 3046604 on 2016/07/12 by Richard.Hinckley

	Adding a template file and code to support creating a UInterface directly from the New C++ Class wizard.

Change 3047071 on 2016/07/12 by Matt.Kuhlenschmidt

	Better way of summoning the widget reflector from the console

Change 3047842 on 2016/07/13 by Matt.Kuhlenschmidt

	Mark Subdivision surface setting as advanced since it is experimental and definitely for advanced users only

Change 3048754 on 2016/07/13 by Trung.Le

	#jira UE-32159 Automatically regain focus after user gets mouse control during PIE session so we can continue process PIE keybinding commands

Change 3048756 on 2016/07/13 by Trung.Le

	Removed default toggle pause/play keybinding from BaseInput.ini, instead we should use the action defined in DebuggerCommands that is customizable

Change 3048865 on 2016/07/13 by Trung.Le

	#jira UE-32159 SGlobalPlayWorldActions widget shouldn't clear out active widget pointer when it's being handled properly

Change 3048892 on 2016/07/13 by Nick.Darnell

	UMG - Fixing a problem with the interaction component, it now does some basic intelligent ignoring of anything it's attached to - excluding widget components.  So it's easier to attach it to things that might be inside of a say a player collision capsule.  Also removing the 'Max Interaction Distance' from the widget component as that is no longer the arbitor of interaction distance.

	#jira UE-33250

Change 3049096 on 2016/07/13 by Trung.Le

	Wrap SGlobalPlayActions around ViewportWidget instead of making it a child of ViewportWidget. This was causing PIE to stop working when there are other UMG in game.
	#jira UE-33259

Change 3049177 on 2016/07/13 by Stephan.Jiang

	Fixing the "No Animation Selected" tag shows up after switching back from Graph to Designer.

	#UE-33016

Change 3049726 on 2016/07/14 by Stephan.Jiang

	Adding icons for terrain mirror tool

	#UE-20588

Change 3049957 on 2016/07/14 by Nick.Darnell

	Slate - Fixing a small bug in the virtual user function - was preventing getting the same virtual user multiple times if it had already been created.  Adding an option to the widget component to control the focusabilty of the underlying slate window that's created to host the widget content.  Adding an option to the widget interaction component to control if it should be simulating mouse input at all - use this to effectively disable hit testing, and changing hover states and the like.

Change 3049994 on 2016/07/14 by Stephan.Jiang

	Set viewed animtion to current animtion after switching from Graph to Designer
	(This is for "No Animation Selected" showing up when switching)

	#UE-33016

Change 3050194 on 2016/07/14 by Stephan.Jiang

	Added ability to replace the widget the track is currently bound to
	Also includes changes in WidgetBlueprintEditor to send delegate to AnimationtabSummoner when switching from Graph to Designer

	#UE-31809

[CL 3050870 by Matt Kuhlenschmidt in Main branch]
2016-07-14 19:07:16 -04:00

4287 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, -1), Compiler->Constant(0)), Compiler->Constant(1.f / 2.2f));
}
else if (Property == MP_WorldPositionOffset)
{
//set to 0 to prevent off by 1 pixel errors
Ret = Compiler->Constant(0.0f);
}
else if (Property >= MP_CustomizedUVs0 && Property <= MP_CustomizedUVs7)
{
const int32 TextureCoordinateIndex = Property - MP_CustomizedUVs0;
Ret = Compiler->TextureCoordinate(TextureCoordinateIndex, false, false);
}
else
{
Ret = Compiler->Constant(1.0f);
}
// output should always be the right type for this property
return Compiler->ForceCast(Ret, GetMaterialPropertyType(Property));
}
void FMatExpressionPreview::NotifyCompilationFinished()
{
if (Expression.IsValid() && Expression->GraphNode)
{
CastChecked<UMaterialGraphNode>(Expression->GraphNode)->bPreviewNeedsUpdate = true;
}
}
/////////////////////
// FMaterialEditor //
/////////////////////
void FMaterialEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& 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 = GetFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel());
Canvas->DrawShadowedString(
5,
DrawPositionY,
*FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers),
FontToUse,
SamplersUsed > MaxSamplers ? FLinearColor(1,0,0) : FLinearColor(1,1,0)
);
DrawPositionY += SpacingBetweenLines;
}
}
for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++)
{
Canvas->DrawShadowedString(5, DrawPositionY, *FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]), FontToUse, FLinearColor(1, 0, 0));
DrawPositionY += SpacingBetweenLines;
}
}
void FMaterialEditor::DrawMessages( FViewport* InViewport, FCanvas* Canvas )
{
if( PreviewExpression != NULL )
{
Canvas->PushAbsoluteTransform( FMatrix::Identity );
// The message to display in the viewport.
FString Name = FString::Printf( TEXT("Previewing: %s"), *PreviewExpression->GetName() );
// Size of the tile we are about to draw. Should extend the length of the view in X.
const FIntPoint TileSize( InViewport->GetSizeXY().X, 25);
const FColor PreviewColor( 70,100,200 );
const FColor FontColor( 255,255,128 );
UFont* FontToUse = GEditor->EditorFont;
Canvas->DrawTile( 0.0f, 0.0f, TileSize.X, TileSize.Y, 0.0f, 0.0f, 0.0f, 0.0f, PreviewColor );
int32 XL, YL;
StringSize( FontToUse, XL, YL, *Name );
if( XL > TileSize.X )
{
// There isn't enough room to show the preview expression name
Name = TEXT("Previewing");
StringSize( FontToUse, XL, YL, *Name );
}
// Center the string in the middle of the tile.
const FIntPoint StringPos( (TileSize.X-XL)/2, ((TileSize.Y-YL)/2)+1 );
// Draw the preview message
Canvas->DrawShadowedString( StringPos.X, StringPos.Y, *Name, FontToUse, FontColor );
Canvas->PopTransform();
}
}
void FMaterialEditor::RecenterEditor()
{
UEdGraphNode* FocusNode = NULL;
if (MaterialFunction)
{
bool bSetPreviewExpression = false;
UMaterialExpressionFunctionOutput* FirstOutput = NULL;
for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
{
UMaterialExpression* Expression = Material->Expressions[ExpressionIndex];
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression);
if (FunctionOutput)
{
FirstOutput = FunctionOutput;
if (FunctionOutput->bLastPreviewed)
{
bSetPreviewExpression = true;
FocusNode = FunctionOutput->GraphNode;
}
}
}
if (!bSetPreviewExpression && FirstOutput)
{
FocusNode = FirstOutput->GraphNode;
}
}
else
{
FocusNode = Material->MaterialGraph->RootNode;
}
if (FocusNode)
{
JumpToNode(FocusNode);
}
else
{
// Get current view location so that we don't change the zoom amount
FVector2D CurrLocation;
float CurrZoomLevel;
GraphEditor->GetViewLocation(CurrLocation, CurrZoomLevel);
GraphEditor->SetViewLocation(FVector2D::ZeroVector, CurrZoomLevel);
}
}
bool FMaterialEditor::SetPreviewAsset(UObject* InAsset)
{
if (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)
{
// 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);
}
GWarn->EndSlowTask();
}
void FMaterialEditor::UpdateMaterialInfoList(bool bForceDisplay)
{
TArray< TSharedRef<class FTokenizedMessage> > Messages;
TArray<TSharedPtr<FMaterialInfo>> TempMaterialInfoList;
ERHIFeatureLevel::Type FeatureLevelsToDisplay[2];
int32 NumFeatureLevels = 0;
// Always show basic features so that errors aren't hidden
FeatureLevelsToDisplay[NumFeatureLevels++] = GMaxRHIFeatureLevel;
if (bShowMobileStats)
{
FeatureLevelsToDisplay[NumFeatureLevels++] = ERHIFeatureLevel::ES2;
}
if (NumFeatureLevels > 0)
{
UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial;
for (int32 i = 0; i < NumFeatureLevels; ++i)
{
TArray<FString> CompileErrors;
ERHIFeatureLevel::Type FeatureLevel = FeatureLevelsToDisplay[i];
const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(FeatureLevel);
if (MaterialFunction && ExpressionPreviewMaterial)
{
// Add a compile error message for functions missing an output
CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel)->GetCompileErrors();
bool bFoundFunctionOutput = false;
for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++)
{
if (Material->Expressions[ExpressionIndex]->IsA(UMaterialExpressionFunctionOutput::StaticClass()))
{
bFoundFunctionOutput = true;
break;
}
}
if (!bFoundFunctionOutput)
{
CompileErrors.Add(TEXT("Missing a function output"));
}
}
else
{
CompileErrors = MaterialResource->GetCompileErrors();
}
// Only show general info if stats enabled
if (!MaterialFunction && bShowStats)
{
// Display any errors and messages in the upper left corner of the viewport.
TArray<FString> Descriptions;
TArray<int32> InstructionCounts;
TArray<FString> EmptyDescriptions;
TArray<int32> EmptyInstructionCounts;
MaterialResource->GetRepresentativeInstructionCounts(Descriptions, InstructionCounts);
//Built in stats is no longer exposed to the UI but may still be useful so they're still in the code.
bool bBuiltinStats = false;
const FMaterialResource* EmptyMaterialResource = EmptyMaterial ? EmptyMaterial->GetMaterialResource(FeatureLevel) : NULL;
if (bShowBuiltinStats && bStatsFromPreviewMaterial && EmptyMaterialResource && InstructionCounts.Num() > 0)
{
EmptyMaterialResource->GetRepresentativeInstructionCounts(EmptyDescriptions, EmptyInstructionCounts);
if (EmptyInstructionCounts.Num() > 0)
{
//The instruction counts should match. If not, the preview material has been changed without the EmptyMaterial being updated to match.
if (ensure(InstructionCounts.Num() == EmptyInstructionCounts.Num()))
{
bBuiltinStats = true;
}
}
}
for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++)
{
FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"),*Descriptions[InstructionIndex], InstructionCounts[InstructionIndex]);
if (bBuiltinStats)
{
InstructionCountString += FString::Printf(TEXT(" - Built-in instructions: %u"), EmptyInstructionCounts[InstructionIndex]);
}
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InstructionCountString, FLinearColor::Yellow)));
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
Line->AddToken(FTextToken::Create(FText::FromString(InstructionCountString)));
Messages.Add(Line);
}
// Display the number of samplers used by the material.
const int32 SamplersUsed = MaterialResource->GetSamplerUsage();
if (SamplersUsed >= 0)
{
int32 MaxSamplers = GetFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel());
FString SamplersString = FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers);
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(SamplersString, FLinearColor::Yellow)));
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create( EMessageSeverity::Info );
Line->AddToken( FTextToken::Create( FText::FromString( SamplersString ) ) );
Messages.Add(Line);
}
}
FString FeatureLevelName;
GetFeatureLevelName(FeatureLevel,FeatureLevelName);
for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++)
{
FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]);
TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red)));
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create( EMessageSeverity::Error );
Line->AddToken( FTextToken::Create( FText::FromString( ErrorString ) ) );
Messages.Add(Line);
bForceDisplay = true;
}
}
}
bool bNeedsRefresh = false;
if (TempMaterialInfoList.Num() != MaterialInfoList.Num())
{
bNeedsRefresh = true;
}
for (int32 Index = 0; !bNeedsRefresh && Index < TempMaterialInfoList.Num(); ++Index)
{
if (TempMaterialInfoList[Index]->Color != MaterialInfoList[Index]->Color)
{
bNeedsRefresh = true;
break;
}
if (TempMaterialInfoList[Index]->Text != MaterialInfoList[Index]->Text)
{
bNeedsRefresh = true;
break;
}
}
if (bNeedsRefresh)
{
MaterialInfoList = TempMaterialInfoList;
/*TSharedPtr<SWidget> TitleBar = GraphEditor->GetTitleBar();
if (TitleBar.IsValid())
{
StaticCastSharedPtr<SMaterialEditorTitleBar>(TitleBar)->RequestRefresh();
}*/
StatsListing->ClearMessages();
StatsListing->AddMessages(Messages);
if (bForceDisplay)
{
TabManager->InvokeTab(StatsTabId);
}
}
}
void FMaterialEditor::UpdateGraphNodeStates()
{
const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel);
const FMaterialResource* ErrorMaterialResourceES2 = NULL;
if (bShowMobileStats)
{
ErrorMaterialResourceES2 = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(ERHIFeatureLevel::ES2) : Material->GetMaterialResource(ERHIFeatureLevel::ES2);
}
bool bUpdatedErrorState = false;
// Have to loop through everything here as there's no way to be notified when the material resource updates
for (int32 Index = 0; Index < Material->MaterialGraph->Nodes.Num(); ++Index)
{
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Material->MaterialGraph->Nodes[Index]);
if (MaterialNode)
{
MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression);
MaterialNode->bIsErrorExpression = (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE)
|| (ErrorMaterialResourceES2 && ErrorMaterialResourceES2->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE);
if (MaterialNode->bIsErrorExpression && !MaterialNode->bHasCompilerMessage)
{
bUpdatedErrorState = true;
MaterialNode->bHasCompilerMessage = true;
MaterialNode->ErrorMsg = MaterialNode->MaterialExpression->LastErrorText;
MaterialNode->ErrorType = EMessageSeverity::Error;
}
else if (!MaterialNode->bIsErrorExpression && MaterialNode->bHasCompilerMessage)
{
bUpdatedErrorState = true;
MaterialNode->bHasCompilerMessage = false;
}
}
}
if (bUpdatedErrorState)
{
// Rebuild the SGraphNodes to display/hide error block
GraphEditor->NotifyGraphChanged();
}
}
void FMaterialEditor::AddReferencedObjects( FReferenceCollector& Collector )
{
Collector.AddReferencedObject( EditorOptions );
Collector.AddReferencedObject( Material );
Collector.AddReferencedObject( OriginalMaterial );
Collector.AddReferencedObject( MaterialFunction );
Collector.AddReferencedObject( ExpressionPreviewMaterial );
Collector.AddReferencedObject( EmptyMaterial );
}
void FMaterialEditor::BindCommands()
{
const FMaterialEditorCommands& Commands = FMaterialEditorCommands::Get();
ToolkitCommands->MapAction(
Commands.Apply,
FExecuteAction::CreateSP( this, &FMaterialEditor::OnApply ),
FCanExecuteAction::CreateSP( this, &FMaterialEditor::OnApplyEnabled ) );
ToolkitCommands->MapAction(
FEditorViewportCommands::Get().ToggleRealTime,
FExecuteAction::CreateSP( 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 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 = NULL;
Material->Expressions.Add(NewExpression);
// There can be only one default mesh paint texture.
UMaterialExpressionTextureBase* TextureSample = Cast<UMaterialExpressionTextureBase>( NewExpression );
if( TextureSample )
{
TextureSample->IsDefaultMeshpaintTexture = false;
}
NewExpression->UpdateParameterGuid(true, true);
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;
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