Files
UnrealEngineUWP/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp
Matt Kuhlenschmidt 67a0d73fa0 Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3082391)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3051464 on 2016/07/15 by Nick.Darnell

	Regression Testing - Several upgrades to the functional testing system, better tracking of failure cases, some source line failure detection, trying to make it easier to run a specific test on a map.  Some UI improvements, easier access to the automation system.  Lots more refactoring to come, lots of improvements are still needed in transmitting screenshots and just generally building a automation report we could dump from the build machines.

Change 3051465 on 2016/07/15 by Nick.Darnell

	Adding the "Engine Test" project our one stop shope for running automation tests in the engine to try and reduce regressions.

Change 3051847 on 2016/07/15 by Matt.Kuhlenschmidt

	Fixed material editor viewport messages being blocked by viewport toolbar

Change 3052025 on 2016/07/15 by Nick.Darnell

	Moving the placement mode hooks out of functional testing module, moving them into the editor automation module.

Change 3053508 on 2016/07/18 by Stephan.Jiang

	Copy,Cut,Paste tracks, not for mastertracks yet.

	#UE-31808

Change 3054723 on 2016/07/18 by Stephan.Jiang

	Small fixes for typo & comments

Change 3055996 on 2016/07/19 by Trung.Le

	PIE: No longer auto resume game in PIE on focus received

Change 3056106 on 2016/07/19 by Trung.Le

	Back out changelist 3055996. Build break.

Change 3056108 on 2016/07/19 by Stephan.Jiang

	Updating "SoundConcurrency" asseticon

Change 3056389 on 2016/07/19 by Trung.Le

	PIE: No longer auto resume game in PIE on focus received
	#jira UE-33339

Change 3056396 on 2016/07/19 by Matt.Kuhlenschmidt

	More perf selection improvements:
	- Static meshes now go through the static draw path when rendered for selection outline instead of just rendering using the dynamic path

Change 3056758 on 2016/07/19 by Stephan.Jiang

	Update SelectedWidgets in WidgetblueprintEditor to match the selected tracks in sequencer.

Change 3057519 on 2016/07/20 by Matt.Kuhlenschmidt

	Another fix for selecting lots of objects taking forever.  This one is due to repeated Modify calls if there are groups in the selection.  Each group actor selected iterates through each object selected during USelection::Modify!

Change 3057635 on 2016/07/20 by Stephan.Jiang

	Updating visual logger icon UI

Change 3057645 on 2016/07/20 by Richard.TalbotWatkin

	Fixed single player PIE so the window position is correctly fetched and saved, even when running a dedicated server. This does not interfere with stored positions for multiple PIE, which uses ULevelEditorPlaySettings::MultipleInstancePositions.
	#jira UE-33416 - New Editor PIE window does not center to screen when running with a dedicated server

Change 3057868 on 2016/07/20 by Richard.TalbotWatkin

	Spline component improvements, both tools and runtime:

	- SplineComponentVisualizer now works within the Blueprint editor. This works via a generic extension added to the base ComponentVisualizer class which correctly propagates modified properties from the preview actor to the archetype, and then on to any instances whose properties are at the default value.

	- The above feature required a breaking change to USplineComponent - namely, the three FInterpCurve properties have been collected together into a struct and added as a single property. This is so that changes to the length of one of the FInterpCurves marks all three as dirty and needing rebuilding.

	- Added a custom version for SplineComponent and provded serialization fixes.

	- Added a details customization to SplineComponent to hide the raw FInterpCurve properties.

	- Added a custom detail builder category which polls the SplineComponentVisualizer each tick and provides numerical editing for spline points which are selected in the visualizer.

	- Relaxed the limitation that SplineComponent keys need to have an increment of 1.0.  Now any SplineComponent key can be set.  The details customization enforces that the sequence remains strictly ascending.

	- Allowed an explicit loop point to be specified for closed splines.

	- Allowed discontinuous splines by no longer forcing the ArriveTangent and LeaveTangent to be equal.

	- Added some new Blueprintable methods for building splines with an FSplinePoint struct, which allows all of a spline point's properties to be specified, and added to the FInterpCurves sorted by the input key.

	- Fixed the logic which determines whether the UCS has modified the spline curves.

	- Added UActorComponent::RemoveUCSModifiedProperties, which allows a component to remove any properties from the cached list which it doesn't want to be considered as 'modified'. This is used to distinguish the case of properties preserved by the SplineInstanceDataCache from those genuinely modified by the UCS.

	- Fixed "Apply Instance Changes to Blueprint" so that edited spline data can be applied to the archetype.

	- Fixed some issues with the spline component visualizer to make it generate appropriate up vectors if scale and rotation are enabled.

	#jira UETOOL-766 - Spline tool improvements
	#jira UE-33049 - Transform widget visible in blueprint viewport when editing spline points in editor viewport
	#jira UE-9062 - Spline editing: It would be nice to be able to type in a specific value for a point
	#jira UE-7476 - Add ability to edit SplineComponent in BP editor (not just instance in level)
	#jira UE-13082 - Users would like a snapping feature for splines
	#jira UE-13568 - Additional Spline Component Functionality
	#jira UE-17822 - It would be useful to be able to update a bp spline layout from the editor viewport.

Change 3057895 on 2016/07/20 by Richard.TalbotWatkin

	Mesh paint bugfixes and improvements.

	Changes to RerunConstructionScript so that OnObjectsReplaced is called correctly on all components, whether they have been created by the SCS or the UCS. Previously, components created by the UCS were not being handled, and components created by the SCS were not always being matched.  Now a serialized index is maintained for UCS-created objects, which is matched after the construction scripts have been executed.

	This will fix issues with the mesh paint tool, and any other editor tool which hooks into the OnObjectsReplaced callback in order to update its internal cache of component pointers, for example, the component visualizer render list.

	#jira UE-33010 - Crash changing mesh paint material in blueprint, then changing to a different mode tab
	#jira UE-32279 - Editor crashes when reselecting a mesh in paint mode
	#jira UE-31763 - [CrashReport] UE4Editor_MeshPaint!FMulticastDelegateBase<FWeakObjectPtr>::RemoveAll() [multicastdelegatebase.h:75]
	#jira UE-30661 - Vertex Painting changes collision complexity if the asset is saved while vertex painting

Change 3057966 on 2016/07/20 by Richard.TalbotWatkin

	Renamed IsEditingArchetype to IsVisualizingArchetype in the ComponentVisualizer API.
	#jira UE-33049 - Transform widget visible in blueprint viewport when editing spline points in editor viewport

Change 3058009 on 2016/07/20 by Richard.TalbotWatkin

	Fixed build failure due to changes to FComponentVisualizer API, as of CL 3057868.

Change 3058047 on 2016/07/20 by Stephan.Jiang

	Fixing error on previous CL: 3056758
	(extra qualification)

Change 3058266 on 2016/07/20 by Nick.Darnell

	Automation - Work continues on automation integrating some ideas form a licensee.  Continuing to work on the usability aspects, I've made it possible for tests to provide custom open commands, as well as have complex subclasses that do different things.  The functional tests now have a custom open command they emit that makes it so clicking on a test opens not the C++ location where the functional test macro lives, but instead the map, AND focuses the functional test actor.

Change 3058282 on 2016/07/20 by Matt.Kuhlenschmidt

	PR #2611: Fix spurious component diff when properties are in subcategories (Contributed by CA-ADuran)

Change 3059214 on 2016/07/21 by Richard.TalbotWatkin

	Further fixes to visualizers following Component Visualizer API change.

Change 3059260 on 2016/07/21 by Richard.TalbotWatkin

	Template specialization not allowed in class scope, but Visual Studio allows it anyway.  Fixed for clang.

Change 3059543 on 2016/07/21 by Stephan.Jiang

	Changeing level details icon

Change 3059732 on 2016/07/21 by Stephan.Jiang

	Directional Light icon update

Change 3060095 on 2016/07/21 by Stephan.Jiang

	Directional Light editor icon asset changed

Change 3060129 on 2016/07/21 by Nick.Darnell

	Automation - The session browser now attempts to select the app instance if no other thing is selected when it refreshes.  This is to try and make it easier to use when you first bring it up and nothing is selected when most of the time you're going to use it on your own instance.

Change 3061735 on 2016/07/22 by Stephan.Jiang

	Improve UMG replace with in HierarchyView function

	#UE-33582

Change 3062059 on 2016/07/22 by Stephan.Jiang

	Strip off "b" in propertyname in replace with function for tracks.

Change 3062146 on 2016/07/22 by Stephan.Jiang

	checkin with CL: 3061735

Change 3062182 on 2016/07/22 by Stephan.Jiang

	Change both animation bindings' widget name when renameing the widget so the slot content is still valid

Change 3062257 on 2016/07/22 by Stephan.Jiang

	comments

Change 3062381 on 2016/07/22 by Nick.Darnell

	Build - Adding #undef LOCTEXT_NAMESPACE to try and fix the build.

Change 3062924 on 2016/07/25 by Chris.Wood

	Fix a crash in CrashReportClient that happens when the CrashReportReceiver is not responding to pings and there are no PendingReportDirectories.

	This is a change in the UE4 stream depot based on a fix in the Fortnite stream depot -> JIRA FORT-27570

Change 3063017 on 2016/07/25 by Matt.Kuhlenschmidt

	PR #2618: DebuggerCommand not recording PlayLocationString (Contributed by ungalyant)

Change 3063021 on 2016/07/25 by Matt.Kuhlenschmidt

	PR #2619: added a search box to ModuleUI (Contributed by straymist)

Change 3063084 on 2016/07/25 by Matt.Kuhlenschmidt

	Fix "YesToAll" when deleting referenced actors overriding the "YesToAll" state for other referenced messages.

	https://jira.ol.epicgames.net/browse/UE-33651
	#jira UE-33651

Change 3063091 on 2016/07/25 by Alex.Delesky

	#jira UE-32949 - Truncating the hue inside the theme color block tooltip to only display whole numbers, to match how the color picker displays the hue value inside the hue scrubber.

Change 3063388 on 2016/07/25 by Matt.Kuhlenschmidt

	Selection Perf:
	- Fix large FName creation time when selecting thousands of objects

Change 3063568 on 2016/07/25 by Matt.Kuhlenschmidt

	Selection Perf:
	- Modified how USelection stores classes.  Classes are now in a  TSet and can be accessed efficiently using IsClassSelected.  The old unused way of checking if a selection has a class by iterating through them is deprecated
	- USelection no longer directly checks if an item is already selected with a costly n^2 search.  The check is done by using the already existing UObject selected annotation
	- Object property nodes no longer perform an n^2 check for object uniqueness when objects are added to details panels.  This is now left up to the caller to avoid
	- Eliminated useless work on FObjectPropertyNode::GetReadAddressUncached.  If a read address list is not passed in we'll not attempt to the work to populate it
	- Removed expensive checking for brush actors when any actor is selected

Change 3063749 on 2016/07/25 by Stephan.Jiang

	Disallow naming the widgetanimation to the same name with a override function in uuserwidget, because it will trigger a breakpoint in Rename()

	#jira UE-33711

Change 3064585 on 2016/07/26 by Matt.Kuhlenschmidt

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 3064612 on 2016/07/26 by Alex.Delesky

	#jira UE-33712 - Deleting many assets at once will now batch SourceControl commands rather than executing one for each asset.

Change 3064647 on 2016/07/26 by Alexis.Matte

	#jira UE-33274 dont hash the same file over and over when importing multiple asset from one fbx file.

Change 3064739 on 2016/07/26 by Matt.Kuhlenschmidt

	Fixed typo

Change 3064795 on 2016/07/26 by Jamie.Dale

	Fixed typo in FLocalizationModule::GetLocalizationTargetByName

	#jira UE-32961

Change 3066461 on 2016/07/27 by Jamie.Dale

	Enabled stable localization keys

Change 3066463 on 2016/07/27 by Jamie.Dale

	Set "Build Engine Localization" to upload all cultures to ensure we don't lose translation due to the archive keying changes

Change 3066467 on 2016/07/27 by Jamie.Dale

	Updated internationalization archives to store translations per-identity

	This allows translators to translate each instance of a piece of text based upon their context, rather than requiring a content producer to go back and give the entry a unique namespace. It also allows us to optionally compile out-of-date translations, as they are now mapped to their source identity (namespace + key) rather than their source text.

	Major changes:
	 - Added FLocTextHelper. This acts as a high-level API for uncompiled localized text, and replaces all the old ad-hoc loading/saving of manifests and archives, ensuring that everything is consistently using source control, and that older archives can be upgraded correctly to the new format. It also takes care of some of the quirks of our archives, such as native translations. All major localization commandlets have been updated to use FLocTextHelper.
	 - Moved FTextLocalizationResourceGenerator from Core to Internationalization. This also allows IJsonInternationalizationManifestSerializer and IJsonInternationalizationArchiveSerializer to be removed, and for FJsonInternationalizationManifestSerializer and FJsonInternationalizationArchiveSerializer to have all their functions become static.
	 - FTextLocalizationResourceGenerator being moved from Core meant that FTextLocalizationManager::LoadFromManifestAndArchives was also removed. This functionality is now handled by FTextLocalizationResourceGenerator::GenerateAndUpdateLiveEntriesFromConfig.
	 - The RepairLocalizationData commandlet has been removed. This existed to fix a change that pre-dated 4.0 so no such data should exist in the wild, and the commandlet couldn't be updated to work with the new API (we handle format upgrades in-place now).
	 - Removed FInternationalizationArchive::FindEntryBySource as it is no-longer safe to use. All existing code has been updated to use FInternationalizationArchive::FindEntryByKey instead.

	Workflow changes:
	 - Archive conditioning now only adds new entries if they don't exist in the archive. This allows us to persist any existing translations, even if they're for old source text (caveat: native archives still update existing entries if the source is changed).
	 - PO export now sets the msgctx for each entry to be "namespace,key", rather than only doing it when the entry had key meta-data.
	 - PO import will now update both the source and translation stored in the archive to match the current PO data. This is the primary method by which stale source->translation pairs are updated.
	 - LocRes compilation may now optionally compile stale translations. There's an option controlling this (defaulted to off) that can be changed via the Localization Dashboard (or added to an existing config file).

	Format changes:
	 - The archive version was bumped to 2.
	 - Archive entries now use the "Key" entry to store the key from the source text. Previously this "Key" entry was used to store the key meta-data, but that now exists within a "MetaData" sub-object. Loading handles this correctly based upon the archive version.

	#jira UETOOL-897
	#jira UETOOL-898
	#jira UE-29481

Change 3066487 on 2016/07/27 by Matt.Kuhlenschmidt

	Attempt to fix linux compilation

Change 3066504 on 2016/07/27 by Matt.Kuhlenschmidt

	Fixed data tables with structs crashing due to recent editor selection optimizations

Change 3066886 on 2016/07/27 by Jamie.Dale

	Added required data to accurately detect TZ (needed for DST)

	#jira UE-28511

Change 3067122 on 2016/07/27 by Jamie.Dale

	Added AsTime, AsDateTime, and AsDate overrides to BP to let you format a UTC time in a given timezone (default is the local timezone).

	Previously you could only format times using the "invariant" timezone, which assumed that the time was already specified in the correct timezone for display.

Change 3067227 on 2016/07/27 by Jamie.Dale

	Added a test to verify that the ICU timezone is set correctly to produce local time (including DST)

Change 3067313 on 2016/07/27 by Richard.TalbotWatkin

	Fixed SplineComponent constructor so that old assets (prior to the property changes) load correctly if they had properties at default values.
	#jira UE-33669 - Crash in Dev-Editor

Change 3067736 on 2016/07/27 by Stephan.Jiang

	Border changes for experimental classes warning

Change 3067769 on 2016/07/27 by Stephan.Jiang

	HERE BE DRAGONS

	for experimental class warning

	#UE-33780

Change 3068192 on 2016/07/28 by Alexis.Matte

	#jira UE-33586 make sure we remove any false warning when running fbx automation test.

Change 3068264 on 2016/07/28 by Jamie.Dale

	Removed some code that was no longer needed and could cause a crash

	#jira UE-33342

Change 3068293 on 2016/07/28 by Alex.Delesky

	#jira UE-33620 - Comments on constant and parameter nodes in the Material Editor will now persist when converting them.

Change 3068481 on 2016/07/28 by Stephan.Jiang

	Adding Options to show/hide soft & hard references & dependencies in References Viewer

	#jira UE-33746

Change 3068585 on 2016/07/28 by Richard.TalbotWatkin

	Fix to Spline Mesh collision building so that geometry does not default to being auto-inflated in PhysX.

Change 3068701 on 2016/07/28 by Matt.Kuhlenschmidt

	Fixed some issues with the selected classes not updating when objects are deselected

Change 3069335 on 2016/07/28 by Jamie.Dale

	Fixed unintended error when trying to load a manifest/archive that didn't exist
	Fixed a warning when trying to load a PO file that didn't exist

Change 3069408 on 2016/07/28 by Alex.Delesky

	#jira UE-33429 - The editor should no longer hit an ensure if the user attempts to drop a tab into a tab well before the tab well has a chance to acknowledge its been dragged into a tab well.

Change 3069878 on 2016/07/29 by Jamie.Dale

	Fixed include casing

	#jira UE-33910

Change 3071807 on 2016/08/01 by Matt.Kuhlenschmidt

	PR #2654: Fix the spell'ing of "diff'ing" and "diff'd". (Contributed by geary)

Change 3071813 on 2016/08/01 by Jamie.Dale

	Fixed include casing

	#jira UE-33936

Change 3072043 on 2016/08/01 by Jamie.Dale

	Fixed FText formatting of pre-Gregorian dates

	We now convert to an ICU UDate via an ICU GregorianCalendar, as UE4 and ICU have a different time scale for pre-Gregorian dates.

	#jira UE-14504

Change 3072066 on 2016/08/01 by Jamie.Dale

	PR #2590: FEATURE: Collapse/expand folders in the outliner (Contributed by projectgheist)

Change 3072149 on 2016/08/01 by Jamie.Dale

	We no longer use the editor culture when running with -game

Change 3072169 on 2016/08/01 by Richard.TalbotWatkin

	A couple of changes to the BSP code:
	* Fixed longstanding issue where sometimes BSP geometry is not rebuilt correctly after editing it.  This was due to poly normals not being recalculated after translating vertices in Geometry Mode.
	* Fixed corruption to FPoly::iLink as it is overloaded to have two meanings: when building BSP, it represents the surface index of the next coplanar surface (and adding a new BSP node uses this to determine whether a new surface needs to be added or not). In other operations it represents an FPoly index, in general this is used more in editor geometry operations.  This fixes various crashes which arose from rebuilding BSP resulting in invalid FPoly indices.

	#jira UE-12157 - BSP brushes break when non-standard subtractive bsp brushes are used
	#jira UE-32087 - Crash occurs when creating Static Mesh from Trigger Volume

Change 3072221 on 2016/08/01 by Jamie.Dale

	Fixed "Launch On" not providing the correct cultures to StartCookByTheBookInEditor

	#jira UE-33001

Change 3073389 on 2016/08/02 by Matt.Kuhlenschmidt

	Added ability to vsync the editor.  Disabled by default.  Set r.VSyncEditor to 1 to enable it.

	Reimplemented this change from the siggraph demo stream

Change 3073396 on 2016/08/02 by Matt.Kuhlenschmidt

	Removed unused code as suggested by a pull request

Change 3073750 on 2016/08/02 by Richard.TalbotWatkin

	Fixed formatting (broken in CL 3057895) in anticipation of merge from Main.

Change 3073789 on 2016/08/02 by Jamie.Dale

	Added a way to mark text in text properties as culture invariant

	This allows you to flag properties containing text that doesn't need to be gathered.

	#jira UE-33713

Change 3073825 on 2016/08/02 by Stephan.Jiang

	Material Editor: Highligh all Nodes connect to an input.

	#jira UE-32502

Change 3073947 on 2016/08/02 by Stephan.Jiang

	UMG Project settings to show/hide different classes and categories in Palette view.

	--under Project Settings ->Editor->UMG Editor

Change 3074012 on 2016/08/02 by Stephan.Jiang

	Minor changes and comments for CL: 3073947

Change 3074029 on 2016/08/02 by Jamie.Dale

	Deleting folders in the Content Browser now removes the folder from disk

	#jira UE-24303

Change 3074054 on 2016/08/02 by Matt.Kuhlenschmidt

	Added missing stats to track pooled vertex and index buffer cpu memory
	A new slate allocator was added to track memory usage for this case.

Change 3074056 on 2016/08/02 by Matt.Kuhlenschmidt

	Renamed a few slate stats for consistency

Change 3074810 on 2016/08/02 by Matt.Kuhlenschmidt

	Moved geometry cache asset type to the animation category.  It is not a basic asset type

Change 3074826 on 2016/08/02 by Matt.Kuhlenschmidt

	Fix a few padding and sizing issues

Change 3075322 on 2016/08/03 by Matt.Kuhlenschmidt

	Settings UI improvements
	* Added the ability to search through all settings at once
	* Settings files which are not checked out are no longer grayed out.  The editor now attempts to check out the file automatically if connected to source control and if that fails it marks the settings file writiable so it can save the setting properly

	-------
	* This change adds a refactor to the details panel to support multiple top level objects existing in the details panel at once instead of combining all passed in objects to a single common base class.  This is disabled by default but can be turned on setting bAllowMultipleTopLevelObjects to true in FDetailsViewArgs when creating a details panel.
	*  Each top level object in a details panel will get their own customization instance.  This made it necessary to deprecate a IDetailsView::GetBaseClass since there is no longer guaranteed to be one base class.
	*Details panels can have their own customization for each "root object header" in order to customize the look of having multiple top level objects in the details panel.

Change 3075369 on 2016/08/03 by Matt.Kuhlenschmidt

	Removed FBX scene as a top level option in asset filter menu in the content browser.

Change 3075556 on 2016/08/03 by Matt.Kuhlenschmidt

	Mac warning fix

Change 3075603 on 2016/08/03 by Nick.Darnell

	Adding two new plugins to engine, one for editor and one for runtime based testing.  Currently the only consumer of these plugins is going to be the EngineTest project.

Change 3075605 on 2016/08/03 by Nick.Darnell

	Functional Testing - Continued work on cleanup, reorganization, trying to improve the workflow for using the session browser.

Change 3076084 on 2016/08/03 by Jamie.Dale

	Added basic support for localizing plugins

	You can now localize plugins! There's no localization dashboard integration for this so it has to be done manually.

	You need to define the localization targets your plugin uses in its .uplugin file, eg)

		"LocalizationTargets": [
			{
				"Name": "Paper2D",
				"LoadingPolicy": "Always"
			}
		]

	"Name" should match a localization config under the Config/Localization folder for your plugin. These configs are set-up the same as any other localization config.

	"LoadingPolicy" may be one of Never, Always, Editor, Game, PropertyNames, or ToolTips. This allows you to control under what conditions your localizations should be loaded (eg, if your plugin has both game and editor data, you can separate the editor data off into its own localization target that's only loaded by the editor).

	UAT has been updated to support gathering from plugins. You can use the "IncludePlugins" flag to have it gather all plugins, or you can specify a whitelist of plugins to gather as an argument to "IncludePlugins", or alternatively, may blacklist certain plugins via "ExcludePlugins". It can now also support out-of-source gathering via the "UEProjectRoot" argument (previously it assumed that everything would be under the UE4 install/checkout directory).

	UAT has been updated to support staging plugin LocRes files. It will stage any plugin targets that are enabled for a game/client build, and are also from a plugin that's enabled for your project.

	#jira UE-4217

Change 3076123 on 2016/08/03 by Stephan.Jiang

	Extend "Select all input nodes" function to general blueprint editor

Change 3077103 on 2016/08/04 by Jamie.Dale

	Added support for underlined text rendering (including with drop-shadows)

	FTextBlockStyle can now specify a brush to use to draw an underline for text (a suitable default would be "DefaultTextUnderline" from FCoreStyle). When a brush is specified here, we inject FSlateTextUnderlineLineHighlighter highlights into the text layout to draw the underline under the relevant pieces of text, using the correct color, position, and thickness.

	FSlateFontCache::GetUnderlineMetrics and FSlateFontRenderer::GetUnderlineMetrics have been added to handle getting the underline metrics (which are slightly different to the baseline).

	This change also adds FTextLayout::RemoveRunRenderer and FTextLayout::RemoveLineHighlight to fix some bad assumptions that FSlateEditableTextLayout and FTextBlockLayout were making about ownership of run renderers and line highlighters that could cause them to remove instances they didn't own (such as the new underline highlighter) when updating things like the cursor position or highlight.

Change 3077842 on 2016/08/04 by Jamie.Dale

	Fixed fallout from API changes

Change 3077999 on 2016/08/04 by Jamie.Dale

	Ensured that BULKDATA_SingleUse is only set by UFontBulkData::Serialize when loading

	This prevents it being incorrectly set by other operations, such as counting memory used by font data.

	#jira UE-34252

Change 3078000 on 2016/08/04 by Trung.Le

	Categories VREditor-specific UMG widget assets as "VR Editor"
	#jira UE-34134

Change 3078056 on 2016/08/04 by Nick.Darnell

	Build - Fixing a mac compiler warning, reodering constructor initializers.

Change 3078813 on 2016/08/05 by Nick.Darnell

	Reorganizing editor tests, establishing plugins in the EditorTest project that will house the tests.

Change 3078818 on 2016/08/05 by Nick.Darnell

	Additional rename and cleanup associated with test moving.

Change 3078819 on 2016/08/05 by Nick.Darnell

	Removing the Oculus performance automation test, not running, and was unclaimed.

Change 3078842 on 2016/08/05 by Nick.Darnell

	Continued reorganizing tests.

Change 3078897 on 2016/08/05 by Nick.Darnell

	Additional changes to get some moved tests compiling

Change 3079157 on 2016/08/05 by Nick.Darnell

	Making it possible to browse provider names thorugh the source control module interface.

Change 3079176 on 2016/08/05 by Stephan.Jiang

	Add shortcut Ctrl+Shift+Space to rotate through different viewport options

	#jira UE-34140

Change 3079208 on 2016/08/05 by Stephan.Jiang

	Fix new animation name check in UMG

Change 3079278 on 2016/08/05 by Nick.Darnell

	Fixing the build

Change 3080555 on 2016/08/08 by Matt.Kuhlenschmidt

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 3081155 on 2016/08/08 by Nick.Darnell

	Fixing some issues with the editor tests / runtime tests under certain build configs.

Change 3081243 on 2016/08/08 by Stephan.Jiang

	Add gesture in LevelViewport to switch between Top/Bottom...etc.

Change 3082226 on 2016/08/09 by Matt.Kuhlenschmidt

	Work around animations not playing in paragon due to bsp rebuilds (UE-34391)

Change 3082254 on 2016/08/09 by Stephan.Jiang

	DragTool_ViewportChange init changes

[CL 3082411 by Matt Kuhlenschmidt in Main branch]
2016-08-09 11:28:56 -04:00

2443 lines
78 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "PersonaPrivatePCH.h"
#include "SAnimationEditorViewport.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
#include "SAnimViewportToolBar.h"
#include "AnimViewportShowCommands.h"
#include "AnimGraphDefinitions.h"
#include "AnimPreviewInstance.h"
#include "AnimationEditorViewportClient.h"
#include "AnimGraphNode_SkeletalControlBase.h"
#include "Runtime/Engine/Classes/Components/ReflectionCaptureComponent.h"
#include "Runtime/Engine/Classes/Components/SphereReflectionCaptureComponent.h"
#include "UnrealWidget.h"
#include "MouseDeltaTracker.h"
#include "ScopedTransaction.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/ExponentialHeightFogComponent.h"
#include "CanvasTypes.h"
#include "Engine/TextureCube.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Engine/CollisionProfile.h"
#include "Engine/SkeletalMeshSocket.h"
#include "EngineUtils.h"
#include "GameFramework/WorldSettings.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "Components/WindDirectionalSourceComponent.h"
#include "Engine/StaticMesh.h"
#include "SAnimationEditorViewport.h"
#include "AssetViewerSettings.h"
namespace {
// Value from UE3
static const float AnimationEditorViewport_RotateSpeed = 0.02f;
// Value from UE3
static const float AnimationEditorViewport_TranslateSpeed = 0.25f;
// follow camera feature
static const float FollowCamera_InterpSpeed = 4.f;
static const float FollowCamera_InterpSpeed_Z = 1.f;
// @todo double define - fix it
const float FOVMin = 5.f;
const float FOVMax = 170.f;
}
/**
* A hit proxy class for sockets in the Persona viewport.
*/
struct HPersonaSocketProxy : public HHitProxy
{
DECLARE_HIT_PROXY();
FSelectedSocketInfo SocketInfo;
explicit HPersonaSocketProxy( FSelectedSocketInfo InSocketInfo )
: SocketInfo( InSocketInfo )
{}
};
IMPLEMENT_HIT_PROXY( HPersonaSocketProxy, HHitProxy );
/**
* A hit proxy class for sockets in the Persona viewport.
*/
struct HPersonaBoneProxy : public HHitProxy
{
DECLARE_HIT_PROXY();
FName BoneName;
explicit HPersonaBoneProxy(const FName& InBoneName)
: BoneName( InBoneName )
{}
};
IMPLEMENT_HIT_PROXY( HPersonaBoneProxy, HHitProxy );
#define LOCTEXT_NAMESPACE "FAnimationViewportClient"
/////////////////////////////////////////////////////////////////////////
// FAnimationViewportClient
FAnimationViewportClient::FAnimationViewportClient(FAdvancedPreviewScene& InPreviewScene, TWeakPtr<FPersona> InPersonaPtr, const TSharedRef<SAnimationEditorViewport>& InAnimationEditorViewport)
: FEditorViewportClient(nullptr, &InPreviewScene, StaticCastSharedRef<SEditorViewport>(InAnimationEditorViewport))
, PersonaPtr( InPersonaPtr )
, bManipulating(false)
, bInTransaction(false)
, GravityScaleSliderValue(0.25f)
, PrevWindStrength(0.0f)
, SelectedWindActor(NULL)
, bFocusOnDraw(false)
, BodyTraceDistance(100000.0f)
, bShouldUpdateDefaultValues(false)
{
// load config
ConfigOption = UPersonaOptions::StaticClass()->GetDefaultObject<UPersonaOptions>();
check (ConfigOption);
// DrawHelper set up
DrawHelper.PerspectiveGridSize = HALF_WORLD_MAX1;
DrawHelper.AxesLineThickness = ConfigOption->bHighlightOrigin ? 1.0f : 0.0f;
DrawHelper.bDrawGrid = ConfigOption->bShowGrid;
LocalAxesMode = static_cast<ELocalAxesMode::Type>(ConfigOption->DefaultLocalAxesSelection);
BoneDrawMode = static_cast<EBoneDrawMode::Type>(ConfigOption->DefaultBoneDrawSelection);
WidgetMode = FWidget::WM_Rotate;
SetSelectedBackgroundColor(ConfigOption->ViewportBackgroundColor, false);
EngineShowFlags.Game = 0;
EngineShowFlags.ScreenSpaceReflections = 1;
EngineShowFlags.AmbientOcclusion = 1;
EngineShowFlags.SetSnap(0);
SetRealtime(true);
if(GEditor->PlayWorld)
{
SetRealtime(false,true); // We are PIE, don't start in realtime mode
}
ViewFOV = FMath::Clamp<float>(ConfigOption->ViewFOV, FOVMin, FOVMax);
EngineShowFlags.SetSeparateTranslucency(true);
EngineShowFlags.SetCompositeEditorPrimitives(true);
// set camera mode
bCameraFollow = false;
bDrawUVs = false;
UVChannelToDraw = 0;
bAutoAlignFloor = ConfigOption->bAutoAlignFloorToMesh;
// Set audio mute option
UWorld* World = PreviewScene->GetWorld();
if(World)
{
World->bAllowAudioPlayback = !ConfigOption->bMuteAudio;
}
// Grab a wind actor if it exists (could have been placed in the scene before a mode transition)
TActorIterator<AWindDirectionalSource> WindIter(World);
if(WindIter)
{
AWindDirectionalSource* WindActor = *WindIter;
WindSourceActor = WindActor;
PrevWindLocation = WindActor->GetActorLocation();
PrevWindRotation = WindActor->GetActorRotation();
PrevWindStrength = WindActor->GetComponent()->Strength;
}
else
{
//wind actor's initial position, rotation and strength
PrevWindLocation = FVector(100, 100, 100);
PrevWindRotation = FRotator(0, 0, 0); // roll, yaw, pitch
PrevWindStrength = 0.2f;
}
}
FAnimationViewportClient::~FAnimationViewportClient()
{
}
FLinearColor FAnimationViewportClient::GetBackgroundColor() const
{
return SelectedHSVColor.HSVToLinearRGB();
}
void FAnimationViewportClient::SetSelectedBackgroundColor(const FLinearColor& RGBColor, bool bSave/*=true*/)
{
SelectedHSVColor = RGBColor.LinearRGBToHSV();
// save to config
if (bSave)
{
ConfigOption->SetViewportBackgroundColor(RGBColor);
}
// only consider V from HSV
// just inversing works against for mid range brightness
// V being from 0-0.3 (almost white), 0.31-1 (almost black)
if (SelectedHSVColor.B < 0.3f)
{
DrawHelper.GridColorAxis = FColor(230,230,230);
DrawHelper.GridColorMajor = FColor(180,180,180);
DrawHelper.GridColorMinor = FColor(128,128,128);
}
else
{
DrawHelper.GridColorAxis = FColor(20,20,20);
DrawHelper.GridColorMajor = FColor(60,60,60);
DrawHelper.GridColorMinor = FColor(128,128,128);
}
Invalidate();
}
void FAnimationViewportClient::SetBackgroundColor( FLinearColor InColor )
{
SetSelectedBackgroundColor(InColor);
}
float FAnimationViewportClient::GetBrightnessValue() const
{
return SelectedHSVColor.B;
}
void FAnimationViewportClient::SetBrightnessValue( float Value )
{
SelectedHSVColor.B = Value;
SetSelectedBackgroundColor(SelectedHSVColor.HSVToLinearRGB());
}
void FAnimationViewportClient::OnToggleShowGrid()
{
FEditorViewportClient::SetShowGrid();
ConfigOption->SetShowGrid(DrawHelper.bDrawGrid);
}
bool FAnimationViewportClient::IsShowingGrid() const
{
return FEditorViewportClient::IsSetShowGridChecked();
}
void FAnimationViewportClient::OnToggleMuteAudio()
{
UWorld* World = PreviewScene->GetWorld();
if(World)
{
bool bNewAllowAudioPlayback = !World->AllowAudioPlayback();
World->bAllowAudioPlayback = bNewAllowAudioPlayback;
ConfigOption->SetMuteAudio(!bNewAllowAudioPlayback);
}
}
bool FAnimationViewportClient::IsAudioMuted() const
{
UWorld* World = PreviewScene->GetWorld();
return (World) ? !World->AllowAudioPlayback() : false;
}
void FAnimationViewportClient::SetCameraFollow()
{
bCameraFollow = !bCameraFollow;
if( bCameraFollow )
{
EnableCameraLock(false);
if (PreviewSkelMeshComp.IsValid())
{
FBoxSphereBounds Bound = PreviewSkelMeshComp.Get()->CalcBounds(FTransform::Identity);
SetViewLocationForOrbiting(Bound.Origin);
}
}
else
{
FocusViewportOnPreviewMesh();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetCameraFollowChecked() const
{
return bCameraFollow;
}
void FAnimationViewportClient::SetPreviewMeshComponent(UDebugSkelMeshComponent* InPreviewSkelMeshComp)
{
PreviewSkelMeshComp = InPreviewSkelMeshComp;
PreviewSkelMeshComp->BonesOfInterest.Empty();
UpdateCameraSetup();
// Setup physics data from physics assets if available, clearing any physics setup on the component
UPhysicsAsset* PhysAsset = PreviewSkelMeshComp->GetPhysicsAsset();
if(PhysAsset)
{
PhysAsset->InvalidateAllPhysicsMeshes();
PreviewSkelMeshComp->TermArticulated();
PreviewSkelMeshComp->InitArticulated(GetWorld()->GetPhysicsScene());
// Set to block all to enable tracing.
PreviewSkelMeshComp->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
}
}
void FAnimationViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
FEditorViewportClient::Draw(View, PDI);
if (PreviewSkelMeshComp.IsValid() && PreviewSkelMeshComp->SkeletalMesh)
{
// Can't have both bones of interest and sockets of interest set
check( !(PreviewSkelMeshComp->BonesOfInterest.Num() && PreviewSkelMeshComp->SocketsOfInterest.Num() ) )
// if we have BonesOfInterest, draw sub set of the bones only
if ( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
DrawMeshSubsetBones(PreviewSkelMeshComp.Get(), PreviewSkelMeshComp->BonesOfInterest, PDI);
}
// otherwise, if we display bones, display
if ( BoneDrawMode != EBoneDrawMode::None )
{
DrawMeshBones(PreviewSkelMeshComp.Get(), PDI);
}
if ( PreviewSkelMeshComp->bDisplayRawAnimation )
{
DrawMeshBonesUncompressedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if ( PreviewSkelMeshComp->NonRetargetedSpaceBases.Num() > 0 )
{
DrawMeshBonesNonRetargetedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if( PreviewSkelMeshComp->bDisplayAdditiveBasePose )
{
DrawMeshBonesAdditiveBasePose(PreviewSkelMeshComp.Get(), PDI);
}
if(PreviewSkelMeshComp->bDisplayBakedAnimation)
{
DrawMeshBonesBakedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if(PreviewSkelMeshComp->bDisplaySourceAnimation)
{
DrawMeshBonesSourceRawAnimation(PreviewSkelMeshComp.Get(), PDI);
}
DrawWatchedPoses(PreviewSkelMeshComp.Get(), PDI);
// Display normal vectors of each simulation vertex
if ( PreviewSkelMeshComp->bDisplayClothingNormals )
{
PreviewSkelMeshComp->DrawClothingNormals(PDI);
}
// Display tangent spaces of each graphical vertex
if ( PreviewSkelMeshComp->bDisplayClothingTangents )
{
PreviewSkelMeshComp->DrawClothingTangents(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothingCollisionVolumes )
{
PreviewSkelMeshComp->DrawClothingCollisionVolumes(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothPhysicalMeshWire )
{
PreviewSkelMeshComp->DrawClothingPhysicalMeshWire(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothMaxDistances )
{
PreviewSkelMeshComp->DrawClothingMaxDistances(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothBackstops )
{
PreviewSkelMeshComp->DrawClothingBackstops(PDI);
}
if( PreviewSkelMeshComp->bDisplayClothFixedVertices )
{
PreviewSkelMeshComp->DrawClothingFixedVertices(PDI);
}
// Display socket hit points
if ( PreviewSkelMeshComp->bDrawSockets )
{
if ( PreviewSkelMeshComp->bSkeletonSocketsVisible && PreviewSkelMeshComp->SkeletalMesh->Skeleton )
{
DrawSockets( PreviewSkelMeshComp.Get()->SkeletalMesh->Skeleton->Sockets, PDI, true );
}
if ( PreviewSkelMeshComp->bMeshSocketsVisible )
{
DrawSockets( PreviewSkelMeshComp.Get()->SkeletalMesh->GetMeshOnlySocketList(), PDI, false );
}
}
// If we have a socket of interest, draw the widget
if ( PreviewSkelMeshComp->SocketsOfInterest.Num() == 1 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
bool bSocketIsOnSkeleton = PreviewSkelMeshComp->SocketsOfInterest[0].bSocketIsOnSkeleton;
TArray<USkeletalMeshSocket*> SocketAsArray;
SocketAsArray.Add( Socket );
DrawSockets( SocketAsArray, PDI, false );
}
}
if ( PersonaPtr.IsValid() && PreviewSkelMeshComp.IsValid() )
{
// Allow selected nodes to draw debug rendering if they support it
const FGraphPanelSelectionSet SelectedNodes = PersonaPtr.Pin()->GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UAnimGraphNode_SkeletalControlBase* Node = Cast<UAnimGraphNode_SkeletalControlBase>(*NodeIt);
if (Node)
{
Node->Draw(PDI, PreviewSkelMeshComp.Get());
}
}
if(bFocusOnDraw)
{
bFocusOnDraw = false;
FocusViewportOnPreviewMesh();
}
}
}
void FAnimationViewportClient::DrawCanvas( FViewport& InViewport, FSceneView& View, FCanvas& Canvas )
{
if (PreviewSkelMeshComp.IsValid())
{
// Display bone names
if (PreviewSkelMeshComp->bShowBoneNames)
{
ShowBoneNames(&Canvas, &View);
}
// Allow nodes to draw with the canvas, and collect on screen strings to draw later
TArray<FText> NodeDebugLines;
if(PersonaPtr.IsValid())
{
// Allow selected nodes to draw debug rendering if they support it
const FGraphPanelSelectionSet SelectedNodes = PersonaPtr.Pin()->GetSelectedNodes();
for(FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UAnimGraphNode_SkeletalControlBase* Node = Cast<UAnimGraphNode_SkeletalControlBase>(*NodeIt);
if(Node)
{
Node->DrawCanvas(InViewport, View, Canvas, PreviewSkelMeshComp.Get());
Node->GetOnScreenDebugInfo(NodeDebugLines, PreviewSkelMeshComp.Get());
}
}
}
// Display info
if (IsShowingMeshStats())
{
DisplayInfo(&Canvas, &View, IsDetailedMeshStats());
}
else if(IsShowingSelectedNodeStats())
{
// Draw Node info instead of mesh info if we have entries
DrawNodeDebugLines(NodeDebugLines, &Canvas, &View);
}
// Draw name of selected bone
if (PreviewSkelMeshComp->BonesOfInterest.Num() == 1)
{
const FIntPoint ViewPortSize = Viewport->GetSizeXY();
const int32 HalfX = ViewPortSize.X/2;
const int32 HalfY = ViewPortSize.Y/2;
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
FMatrix BoneMatrix = PreviewSkelMeshComp->GetBoneMatrix(BoneIndex);
const FPlane proj = View.Project(BoneMatrix.GetOrigin());
if (proj.W > 0.f)
{
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
FQuat BoneQuat = PreviewSkelMeshComp->GetBoneQuaternion(BoneName);
FVector Loc = PreviewSkelMeshComp->GetBoneLocation(BoneName);
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( BoneName.ToString() ), GEngine->GetSmallFont(), FLinearColor::White );
Canvas.DrawItem( TextItem );
}
}
// Draw name of selected socket
if (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1)
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
const FVector SocketPos = SocketMatrix.GetOrigin();
const FPlane proj = View.Project( SocketPos );
if(proj.W > 0.f)
{
const FIntPoint ViewPortSize = Viewport->GetSizeXY();
const int32 HalfX = ViewPortSize.X/2;
const int32 HalfY = ViewPortSize.Y/2;
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( Socket->SocketName.ToString() ), GEngine->GetSmallFont(), FLinearColor::White );
Canvas.DrawItem( TextItem );
}
}
if (bDrawUVs)
{
DrawUVsForMesh(Viewport, &Canvas, 1.0f);
}
}
}
void FAnimationViewportClient::DrawUVsForMesh(FViewport* InViewport, FCanvas* InCanvas, int32 InTextYPos)
{
//use the overridden LOD level
const uint32 LODLevel = FMath::Clamp(PreviewSkelMeshComp->ForcedLodModel - 1, 0, PreviewSkelMeshComp->SkeletalMesh->LODInfo.Num() - 1);
TArray<FVector2D> SelectedEdgeTexCoords; //No functionality in Persona for this (yet?)
DrawUVs(InViewport, InCanvas, InTextYPos, LODLevel, UVChannelToDraw, SelectedEdgeTexCoords, NULL, &PreviewSkelMeshComp->GetSkeletalMeshResource()->LODModels[LODLevel] );
}
FAnimNode_SkeletalControlBase* FAnimationViewportClient::FindSkeletalControlAnimNode(TWeakObjectPtr<UAnimGraphNode_SkeletalControlBase> AnimGraphNode) const
{
FAnimNode_SkeletalControlBase* AnimNode = NULL;
if (PreviewSkelMeshComp.IsValid() && PreviewSkelMeshComp->GetAnimInstance() && AnimGraphNode.IsValid())
{
AnimNode = AnimGraphNode.Get()->FindDebugAnimNode(PreviewSkelMeshComp.Get());
}
return AnimNode;
}
void FAnimationViewportClient::FindSelectedAnimGraphNode()
{
bool bSelected = false;
// finding a selected node
if (PersonaPtr.IsValid() && PreviewSkelMeshComp.IsValid())
{
USkeletalMeshComponent* PreviewComponent = PreviewSkelMeshComp.Get();
check(PreviewComponent);
const FGraphPanelSelectionSet SelectedNodes = PersonaPtr.Pin()->GetSelectedNodes();
// don't support multi-selection
if (SelectedNodes.Num() == 1)
{
FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes);
// first element
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(*NodeIt);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
bSelected = true;
// when selected first after AnimGraph is opened, assign previous data to current node
if (SelectedSkelControlAnimGraph != AnimGraphNode)
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewComponent);
}
// make same values to ensure data consistency
AnimGraphNode->CopyNodeDataTo(AnimNode);
WidgetMode = (FWidget::EWidgetMode)AnimGraphNode->GetWidgetMode(PreviewComponent);
ECoordSystem DesiredCoordSystem = (ECoordSystem)AnimGraphNode->GetWidgetCoordinateSystem(PreviewComponent);
SetWidgetCoordSystemSpace(DesiredCoordSystem);
}
else
{
if (bShouldUpdateDefaultValues)
{
// copy updated values into internal node to ensure data consistency
AnimGraphNode->CopyNodeDataFrom(AnimNode);
bShouldUpdateDefaultValues = false;
}
}
AnimGraphNode->MoveSelectActorLocation(PreviewComponent, AnimNode);
SelectedSkelControlAnimGraph = AnimGraphNode;
}
}
}
if (!bSelected)
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewComponent);
}
SelectedSkelControlAnimGraph.Reset();
}
}
}
void FAnimationViewportClient::ClearSelectedAnimGraphNode()
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewSkelMeshComp.Get());
}
}
void FAnimationViewportClient::PostUndo()
{
// undo for skeletal controllers
// search all UAnimGraphNode_SkeletalControlBase nodes and apply data
UEdGraph* FocusedGraph = PersonaPtr.Pin()->GetFocusedGraph();
// @fixme : fix this to better way than looping all nodes
if (FocusedGraph)
{
// find UAnimGraphNode_SkeletalControlBase
for (UEdGraphNode* Node : FocusedGraph->Nodes)
{
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(Node);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
// copy undo data
AnimGraphNode->CopyNodeDataTo(AnimNode);
}
}
}
}
}
void FAnimationViewportClient::PostCompile()
{
// reset the selected anim graph to copy
SelectedSkelControlAnimGraph.Reset();
// if the user manipulated Pin values directly from the node, then should copy updated values to the internal node to retain data consistency
UEdGraph* FocusedGraph = PersonaPtr.Pin()->GetFocusedGraph();
// @fixme : fix this to better way than looping all nodes
if (FocusedGraph)
{
// find UAnimGraphNode_SkeletalControlBase
for (UEdGraphNode* Node : FocusedGraph->Nodes)
{
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(Node);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
AnimGraphNode->CopyNodeDataFrom(AnimNode);
}
}
}
}
}
void FAnimationViewportClient::Tick(float DeltaSeconds)
{
FEditorViewportClient::Tick(DeltaSeconds);
// @todo fixme
if (bCameraFollow && PreviewSkelMeshComp.IsValid())
{
// if camera isn't lock, make the mesh bounds to be center
FSphere BoundSphere = GetCameraTarget();
// need to interpolate from ViewLocation to Origin
SetCameraTargetLocation(BoundSphere, DeltaSeconds);
}
if (!GIntraFrameDebuggingGameThread)
{
PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaSeconds);
}
UDebugSkelMeshComponent* PreviewComp = PreviewSkelMeshComp.Get();
if (PreviewComp)
{
// Handle updating the preview component to represent the effects of root motion
const UStaticMeshComponent* FloorMeshComponent = GetAdvancedPreviewScene()->GetFloorMeshComponent();
FBoxSphereBounds Bounds = FloorMeshComponent->CalcBounds(FloorMeshComponent->GetRelativeTransform());
PreviewComp->ConsumeRootMotion(Bounds.GetBox().Min, Bounds.GetBox().Max);
}
FindSelectedAnimGraphNode();
}
void FAnimationViewportClient::SetCameraTargetLocation(const FSphere &BoundSphere, float DeltaSeconds)
{
FVector OldViewLoc = GetViewLocation();
FMatrix EpicMat = FTranslationMatrix(-GetViewLocation());
EpicMat = EpicMat * FInverseRotationMatrix(GetViewRotation());
FMatrix CamRotMat = EpicMat.InverseFast();
FVector CamDir = FVector(CamRotMat.M[0][0],CamRotMat.M[0][1],CamRotMat.M[0][2]);
FVector NewViewLocation = BoundSphere.Center - BoundSphere.W * 2 * CamDir;
NewViewLocation.X = FMath::FInterpTo(OldViewLoc.X, NewViewLocation.X, DeltaSeconds, FollowCamera_InterpSpeed);
NewViewLocation.Y = FMath::FInterpTo(OldViewLoc.Y, NewViewLocation.Y, DeltaSeconds, FollowCamera_InterpSpeed);
NewViewLocation.Z = FMath::FInterpTo(OldViewLoc.Z, NewViewLocation.Z, DeltaSeconds, FollowCamera_InterpSpeed_Z);
SetViewLocation( NewViewLocation );
}
void FAnimationViewportClient::ShowBoneNames( FCanvas* Canvas, FSceneView* View )
{
if (!PreviewSkelMeshComp.IsValid() || !PreviewSkelMeshComp->MeshObject)
{
return;
}
//Most of the code taken from FASVViewportClient::Draw() in AnimSetViewerMain.cpp
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num()-1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[ LODIndex ];
const int32 HalfX = Viewport->GetSizeXY().X/2;
const int32 HalfY = Viewport->GetSizeXY().Y/2;
for (int32 i=0; i< LODModel.RequiredBones.Num(); i++)
{
const int32 BoneIndex = LODModel.RequiredBones[i];
// If previewing a specific section, only show the bone names that belong to it
if ((PreviewSkelMeshComp->SectionIndexPreview >= 0) && !LODModel.Sections[PreviewSkelMeshComp->SectionIndexPreview].BoneMap.Contains(BoneIndex))
{
continue;
}
const FColor BoneColor = FColor::White;
if (BoneColor.A != 0)
{
const FVector BonePos = PreviewSkelMeshComp->ComponentToWorld.TransformPosition(PreviewSkelMeshComp->GetComponentSpaceTransforms()[BoneIndex].GetLocation());
const FPlane proj = View->Project(BonePos);
if (proj.W > 0.f)
{
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
const FString BoneString = FString::Printf( TEXT("%d: %s"), BoneIndex, *BoneName.ToString() );
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( BoneString ), GEngine->GetSmallFont(), BoneColor );
TextItem.EnableShadow(FLinearColor::Black);
Canvas->DrawItem( TextItem );
}
}
}
}
void FAnimationViewportClient::DisplayInfo(FCanvas* Canvas, FSceneView* View, bool bDisplayAllInfo)
{
int32 CurXOffset = 5;
int32 CurYOffset = 60;
int32 XL, YL;
StringSize( GEngine->GetSmallFont(), XL, YL, TEXT("L") );
FString InfoString;
const UAssetViewerSettings* Settings = UAssetViewerSettings::Get();
const UEditorPerProjectUserSettings* PerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
const int32 ProfileIndex = Settings->Profiles.IsValidIndex(PerProjectUserSettings->AssetViewerProfileIndex) ? PerProjectUserSettings->AssetViewerProfileIndex : 0;
// it is weird, but unless it's completely black, it's too bright, so just making it white if only black
const FLinearColor TextColor = ((SelectedHSVColor.B < 0.3f) || (Settings->Profiles[ProfileIndex].bShowEnvironment)) ? FLinearColor::White : FLinearColor::Black;
const FColor HeadlineColour(255, 83, 0);
const FColor SubHeadlineColour(202, 66, 0);
// if not valid skeletalmesh
if (!PreviewSkelMeshComp.IsValid() || !PreviewSkelMeshComp->SkeletalMesh)
{
return;
}
TSharedPtr<FPersona> SharedPersona = PersonaPtr.Pin();
if (SharedPersona.IsValid() && SharedPersona->ShouldDisplayAdditiveScaleErrorMessage())
{
InfoString = TEXT("Additve ref pose contains scales of 0.0, this can cause additive animations to not give the desired results");
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour);
CurYOffset += YL + 2;
}
if (PreviewSkelMeshComp->SkeletalMesh->MorphTargets.Num() > 0)
{
int32 SubHeadingIndent = CurXOffset + 10;
TArray<UMaterial*> ProcessedMaterials;
TArray<UMaterial*> MaterialsThatNeedMorphFlagOn;
TArray<UMaterial*> MaterialsThatNeedSaving;
for (int i = 0; i < PreviewSkelMeshComp->GetNumMaterials(); ++i)
{
if (UMaterialInterface* MaterialInterface = PreviewSkelMeshComp->GetMaterial(i))
{
UMaterial* Material = MaterialInterface->GetMaterial();
if ((Material != nullptr) && !ProcessedMaterials.Contains(Material))
{
ProcessedMaterials.Add(Material);
if (!Material->GetUsageByFlag(MATUSAGE_MorphTargets))
{
MaterialsThatNeedMorphFlagOn.Add(Material);
}
else if (Material->IsUsageFlagDirty(MATUSAGE_MorphTargets))
{
MaterialsThatNeedSaving.Add(Material);
}
}
}
}
if (MaterialsThatNeedMorphFlagOn.Num() > 0)
{
InfoString = FString::Printf( *LOCTEXT("MorphSupportNeeded", "The following materials need morph support ('Used with Morph Targets' in material editor):").ToString() );
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), HeadlineColour );
CurYOffset += YL + 2;
for(auto Iter = MaterialsThatNeedMorphFlagOn.CreateIterator(); Iter; ++Iter)
{
UMaterial* Material = (*Iter);
InfoString = FString::Printf(TEXT("%s"), *Material->GetPathName());
Canvas->DrawShadowedString( SubHeadingIndent, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour );
CurYOffset += YL + 2;
}
CurYOffset += 2;
}
if (MaterialsThatNeedSaving.Num() > 0)
{
InfoString = FString::Printf( *LOCTEXT("MaterialsNeedSaving", "The following materials need saving to fully support morph targets:").ToString() );
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), HeadlineColour );
CurYOffset += YL + 2;
for(auto Iter = MaterialsThatNeedSaving.CreateIterator(); Iter; ++Iter)
{
UMaterial* Material = (*Iter);
InfoString = FString::Printf(TEXT("%s"), *Material->GetPathName());
Canvas->DrawShadowedString( SubHeadingIndent, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour );
CurYOffset += YL + 2;
}
CurYOffset += 2;
}
}
UAnimPreviewInstance* PreviewInstance = PreviewSkelMeshComp->PreviewInstance;
if( PreviewInstance )
{
// see if you have anim sequence that has transform curves
UAnimSequence* Sequence = Cast<UAnimSequence>(PreviewInstance->GetCurrentAsset());
if (Sequence)
{
if (Sequence->DoesNeedRebake())
{
InfoString = TEXT("Animation is being edited. To apply to raw animation data, click \"Apply\"");
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour);
CurYOffset += YL + 2;
}
if (Sequence->DoesNeedRecompress())
{
InfoString = TEXT("Animation is being edited. To apply to compressed data (and recalculate baked additives), click \"Apply\"");
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour);
CurYOffset += YL + 2;
}
}
}
if (PreviewSkelMeshComp->IsUsingInGameBounds())
{
if (!PreviewSkelMeshComp->CheckIfBoundsAreCorrrect())
{
if( PreviewSkelMeshComp->GetPhysicsAsset() == NULL )
{
InfoString = FString::Printf( *LOCTEXT("NeedToSetupPhysicsAssetForAccurateBounds", "You may need to setup Physics Asset to use more accurate bounds").ToString() );
}
else
{
InfoString = FString::Printf( *LOCTEXT("NeedToSetupBoundsInPhysicsAsset", "You need to setup bounds in Physics Asset to include whole mesh").ToString() );
}
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor );
CurYOffset += YL + 2;
}
}
if (PreviewSkelMeshComp != NULL && PreviewSkelMeshComp->MeshObject != NULL)
{
if (bDisplayAllInfo)
{
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
// Draw stats about the mesh
const FBoxSphereBounds& SkelBounds = PreviewSkelMeshComp->Bounds;
const FPlane ScreenPosition = View->Project(SkelBounds.Origin);
const int32 HalfX = Viewport->GetSizeXY().X / 2;
const int32 HalfY = Viewport->GetSizeXY().Y / 2;
const float ScreenRadius = FMath::Max((float)HalfX * View->ViewMatrices.ProjMatrix.M[0][0], (float)HalfY * View->ViewMatrices.ProjMatrix.M[1][1]) * SkelBounds.SphereRadius / FMath::Max(ScreenPosition.W, 1.0f);
const float LODFactor = ScreenRadius / 320.0f;
int32 NumBonesInUse;
int32 NumBonesMappedToVerts;
int32 NumSectionsInUse;
FString WeightUsage;
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num() - 1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[LODIndex];
NumBonesInUse = LODModel.RequiredBones.Num();
NumBonesMappedToVerts = LODModel.ActiveBoneIndices.Num();
NumSectionsInUse = LODModel.Sections.Num();
// Calculate polys based on non clothing sections so we don't duplicate the counts.
uint32 NumTotalTriangles = 0;
int32 NumSections = LODModel.NumNonClothingSections();
for(int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
NumTotalTriangles += LODModel.Sections[SectionIndex].NumTriangles;
}
InfoString = FString::Printf(TEXT("LOD: %d, Bones: %d (Mapped to Vertices: %d), Polys: %d"),
LODIndex,
NumBonesInUse,
NumBonesMappedToVerts,
NumTotalTriangles);
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
InfoString = FString::Printf(TEXT("Current Screen Size: %3.2f, FOV:%3.0f"), LODFactor, ViewFOV);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += 1; // --
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
int32 SectionVerts = LODModel.Sections[SectionIndex].GetNumVertices();
InfoString = FString::Printf(TEXT(" [Section %d] Verts:%d, Bones:%d"),
SectionIndex,
SectionVerts,
LODModel.Sections[SectionIndex].BoneMap.Num()
);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor*0.8f);
}
InfoString = FString::Printf(TEXT("TOTAL Verts:%d"),
LODModel.NumVertices);
CurYOffset += 1; // --
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
InfoString = FString::Printf(TEXT("Sections:%d %s"),
NumSectionsInUse,
*WeightUsage
);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
if (PreviewSkelMeshComp->BonesOfInterest.Num() > 0)
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
FTransform ReferenceTransform = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetRefBonePose()[BoneIndex];
FTransform LocalTransform = PreviewSkelMeshComp->BoneSpaceTransforms[BoneIndex];
FTransform ComponentTransform = PreviewSkelMeshComp->GetComponentSpaceTransforms()[BoneIndex];
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Local :%s"), *LocalTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Component :%s"), *ComponentTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Reference :%s"), *ReferenceTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
}
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Approximate Size: %ix%ix%i"),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.X * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Y * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Z * 2.0f));
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
uint32 NumNotiesWithErrors = PreviewSkelMeshComp->AnimNotifyErrors.Num();
for (uint32 i = 0; i < NumNotiesWithErrors; ++i)
{
uint32 NumErrors = PreviewSkelMeshComp->AnimNotifyErrors[i].Errors.Num();
for (uint32 ErrorIdx = 0; ErrorIdx < NumErrors; ++ErrorIdx)
{
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *PreviewSkelMeshComp->AnimNotifyErrors[i].Errors[ErrorIdx], GEngine->GetSmallFont(), FLinearColor(1.0f, 0.0f, 0.0f, 1.0f));
}
}
}
else // simplified default display info to be same as static mesh editor
{
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num() - 1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[LODIndex];
// Current LOD
InfoString = FString::Printf(TEXT("LOD: %d"), LODIndex);
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// current screen size
const FBoxSphereBounds& SkelBounds = PreviewSkelMeshComp->Bounds;
const FPlane ScreenPosition = View->Project(SkelBounds.Origin);
const int32 HalfX = Viewport->GetSizeXY().X / 2;
const int32 HalfY = Viewport->GetSizeXY().Y / 2;
const float ScreenRadius = FMath::Max((float)HalfX * View->ViewMatrices.ProjMatrix.M[0][0], (float)HalfY * View->ViewMatrices.ProjMatrix.M[1][1]) * SkelBounds.SphereRadius / FMath::Max(ScreenPosition.W, 1.0f);
const float LODFactor = ScreenRadius / 320.0f;
float ScreenSize = ComputeBoundsScreenSize(ScreenPosition, SkelBounds.SphereRadius, *View);
InfoString = FString::Printf(TEXT("Current Screen Size: %3.2f"), LODFactor);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Triangles
uint32 NumTotalTriangles = 0;
int32 NumSections = LODModel.NumNonClothingSections();
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
NumTotalTriangles += LODModel.Sections[SectionIndex].NumTriangles;
}
InfoString = FString::Printf(TEXT("Triangles: %d"), NumTotalTriangles);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Vertices
InfoString = FString::Printf(TEXT("Vertices: %d"), LODModel.NumVertices);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// UV Channels
InfoString = FString::Printf(TEXT("UV Channels: %d"), LODModel.NumTexCoords);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Approx Size
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Approx Size: %ix%ix%i"),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.X * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Y * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Z * 2.0f));
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
}
}
if (PreviewSkelMeshComp->SectionIndexPreview != INDEX_NONE)
{
// Notify the user if they are isolating a mesh section.
CurYOffset += YL + 2;
InfoString = FString::Printf(*LOCTEXT("MeshSectionsHiddenWarning", "Mesh Sections Hidden").ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour);
}
}
void FAnimationViewportClient::DrawNodeDebugLines(TArray<FText>& Lines, FCanvas* Canvas, FSceneView* View)
{
if(Lines.Num() > 0)
{
int32 CurrentXOffset = 5;
int32 CurrentYOffset = 60;
int32 CharWidth;
int32 CharHeight;
StringSize(GEngine->GetSmallFont(), CharWidth, CharHeight, TEXT("0"));
const int32 LineHeight = CharHeight + 2;
for(FText& Line : Lines)
{
FCanvasTextItem TextItem(FVector2D(CurrentXOffset, CurrentYOffset), Line, GEngine->GetSmallFont(), FLinearColor::White);
TextItem.EnableShadow(FLinearColor::Black);
Canvas->DrawItem(TextItem);
CurrentYOffset += LineHeight;
}
}
}
int32 FAnimationViewportClient::FindSelectedBone() const
{
int32 SelectedBone = INDEX_NONE;
if (SelectedSkelControlAnimGraph.IsValid())
{
FName BoneName = SelectedSkelControlAnimGraph->FindSelectedBone();
SelectedBone = PreviewSkelMeshComp->GetBoneIndex(BoneName);
}
if (SelectedBone == INDEX_NONE && PreviewSkelMeshComp.IsValid() && (PreviewSkelMeshComp->BonesOfInterest.Num() == 1) )
{
SelectedBone = PreviewSkelMeshComp->BonesOfInterest[0];
}
return SelectedBone;
}
USkeletalMeshSocket* FAnimationViewportClient::FindSelectedSocket() const
{
USkeletalMeshSocket* SelectedSocket = NULL;
if (PreviewSkelMeshComp.IsValid() && (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1) )
{
SelectedSocket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
}
return SelectedSocket;
}
TWeakObjectPtr<AWindDirectionalSource> FAnimationViewportClient::FindSelectedWindActor() const
{
return SelectedWindActor;
}
void FAnimationViewportClient::ProcessClick(class FSceneView& View, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY)
{
TSharedPtr<FPersona> SharedPersona = PersonaPtr.Pin();
if(!SharedPersona.IsValid())
{
// No reason to continue, cannot select elements
return;
}
if ( HitProxy )
{
if ( HitProxy->IsA( HPersonaSocketProxy::StaticGetType() ) )
{
HPersonaSocketProxy* SocketProxy = ( HPersonaSocketProxy* )HitProxy;
// Tell Persona that the socket has been selected - this will sort out the skeleton tree, etc.
SharedPersona->SetSelectedSocket( SocketProxy->SocketInfo );
}
else if ( HitProxy->IsA( HPersonaBoneProxy::StaticGetType() ) )
{
HPersonaBoneProxy* BoneProxy = ( HPersonaBoneProxy* )HitProxy;
// Tell Persona that the bone has been selected - this will sort out the skeleton tree, etc.
SharedPersona->SetSelectedBone( PreviewSkelMeshComp.Get()->SkeletalMesh->Skeleton, BoneProxy->BoneName );
}
else if ( HitProxy->IsA( HActor::StaticGetType() ) )
{
HActor* ActorHitProxy = static_cast<HActor*>(HitProxy);
AWindDirectionalSource* WindActor = Cast<AWindDirectionalSource>(ActorHitProxy->Actor);
if( WindActor )
{
//clear previously selected things
SharedPersona->DeselectAll();
SelectedWindActor = WindActor;
}
else if (SelectedSkelControlAnimGraph.IsValid() && SelectedSkelControlAnimGraph->IsActorClicked(ActorHitProxy))
{
// do some actions when hit
SelectedSkelControlAnimGraph->ProcessActorClick(ActorHitProxy);
}
}
}
else
{
// Cast for phys bodies if we didn't get any hit proxies
FHitResult Result(1.0f);
const FViewportClick Click(&View, this, EKeys::Invalid, IE_Released, Viewport->GetMouseX(), Viewport->GetMouseY());
bool bHit = PreviewSkelMeshComp.Get()->LineTraceComponent(Result, Click.GetOrigin(), Click.GetOrigin() + Click.GetDirection() * BodyTraceDistance, FCollisionQueryParams(NAME_None,true));
if(bHit)
{
SharedPersona->SetSelectedBone(PreviewSkelMeshComp->SkeletalMesh->Skeleton, Result.BoneName);
}
else
{
// We didn't hit a proxy or a physics object, so deselect all objects
SharedPersona->DeselectAll();
}
}
}
bool FAnimationViewportClient::InputWidgetDelta( FViewport* InViewport, EAxisList::Type CurrentAxis, FVector& Drag, FRotator& Rot, FVector& Scale )
{
// Get some useful info about buttons being held down
const bool bCtrlDown = InViewport->KeyState(EKeys::LeftControl) || InViewport->KeyState(EKeys::RightControl);
const bool bShiftDown = InViewport->KeyState(EKeys::LeftShift) || InViewport->KeyState(EKeys::RightShift);
const bool bMouseButtonDown = InViewport->KeyState( EKeys::LeftMouseButton ) || InViewport->KeyState( EKeys::MiddleMouseButton ) || InViewport->KeyState( EKeys::RightMouseButton );
bool bHandled = false;
if ( bManipulating && CurrentAxis != EAxisList::None )
{
bHandled = true;
int32 BoneIndex = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> WindActor = FindSelectedWindActor();
FAnimNode_ModifyBone* SkelControl = NULL;
if ( BoneIndex >= 0 )
{
//Get the skeleton control manipulating this bone
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
SkelControl = &(PreviewSkelMeshComp->PreviewInstance->ModifyBone(BoneName));
}
if ( SkelControl || SelectedSocket )
{
FTransform CurrentSkelControlTM(
SelectedSocket ? SelectedSocket->RelativeRotation : SkelControl->Rotation,
SelectedSocket ? SelectedSocket->RelativeLocation : SkelControl->Translation,
SelectedSocket ? SelectedSocket->RelativeScale : SkelControl->Scale);
FTransform BaseTM;
if ( SelectedSocket )
{
BaseTM = SelectedSocket->GetSocketTransform( PreviewSkelMeshComp.Get() );
}
else
{
BaseTM = PreviewSkelMeshComp->GetBoneTransform( BoneIndex );
}
// Remove SkelControl's orientation from BoneMatrix, as we need to translate/rotate in the non-SkelControlled space
BaseTM = BaseTM.GetRelativeTransformReverse(CurrentSkelControlTM);
const bool bDoRotation = WidgetMode == FWidget::WM_Rotate || WidgetMode == FWidget::WM_TranslateRotateZ;
const bool bDoTranslation = WidgetMode == FWidget::WM_Translate || WidgetMode == FWidget::WM_TranslateRotateZ;
const bool bDoScale = WidgetMode == FWidget::WM_Scale;
if (bDoRotation)
{
FVector RotAxis;
float RotAngle;
Rot.Quaternion().ToAxisAndAngle( RotAxis, RotAngle );
FVector4 BoneSpaceAxis = BaseTM.TransformVector( RotAxis );
//Calculate the new delta rotation
FQuat DeltaQuat( BoneSpaceAxis, RotAngle );
DeltaQuat.Normalize();
FRotator NewRotation = ( CurrentSkelControlTM * FTransform( DeltaQuat )).Rotator();
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoRotation(PreviewSkelMeshComp.Get(), Rot, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if ( SelectedSocket )
{
SelectedSocket->RelativeRotation = NewRotation;
}
else
{
SkelControl->Rotation = NewRotation;
}
}
if (bDoTranslation)
{
FVector4 BoneSpaceOffset = BaseTM.TransformVector(Drag);
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoTranslation(PreviewSkelMeshComp.Get(), Drag, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if (SelectedSocket)
{
SelectedSocket->RelativeLocation += BoneSpaceOffset;
}
else
{
SkelControl->Translation += BoneSpaceOffset;
}
}
if(bDoScale)
{
FVector4 BoneSpaceScaleOffset;
if (ModeTools->GetCoordSystem() == COORD_World)
{
BoneSpaceScaleOffset = BaseTM.TransformVector(Scale);
}
else
{
BoneSpaceScaleOffset = Scale;
}
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoScale(PreviewSkelMeshComp.Get(), Scale, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if(SelectedSocket)
{
SelectedSocket->RelativeScale += BoneSpaceScaleOffset;
}
else
{
SkelControl->Scale += BoneSpaceScaleOffset;
}
}
}
else if( WindActor.IsValid() )
{
if (WidgetMode == FWidget::WM_Rotate)
{
FTransform WindTransform = WindActor->GetTransform();
FRotator NewRotation = ( WindTransform * FTransform( Rot ) ).Rotator();
WindActor->SetActorRotation( NewRotation );
}
else
{
FVector Location = WindActor->GetActorLocation();
Location += Drag;
WindActor->SetActorLocation(Location);
}
}
InViewport->Invalidate();
}
return bHandled;
}
void FAnimationViewportClient::TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge )
{
int32 BoneIndex = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> WindActor = FindSelectedWindActor();
if( (BoneIndex >= 0 || SelectedSocket || WindActor.IsValid()) && bIsDraggingWidget )
{
bool bValidAxis = false;
FVector WorldAxisDir;
if(InInputState.IsLeftMouseButtonPressed() && (Widget->GetCurrentAxis() & EAxisList::XYZ) != 0)
{
if ( PreviewSkelMeshComp->SocketsOfInterest.Num() == 1 )
{
const bool bAltDown = InInputState.IsAltButtonPressed();
if ( bAltDown && PersonaPtr.IsValid() )
{
// Rather than moving/rotating the selected socket, copy it and move the copy instead
PersonaPtr.Pin()->DuplicateAndSelectSocket( PreviewSkelMeshComp->SocketsOfInterest[0] );
}
// Socket movement is transactional - we want undo/redo and saving of it
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
if ( Socket && bInTransaction == false )
{
if (WidgetMode == FWidget::WM_Rotate )
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_RotateSocket", "Rotate Socket" ) );
}
else
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_TranslateSocket", "Translate Socket" ) );
}
Socket->SetFlags( RF_Transactional ); // Undo doesn't work without this!
Socket->Modify();
bInTransaction = true;
}
}
else if( BoneIndex >= 0 )
{
if ( bInTransaction == false )
{
// we also allow undo/redo of bone manipulations
if (WidgetMode == FWidget::WM_Rotate )
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_RotateBone", "Rotate Bone" ) );
}
else
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_TranslateBone", "Translate Bone" ) );
}
PreviewSkelMeshComp->PreviewInstance->SetFlags( RF_Transactional ); // Undo doesn't work without this!
PreviewSkelMeshComp->PreviewInstance->Modify();
bInTransaction = true;
// now modify the bone array
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
PreviewSkelMeshComp->PreviewInstance->ModifyBone(BoneName);
}
}
}
bManipulating = true;
}
}
void FAnimationViewportClient::TrackingStopped()
{
if (bManipulating)
{
// Socket movement is transactional - we want undo/redo and saving of it
if ( bInTransaction )
{
GEditor->EndTransaction();
bInTransaction = false;
}
bManipulating = false;
}
Invalidate();
}
FWidget::EWidgetMode FAnimationViewportClient::GetWidgetMode() const
{
FWidget::EWidgetMode Mode = FWidget::WM_None;
if (!PreviewSkelMeshComp->IsAnimBlueprintInstanced())
{
if ((PreviewSkelMeshComp != NULL) && ((PreviewSkelMeshComp->BonesOfInterest.Num() == 1) || (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1)))
{
Mode = WidgetMode;
}
else if (SelectedWindActor.IsValid())
{
Mode = WidgetMode;
}
}
if (SelectedSkelControlAnimGraph.IsValid())
{
Mode = (FWidget::EWidgetMode)SelectedSkelControlAnimGraph->GetWidgetMode(PreviewSkelMeshComp.Get());
}
return Mode;
}
FVector FAnimationViewportClient::GetWidgetLocation() const
{
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph.Get());
if (AnimNode)
{
return SelectedSkelControlAnimGraph->GetWidgetLocation(PreviewSkelMeshComp.Get(), AnimNode);
}
}
else if( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest.Last();
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
FMatrix BoneMatrix = PreviewSkelMeshComp->GetBoneMatrix(BoneIndex);
return BoneMatrix.GetOrigin();
}
else if( PreviewSkelMeshComp->SocketsOfInterest.Num() > 0 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest.Last().Socket;
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
return SocketMatrix.GetOrigin();
}
else if( SelectedWindActor.IsValid() )
{
return SelectedWindActor.Get()->GetActorLocation();
}
return FVector::ZeroVector;
}
FMatrix FAnimationViewportClient::GetWidgetCoordSystem() const
{
const bool bIsLocal = GetWidgetCoordSystemSpace() == COORD_Local;
if( bIsLocal )
{
if (SelectedSkelControlAnimGraph.IsValid())
{
FName BoneName = SelectedSkelControlAnimGraph->FindSelectedBone();
int32 BoneIndex = PreviewSkelMeshComp->GetBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
FTransform BoneMatrix = PreviewSkelMeshComp->GetBoneTransform(BoneIndex);
return BoneMatrix.ToMatrixNoScale().RemoveTranslation();
}
}
else if ( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest.Last();
FTransform BoneMatrix = PreviewSkelMeshComp->GetBoneTransform(BoneIndex);
return BoneMatrix.ToMatrixNoScale().RemoveTranslation();
}
else if( PreviewSkelMeshComp->SocketsOfInterest.Num() > 0 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest.Last().Socket;
FTransform SocketMatrix = Socket->GetSocketTransform( PreviewSkelMeshComp.Get() );
return SocketMatrix.ToMatrixNoScale().RemoveTranslation();
}
else if ( SelectedWindActor.IsValid() )
{
return SelectedWindActor->GetTransform().ToMatrixNoScale().RemoveTranslation();
}
}
return FMatrix::Identity;
}
ECoordSystem FAnimationViewportClient::GetWidgetCoordSystemSpace() const
{
return ModeTools->GetCoordSystem();
}
void FAnimationViewportClient::SetWidgetCoordSystemSpace(ECoordSystem NewCoordSystem)
{
ModeTools->SetCoordSystem(NewCoordSystem);
Invalidate();
}
void FAnimationViewportClient::SetViewMode(EViewModeIndex InViewModeIndex)
{
FEditorViewportClient::SetViewMode(InViewModeIndex);
ConfigOption->SetViewModeIndex(InViewModeIndex);
}
void FAnimationViewportClient::SetViewportType(ELevelViewportType InViewportType)
{
FEditorViewportClient::SetViewportType(InViewportType);
FocusViewportOnPreviewMesh();
}
void FAnimationViewportClient::RotateViewportType()
{
FEditorViewportClient::RotateViewportType();
FocusViewportOnPreviewMesh();
}
bool FAnimationViewportClient::InputKey( FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad )
{
const int32 HitX = InViewport->GetMouseX();
const int32 HitY = InViewport->GetMouseY();
bool bHandled = false;
// Handle switching modes - only allowed when not already manipulating
if ( (Event == IE_Pressed) && (Key == EKeys::SpaceBar) && !bManipulating )
{
int32 SelectedBone = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> SelectedWind = FindSelectedWindActor();
if (SelectedBone >= 0 || SelectedSocket || SelectedWind.IsValid())
{
if (SelectedSkelControlAnimGraph.IsValid())
{
WidgetMode = (FWidget::EWidgetMode)SelectedSkelControlAnimGraph->ChangeToNextWidgetMode(PreviewSkelMeshComp.Get(), WidgetMode);
if (WidgetMode == FWidget::WM_Scale)
{
SetWidgetCoordSystemSpace(COORD_Local);
}
else
{
SetWidgetCoordSystemSpace(COORD_World);
}
}
else
if (WidgetMode == FWidget::WM_Rotate)
{
WidgetMode = FWidget::WM_Scale;
}
else if (WidgetMode == FWidget::WM_Scale)
{
WidgetMode = FWidget::WM_Translate;
}
else
{
WidgetMode = FWidget::WM_Rotate;
}
//@TODO: ANIMATION: Add scaling support
}
bHandled = true;
Invalidate();
}
if( (Event == IE_Pressed) && (Key == EKeys::F) )
{
bHandled = true;
FocusViewportOnPreviewMesh();
}
FAdvancedPreviewScene* AdvancedScene = static_cast<FAdvancedPreviewScene*>(PreviewScene);
bHandled |= AdvancedScene->HandleInputKey(InViewport, ControllerId, Key, Event, AmountDepressed, bGamepad);
// Pass keys to standard controls, if we didn't consume input
return (bHandled)
? true
: FEditorViewportClient::InputKey(InViewport, ControllerId, Key, Event, AmountDepressed, bGamepad);
}
bool FAnimationViewportClient::InputAxis(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples /*= 1*/, bool bGamepad /*= false*/)
{
bool bResult = true;
if (!bDisableInput)
{
FAdvancedPreviewScene* AdvancedScene = (FAdvancedPreviewScene*)PreviewScene;
bResult = AdvancedScene->HandleViewportInput(InViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);
if (bResult)
{
Invalidate();
}
else
{
bResult = FEditorViewportClient::InputAxis(InViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);
}
}
return bResult;
}
void FAnimationViewportClient::SetWidgetMode(FWidget::EWidgetMode InMode)
{
bool bAnimBPMode = PersonaPtr.Pin()->IsModeCurrent(FPersonaModes::AnimBlueprintEditMode);
// support WER keys to change gizmo mode for Bone Controller preivew in anim blueprint
if (bAnimBPMode)
{
if (!bManipulating)
{
int32 SelectedBone = FindSelectedBone();
if (SelectedBone >= 0 && SelectedSkelControlAnimGraph.IsValid())
{
if (SelectedSkelControlAnimGraph->SetWidgetMode(PreviewSkelMeshComp.Get(), InMode))
{
WidgetMode = InMode;
}
}
}
}
else
{
WidgetMode = InMode;
}
// Force viewport to redraw
Viewport->Invalidate();
}
bool FAnimationViewportClient::CanSetWidgetMode(FWidget::EWidgetMode NewMode) const
{
return true;
}
void FAnimationViewportClient::SetLocalAxesMode(ELocalAxesMode::Type AxesMode)
{
LocalAxesMode = AxesMode;
ConfigOption->SetDefaultLocalAxesSelection( AxesMode );
}
bool FAnimationViewportClient::IsLocalAxesModeSet( ELocalAxesMode::Type AxesMode ) const
{
return LocalAxesMode == AxesMode;
}
void FAnimationViewportClient::SetBoneDrawMode(EBoneDrawMode::Type AxesMode)
{
BoneDrawMode = AxesMode;
ConfigOption->SetDefaultBoneDrawSelection(AxesMode);
}
bool FAnimationViewportClient::IsBoneDrawModeSet(EBoneDrawMode::Type AxesMode) const
{
return BoneDrawMode == AxesMode;
}
void FAnimationViewportClient::DrawBonesFromTransforms(TArray<FTransform>& Transforms, UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI, FLinearColor BoneColour, FLinearColor RootBoneColour) const
{
if ( Transforms.Num() > 0 )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(Transforms.Num());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(Transforms.Num());
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index < MeshComponent->RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = MeshComponent->RequiredBones[Index];
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
WorldTransforms[BoneIndex] = Transforms[BoneIndex] * MeshComponent->ComponentToWorld;
BoneColours[BoneIndex] = (ParentIndex >= 0) ? BoneColour : RootBoneColour;
}
DrawBones(MeshComponent, MeshComponent->RequiredBones, WorldTransforms, PDI, BoneColours);
}
}
void FAnimationViewportClient::DrawBonesFromCompactPose(const FCompactHeapPose& Pose, UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI, const FLinearColor& DrawColour) const
{
if (Pose.GetNumBones() > 0)
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(Pose.GetBoneContainer().GetNumBones());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(Pose.GetBoneContainer().GetNumBones());
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for (FCompactPoseBoneIndex BoneIndex : Pose.ForEachBoneIndex())
{
FMeshPoseBoneIndex MeshBoneIndex = Pose.GetBoneContainer().MakeMeshPoseIndex(BoneIndex);
int32 ParentIndex = Pose.GetBoneContainer().GetParentBoneIndex(MeshBoneIndex.GetInt());
if (ParentIndex == INDEX_NONE)
{
WorldTransforms[MeshBoneIndex.GetInt()] = Pose[BoneIndex] * MeshComponent->ComponentToWorld;
}
else
{
WorldTransforms[MeshBoneIndex.GetInt()] = Pose[BoneIndex] * WorldTransforms[ParentIndex];
}
BoneColours[MeshBoneIndex.GetInt()] = DrawColour;
}
DrawBones(MeshComponent, MeshComponent->RequiredBones, WorldTransforms, PDI, BoneColours, 1.0f, true);
}
}
void FAnimationViewportClient::DrawMeshBonesUncompressedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->UncompressedSpaceBases, MeshComponent, PDI, FColor(255, 127, 39, 255), FColor(255, 127, 39, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesNonRetargetedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->NonRetargetedSpaceBases, MeshComponent, PDI, FColor(159, 159, 39, 255), FColor(159, 159, 39, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesAdditiveBasePose(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->AdditiveBasePoses, MeshComponent, PDI, FColor(0, 159, 0, 255), FColor(0, 159, 0, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesSourceRawAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if(MeshComponent && MeshComponent->SkeletalMesh)
{
DrawBonesFromTransforms(MeshComponent->SourceAnimationPoses, MeshComponent, PDI, FColor(195, 195, 195, 255), FColor(195, 159, 195, 255));
}
}
void FAnimationViewportClient::DrawWatchedPoses(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI)
{
if (UAnimInstance* AnimInstance = MeshComponent->GetAnimInstance())
{
if (UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = Cast<UAnimBlueprintGeneratedClass>(AnimInstance->GetClass()))
{
if (UAnimBlueprint* Blueprint = Cast<UAnimBlueprint>(AnimBlueprintGeneratedClass->ClassGeneratedBy))
{
if (Blueprint->GetObjectBeingDebugged() == AnimInstance)
{
for (const FAnimNodePoseWatch& AnimNodePoseWatch : AnimBlueprintGeneratedClass->GetAnimBlueprintDebugData().AnimNodePoseWatch)
{
DrawBonesFromCompactPose(*AnimNodePoseWatch.PoseInfo.Get(), MeshComponent, PDI, AnimNodePoseWatch.PoseDrawColour);
}
}
}
}
}
}
void FAnimationViewportClient::DrawMeshBonesBakedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if(MeshComponent && MeshComponent->SkeletalMesh)
{
DrawBonesFromTransforms(MeshComponent->BakedAnimationPoses, MeshComponent, PDI, FColor(0, 128, 192, 255), FColor(0, 128, 192, 255));
}
}
void FAnimationViewportClient::DrawMeshBones(USkeletalMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(MeshComponent->GetNumComponentSpaceTransforms());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(MeshComponent->GetNumComponentSpaceTransforms());
TArray<int32> SelectedBones;
if(UDebugSkelMeshComponent* DebugMeshComponent = Cast<UDebugSkelMeshComponent>(MeshComponent))
{
SelectedBones = DebugMeshComponent->BonesOfInterest;
}
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index<MeshComponent->RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = MeshComponent->RequiredBones[Index];
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
WorldTransforms[BoneIndex] = MeshComponent->GetComponentSpaceTransforms()[BoneIndex] * MeshComponent->ComponentToWorld;
if(SelectedBones.Contains(BoneIndex))
{
BoneColours[BoneIndex] = FLinearColor(1.0f, 0.34f, 0.0f, 1.0f);
}
else
{
BoneColours[BoneIndex] = (ParentIndex >= 0) ? FLinearColor::White : FLinearColor::Red;
}
}
DrawBones(MeshComponent, MeshComponent->RequiredBones, WorldTransforms, PDI, BoneColours);
}
}
void FAnimationViewportClient::DrawBones(const USkeletalMeshComponent* MeshComponent, const TArray<FBoneIndexType> & RequiredBones, const TArray<FTransform> & WorldTransforms, FPrimitiveDrawInterface* PDI, const TArray<FLinearColor>& BoneColours, float LineThickness/*=0.f*/, bool bForceDraw/*=false*/) const
{
check ( MeshComponent && MeshComponent->SkeletalMesh );
TArray<int32> SelectedBones;
if(const UDebugSkelMeshComponent* DebugMeshComponent = Cast<const UDebugSkelMeshComponent>(MeshComponent))
{
SelectedBones = DebugMeshComponent->BonesOfInterest;
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index<RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = RequiredBones[Index];
if (bForceDraw ||
(BoneDrawMode == EBoneDrawMode::All) ||
((BoneDrawMode == EBoneDrawMode::Selected) && SelectedBones.Contains(BoneIndex) )
)
{
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
FVector Start, End;
FLinearColor LineColor = BoneColours[BoneIndex];
if (ParentIndex >= 0)
{
Start = WorldTransforms[ParentIndex].GetLocation();
End = WorldTransforms[BoneIndex].GetLocation();
}
else
{
Start = FVector::ZeroVector;
End = WorldTransforms[BoneIndex].GetLocation();
}
static const float SphereRadius = 1.0f;
TArray<FVector> Verts;
//Calc cone size
FVector EndToStart = (Start - End);
float ConeLength = EndToStart.Size();
float Angle = FMath::RadiansToDegrees(FMath::Atan(SphereRadius / ConeLength));
//Render Sphere for bone end point and a cone between it and its parent.
PDI->SetHitProxy(new HPersonaBoneProxy(MeshComponent->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex)));
DrawWireSphere(PDI, End, LineColor, SphereRadius, 10, SDPG_Foreground);
DrawWireCone(PDI, Verts, FRotationMatrix::MakeFromX(EndToStart)*FTranslationMatrix(End), ConeLength, Angle, 4, LineColor, SDPG_Foreground);
PDI->SetHitProxy(NULL);
// draw gizmo
if ((LocalAxesMode == ELocalAxesMode::All) ||
((LocalAxesMode == ELocalAxesMode::Selected) && SelectedBones.Contains(BoneIndex))
)
{
RenderGizmo(WorldTransforms[BoneIndex], PDI);
}
}
}
}
}
void FAnimationViewportClient::RenderGizmo(const FTransform& Transform, FPrimitiveDrawInterface* PDI) const
{
// Display colored coordinate system axes for this joint.
const float AxisLength = 3.75f;
const float LineThickness = 0.3f;
const FVector Origin = Transform.GetLocation();
// Red = X
FVector XAxis = Transform.TransformVector( FVector(1.0f,0.0f,0.0f) );
XAxis.Normalize();
PDI->DrawLine( Origin, Origin + XAxis * AxisLength, FColor( 255, 80, 80),SDPG_Foreground, LineThickness);
// Green = Y
FVector YAxis = Transform.TransformVector( FVector(0.0f,1.0f,0.0f) );
YAxis.Normalize();
PDI->DrawLine( Origin, Origin + YAxis * AxisLength, FColor( 80, 255, 80),SDPG_Foreground, LineThickness);
// Blue = Z
FVector ZAxis = Transform.TransformVector( FVector(0.0f,0.0f,1.0f) );
ZAxis.Normalize();
PDI->DrawLine( Origin, Origin + ZAxis * AxisLength, FColor( 80, 80, 255),SDPG_Foreground, LineThickness);
}
void FAnimationViewportClient::DrawMeshSubsetBones(const USkeletalMeshComponent* MeshComponent, const TArray<int32>& BonesOfInterest, FPrimitiveDrawInterface* PDI) const
{
// this BonesOfInterest has to be in MeshComponent base, not Skeleton
if ( MeshComponent && MeshComponent->SkeletalMesh && BonesOfInterest.Num() > 0 )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(MeshComponent->GetNumComponentSpaceTransforms());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(MeshComponent->GetNumComponentSpaceTransforms());
TArray<FBoneIndexType> RequiredBones;
const FReferenceSkeleton& RefSkeleton = MeshComponent->SkeletalMesh->RefSkeleton;
static const FName SelectionColorName("SelectionColor");
const FSlateColor SelectionColor = FEditorStyle::GetSlateColor(SelectionColorName);
const FLinearColor LinearSelectionColor( SelectionColor.IsColorSpecified() ? SelectionColor.GetSpecifiedColor() : FLinearColor::White );
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( auto Iter = MeshComponent->RequiredBones.CreateConstIterator(); Iter; ++Iter)
{
const int32 BoneIndex = *Iter;
bool bDrawBone = false;
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
// need to see if it's child of any of Bones of interest
for (auto SubIter=BonesOfInterest.CreateConstIterator(); SubIter; ++SubIter )
{
const int32 SubBoneIndex = *SubIter;
// if I'm child of the BonesOfInterest
if(BoneIndex == SubBoneIndex)
{
//found a bone we are interested in
if(ParentIndex >= 0)
{
WorldTransforms[ParentIndex] = MeshComponent->GetComponentSpaceTransforms()[ParentIndex]*MeshComponent->ComponentToWorld;
}
BoneColours[BoneIndex] = LinearSelectionColor;
bDrawBone = true;
break;
}
else if ( RefSkeleton.BoneIsChildOf(BoneIndex, SubBoneIndex) )
{
BoneColours[BoneIndex] = FLinearColor::White;
bDrawBone = true;
break;
}
}
if (bDrawBone)
{
//add to the list
RequiredBones.AddUnique(BoneIndex);
WorldTransforms[BoneIndex] = MeshComponent->GetComponentSpaceTransforms()[BoneIndex] * MeshComponent->ComponentToWorld;
}
}
DrawBones(MeshComponent, RequiredBones, WorldTransforms, PDI, BoneColours, 0.3f);
}
}
void FAnimationViewportClient::DrawSockets( TArray<USkeletalMeshSocket*>& Sockets, FPrimitiveDrawInterface* PDI, bool bUseSkeletonSocketColor ) const
{
if ( const UDebugSkelMeshComponent* DebugMeshComponent = PreviewSkelMeshComp.Get() )
{
TArray<FSelectedSocketInfo> SelectedSockets;
SelectedSockets = DebugMeshComponent->SocketsOfInterest;
for ( auto SocketIt = Sockets.CreateConstIterator(); SocketIt; ++SocketIt )
{
USkeletalMeshSocket* Socket = *(SocketIt);
FReferenceSkeleton& RefSkeleton = DebugMeshComponent->SkeletalMesh->RefSkeleton;
const int32 ParentIndex = RefSkeleton.FindBoneIndex(Socket->BoneName);
FTransform WorldTransformSocket = Socket->GetSocketTransform(DebugMeshComponent);
FLinearColor SocketColor;
FVector Start, End;
if (ParentIndex >=0)
{
FTransform WorldTransformParent = DebugMeshComponent->GetComponentSpaceTransforms()[ParentIndex]*DebugMeshComponent->ComponentToWorld;
Start = WorldTransformParent.GetLocation();
End = WorldTransformSocket.GetLocation();
}
else
{
Start = FVector::ZeroVector;
End = WorldTransformSocket.GetLocation();
}
const bool bSelectedSocket = SelectedSockets.ContainsByPredicate([Socket](const FSelectedSocketInfo& Info){ return Info.Socket == Socket; });
if( bSelectedSocket )
{
SocketColor = FLinearColor(1.0f, 0.34f, 0.0f, 1.0f);
}
else
{
SocketColor = (ParentIndex >= 0) ? FLinearColor::White : FLinearColor::Red;
}
static const float SphereRadius = 1.0f;
TArray<FVector> Verts;
//Calc cone size
FVector EndToStart = (Start-End);
float ConeLength = EndToStart.Size();
float Angle = FMath::RadiansToDegrees(FMath::Atan(SphereRadius / ConeLength));
//Render Sphere for bone end point and a cone between it and its parent.
PDI->SetHitProxy( new HPersonaBoneProxy( Socket->BoneName ) );
PDI->DrawLine( Start, End, SocketColor, SDPG_Foreground );
PDI->SetHitProxy( NULL );
// draw gizmo
if( (LocalAxesMode == ELocalAxesMode::All) || bSelectedSocket )
{
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
PDI->SetHitProxy( new HPersonaSocketProxy( FSelectedSocketInfo( Socket, bUseSkeletonSocketColor ) ) );
DrawWireDiamond( PDI, SocketMatrix, 2.f, SocketColor, SDPG_Foreground );
PDI->SetHitProxy( NULL );
RenderGizmo(FTransform(SocketMatrix), PDI);
}
}
}
}
FSphere FAnimationViewportClient::GetCameraTarget()
{
bool bFoundTarget = false;
FSphere Sphere(FVector(0,0,0), 100.0f); // default
if( PreviewSkelMeshComp.IsValid() )
{
FTransform ComponentToWorld = PreviewSkelMeshComp->ComponentToWorld;
PreviewSkelMeshComp.Get()->CalcBounds(ComponentToWorld);
if( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
const int32 FocusBoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
if( FocusBoneIndex != INDEX_NONE )
{
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(FocusBoneIndex);
Sphere.Center = PreviewSkelMeshComp->GetBoneLocation(BoneName);
Sphere.W = 30.0f;
bFoundTarget = true;
}
}
if( !bFoundTarget && (PreviewSkelMeshComp->SocketsOfInterest.Num() > 0) )
{
USkeletalMeshSocket * Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
if( Socket )
{
Sphere.Center = Socket->GetSocketLocation( PreviewSkelMeshComp.Get() );
Sphere.W = 30.0f;
bFoundTarget = true;
}
}
if( !bFoundTarget )
{
FBoxSphereBounds Bounds = PreviewSkelMeshComp.Get()->CalcBounds(FTransform::Identity);
Sphere = Bounds.GetSphere();
}
}
return Sphere;
}
void FAnimationViewportClient::UpdateCameraSetup()
{
static FRotator CustomOrbitRotation(-33.75, -135, 0);
if (PreviewSkelMeshComp.IsValid() && PreviewSkelMeshComp->SkeletalMesh)
{
FSphere BoundSphere = GetCameraTarget();
FVector CustomOrbitZoom(0, BoundSphere.W / (75.0f * (float)PI / 360.0f), 0);
FVector CustomOrbitLookAt = BoundSphere.Center;
SetCameraSetup(CustomOrbitLookAt, CustomOrbitRotation, CustomOrbitZoom, CustomOrbitLookAt, GetViewLocation(), GetViewRotation() );
GetAdvancedPreviewScene()->SetFloorOffset(GetFloorOffset());
}
}
void FAnimationViewportClient::FocusViewportOnSphere( FSphere& Sphere )
{
FBox Box( Sphere.Center - FVector(Sphere.W, 0.0f, 0.0f), Sphere.Center + FVector(Sphere.W, 0.0f, 0.0f) );
bool bInstant = false;
FocusViewportOnBox( Box, bInstant );
Invalidate();
}
void FAnimationViewportClient::FocusViewportOnPreviewMesh()
{
FIntPoint ViewportSize = Viewport->GetSizeXY();
if(!(ViewportSize.SizeSquared() > 0))
{
// We cannot focus fully right now as the viewport does not know its size
// and we must have the aspect to correctly focus on the component,
bFocusOnDraw = true;
}
FSphere Sphere = GetCameraTarget();
FocusViewportOnSphere(Sphere);
}
float FAnimationViewportClient::GetFloorOffset() const
{
if ( PersonaPtr.IsValid() )
{
USkeletalMesh* Mesh = PersonaPtr.Pin()->GetMesh();
if ( Mesh )
{
return Mesh->FloorOffset;
}
}
return 0.0f;
}
void FAnimationViewportClient::SetFloorOffset( float NewValue )
{
if ( PersonaPtr.IsValid() )
{
USkeletalMesh* Mesh = PersonaPtr.Pin()->GetMesh();
if ( Mesh )
{
// This value is saved in a UPROPERTY for the mesh, so changes are transactional
FScopedTransaction Transaction( LOCTEXT( "SetFloorOffset", "Set Floor Offset" ) );
Mesh->Modify();
Mesh->FloorOffset = NewValue;
UpdateCameraSetup(); // This does the actual moving of the floor mesh
Invalidate();
}
}
}
TWeakObjectPtr<AWindDirectionalSource> FAnimationViewportClient::CreateWindActor(UWorld* World)
{
TWeakObjectPtr<AWindDirectionalSource> Wind = World->SpawnActor<AWindDirectionalSource>(PrevWindLocation, PrevWindRotation);
check(Wind.IsValid());
//initial wind strength value
Wind->GetComponent()->Strength = PrevWindStrength;
return Wind;
}
void FAnimationViewportClient::EnableWindActor(bool bEnableWind)
{
UWorld* World = PersonaPtr.Pin()->PreviewComponent->GetWorld();
check(World);
if(bEnableWind)
{
if(!WindSourceActor.IsValid())
{
WindSourceActor = CreateWindActor(World);
}
}
else if(WindSourceActor.IsValid())
{
PrevWindLocation = WindSourceActor->GetActorLocation();
PrevWindRotation = WindSourceActor->GetActorRotation();
PrevWindStrength = WindSourceActor->GetComponent()->Strength;
if(World->DestroyActor(WindSourceActor.Get()))
{
//clear the gizmo (translation or rotation)
PersonaPtr.Pin()->DeselectAll();
}
}
}
void FAnimationViewportClient::SetWindStrength( float SliderPos )
{
if(WindSourceActor.IsValid())
{
//Clamp grid size slider value between 0 - 1
WindSourceActor->GetComponent()->Strength = FMath::Clamp<float>(SliderPos, 0.0f, 1.0f);
//to apply this new wind strength
WindSourceActor->UpdateComponentTransforms();
}
}
float FAnimationViewportClient::GetWindStrengthSliderValue() const
{
if(WindSourceActor.IsValid())
{
return WindSourceActor->GetComponent()->Strength;
}
return 0;
}
FText FAnimationViewportClient::GetWindStrengthLabel() const
{
//Clamp slide value so that minimum value displayed is 0.00 and maximum is 1.0
float SliderValue = FMath::Clamp<float>(GetWindStrengthSliderValue(), 0.0f, 1.0f);
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(2)
.SetMaximumFractionalDigits(2);
return FText::AsNumber(SliderValue, &FormatOptions);
}
void FAnimationViewportClient::SetGravityScale( float SliderPos )
{
GravityScaleSliderValue = SliderPos;
float RealGravityScale = SliderPos * 4;
check(PersonaPtr.Pin()->PreviewComponent);
UWorld* World = PersonaPtr.Pin()->PreviewComponent->GetWorld();
AWorldSettings* Setting = World->GetWorldSettings();
Setting->WorldGravityZ = UPhysicsSettings::Get()->DefaultGravityZ*RealGravityScale;
Setting->bWorldGravitySet = true;
}
float FAnimationViewportClient::GetGravityScaleSliderValue() const
{
return GravityScaleSliderValue;
}
FText FAnimationViewportClient::GetGravityScaleLabel() const
{
//Clamp slide value so that minimum value displayed is 0.00 and maximum is 4.0
float SliderValue = FMath::Clamp<float>(GetGravityScaleSliderValue()*4, 0.0f, 4.0f);
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(2)
.SetMaximumFractionalDigits(2);
return FText::AsNumber(SliderValue, &FormatOptions);
}
void FAnimationViewportClient::ToggleCPUSkinning()
{
if (PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bCPUSkinning = !PreviewSkelMeshComp->bCPUSkinning;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetCPUSkinningChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bCPUSkinning;
}
return false;
}
void FAnimationViewportClient::ToggleShowNormals()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawNormals = !PreviewSkelMeshComp->bDrawNormals;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowNormalsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawNormals;
}
return false;
}
void FAnimationViewportClient::ToggleShowTangents()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawTangents = !PreviewSkelMeshComp->bDrawTangents;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowTangentsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawTangents;
}
return false;
}
void FAnimationViewportClient::ToggleShowBinormals()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawBinormals = !PreviewSkelMeshComp->bDrawBinormals;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowBinormalsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawBinormals;
}
return false;
}
void FAnimationViewportClient::ToggleDrawUVOverlay()
{
bDrawUVs = !bDrawUVs;
Invalidate();
}
bool FAnimationViewportClient::IsSetDrawUVOverlayChecked() const
{
return bDrawUVs;
}
void FAnimationViewportClient::OnSetShowMeshStats(int32 ShowMode)
{
ConfigOption->SetShowMeshStats(ShowMode);
}
bool FAnimationViewportClient::IsShowingMeshStats() const
{
const bool bShouldBeEnabled = ConfigOption->ShowMeshStats != EDisplayInfoMode::None;
const bool bCanBeEnabled = !PersonaPtr.Pin()->IsModeCurrent(FPersonaModes::AnimBlueprintEditMode);
return bShouldBeEnabled && bCanBeEnabled;
}
bool FAnimationViewportClient::IsShowingSelectedNodeStats() const
{
return PersonaPtr.Pin()->IsModeCurrent(FPersonaModes::AnimBlueprintEditMode) && ConfigOption->ShowMeshStats == EDisplayInfoMode::SkeletalControls;
}
bool FAnimationViewportClient::IsDetailedMeshStats() const
{
return ConfigOption->ShowMeshStats == EDisplayInfoMode::Detailed;
}
int32 FAnimationViewportClient::GetShowMeshStats() const
{
return ConfigOption->ShowMeshStats;
}
FAdvancedPreviewScene* FAnimationViewportClient::GetAdvancedPreviewScene() const
{
return static_cast<FAdvancedPreviewScene*>(PreviewScene);
}
#undef LOCTEXT_NAMESPACE