Files
UnrealEngineUWP/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp
Marc Audy 22f58737f9 Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3431384)
#lockdown Nick.Penwarden
#rb none

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

Change 3252833 on 2017/01/10 by Ori.Cohen

	Refactor constraint so that it can be used for external solvers. (Copying //Tasks/UE4/Dev-ImmediateModePhysics to Dev-Framework (//UE4/Dev-Framework))

Change 3256288 on 2017/01/12 by Ori.Cohen

	Undo constraint refactor as we found a way around it and it made the code much harder to read/debug

Change 3373195 on 2017/03/30 by Mike.Beach

	For nativization, changing it so we key off of the target platform-info struct instead of the platform (in preparation for defining the nativized plugin's platform whitelist).

Change 3381178 on 2017/04/05 by Dan.Oconnor

	Make sure we don't inherit the NATIVE func flag when generating skeleton functions, also make sure all bojects outer'd to the skeleton class are marked transient
	#jira UE-43616

Change 3381532 on 2017/04/05 by Marc.Audy

	(4.16) Fix various cases where built lighting on child actors could be lost when loading a level
	#jira UE-43553

Change 3381586 on 2017/04/05 by Mike.Beach

	Now generating TArrayCaster conversions for nativized UClass arrays that need it (to handle different TSubclassOf arrays).

	#jira UE-42676, UE-43257

Change 3381682 on 2017/04/05 by mason.seay

	Some more changes to test map

Change 3381844 on 2017/04/05 by Dan.Oconnor

	Match existing logic for CPF_ReturnParm/CPF_OutParm. Fixes compilation error in BP_TurbineBlades when using compilation manager

Change 3382054 on 2017/04/05 by Zak.Middleton

	#ue4 - Optimize CharacterMovementComponent::GetPredictionData_Client_Character() and GetPredictionData_Server_Character() to remove virtual calls.

	#jira UE-30998

Change 3382703 on 2017/04/06 by Lukasz.Furman

	fixed missing links between navmesh polys when there are more than 4 neighbor connections
	#jira UE-43524

Change 3383357 on 2017/04/06 by Marc.Audy

	(4.16) Make SetHiddenInGame propagate consistently with SetVisibility
	#jira UE-43709

Change 3383359 on 2017/04/06 by Dan.Oconnor

	Fix last errant SKEL reference when cooking Odin

Change 3383591 on 2017/04/06 by Mike.Beach

	Prevent users from setting object variables as 'config' properties (disallowed by UHT). This prevents some errors that could happen later when users nativize the Blueprint.

	#jira UE-42085

Change 3384762 on 2017/04/07 by Zak.Middleton

	#ue4 - Fix SpringArmComponent not restoring relative transform when bUsePawnControlRotation is turned off. Fixes the editor interaction ignoring transform of the component in the viewport after bUsePawnControlRotation is toggled on then off, since by then the world transform had been overwritten (from tick in editor) and nothing would drive transform changes from the editable value.

	Toggling bUsePawnControlRotation off at runtime now restores the rotation to the initial relative rotation, not stomping it with the current pawn rotation, allowing toggling between the editable/desired base rotation and the control rotation.

	#jira UE-24850

Change 3384948 on 2017/04/07 by Dan.Oconnor

	Prevent GForceDisableBlueprintCompileOnLoad from causing all sorts of badness when dependencies are loaded as part of a Diff operation. Instead of setting a global flag we flag the package as LOAD_DisableCompileOnLoad

Change 3385267 on 2017/04/07 by Michael.Noland

	Graph Editing: Pushed some node diffing code down from UAIGraphNode into UEdGraphNode so nodes with details panel properties will diff correctly (e.g., various animation nodes and BP switch nodes)

	#jira UE-21724

Change 3385473 on 2017/04/07 by Phillip.Kavan

	#jira UE-43067 - Fix broken pin wires after an Expand Node operation, along with some misc. cleanup.

	Change summary:
	- Fixed to use correct string for "Expand Node" transaction name.
	- Modified FBlueprintEditor::OnExpandNodes() to consolidate some redundant code.
	- Fixed to generate a unique node GUID for cases where the source graph is not removed after expansion.

Change 3385583 on 2017/04/07 by Dan.Oconnor

	Handle CreatePropertyOnScope nullptr return values (happens for structs missing a struct property)

	#jira UE-43746

Change 3386581 on 2017/04/10 by Michael.Noland

	Blueprints: Further hardening FBlueprintActionInfo::GetOwnerClass()
	#jira UE-43824

Change 3386615 on 2017/04/10 by Marc.Audy

	Instanced properties can now properly be set on a per-instance basis in blueprint added components.
	#jira UE-42066

Change 3387000 on 2017/04/10 by Marc.Audy

	Fix includes for CIS

Change 3387229 on 2017/04/10 by mason.seay

	More changes to TM-Gameplay

	Added Save Game test (with blueprint)

	Tick Interval test (with blueprint)

	BP logic cleanup

	Level organization

Change 3388437 on 2017/04/11 by Mike.Beach

	Adding support for map/set literals in the backend (so you can use set nodes for structs containing sets/maps, without having to connect a RHS input - resets to struct defaults).

	#jira UE-42617

Change 3388532 on 2017/04/11 by mason.seay

	Submitting latest changes for crash repro

Change 3389026 on 2017/04/11 by Ben.Zeigler

	Performance and bug fixes for incremetal cooking with asset registry, duplicate of several changes made on //Fortnite/Main
	Fix it so AssetRegistry.ScanPathsAndFilesSynchronous won't scan subdirectories inside already scanned directories, this cuts down on the number of cache files
	Fix 2 second stall when shutting down AssetSourceFilenameCache if it had never been previously created

Change 3389163 on 2017/04/11 by Ben.Zeigler

	#jira UE-42922 Fix it so connecting function input node output pins does not clear default value, we only want to clear the value when connecting an input pin. Properly testing this fix depends on UE-43883

Change 3389205 on 2017/04/11 by Marc.Audy

	Protect against a handful of GEditor usages that can now be hit in standalone

Change 3389220 on 2017/04/11 by Marc.Audy

	Don't borrow ClassWithin to masquerade as ParentClass during compilation and instead just set the super struct immediately

Change 3389222 on 2017/04/11 by Michael.Noland

	Framework: Adding a cvar (t.TickComponentLatentActionsWithTheComponent) to allow users to revert to the old behavior on when component latent actions tick
	- Non-zero values behave the same way as actors do, ticking pending latent action when the component ticks, instead of later on in the frame (default behavior in 4.16 and beyond)
	- Prior to 4.16, components behaved as if the value were 0, which meant their latent actions behaved differently to actors

	This CVar will be removed in a future version, defaulting to on

	#jira UE-43661

Change 3389276 on 2017/04/11 by Marc.Audy

	Spelling fix and NULL to nullptr

Change 3389303 on 2017/04/11 by Mieszko.Zielinski

	Made sure AIController::Posses doesn't get called when compiling Pawn BP #UE4

	#jira UE-43873

Change 3390215 on 2017/04/12 by mason.seay

	Removed some tests, will need further review

Change 3390638 on 2017/04/12 by Mike.Beach

	Generalizing the omission of the CoerceProperty (in EmitTerm) - previously we were only omitting properties for our custom array lib. For wildcards, a coerce property should not be used as its type will not match.

	NOTE: There is a slight behavior change in UEdGraphSchema_K2::ConvertPropertyToPinType(), as it will return 'wildcard' for params marked as 'ArrayTypeDependentParams' (previously would have returned 'int').

	#jira UE-42747

Change 3390774 on 2017/04/12 by Ben.Zeigler

	#jira UE-43911 Fix several issues with saving a runtime asset registry containing redirectors that caused crashes in cook on the fly. Don't resolve redirectors on incoming links because it will make a circular link, and fix an issue where chained redirectors would break the for loop iteration and return a bad dependency
	Fix it so the asset registry written out at the beginning of CookOnTheFly uses the registry generator, otherwise it will include all of the stripped editor only tags

Change 3390778 on 2017/04/12 by Ben.Zeigler

	Fix UCookOnTheFlyServer::CollectFilesToCook to check for initial unsolicited packages up front. This is required in iterative mode because it may skip cooking all explicit packages and thus miss a new startup loaded package

Change 3390782 on 2017/04/12 by Ben.Zeigler

	Change RunProjectCommand to not imply -nomcp, and allow reading -clientcmdline to override setting the map parameter to 127.0.0.1 by default
	Fix RunProjectCommand to remove ios-specific checks to not pass weird platform parameters, and instead never pass them
	Fix PS4Platform to pass along command line when calling build cook run, args needs to be the last parameter so explicitly set -target=

Change 3390859 on 2017/04/12 by Mike.Beach

	T3D class fields now export with the class's fully qualified path name (to avoid abiguity). Since we can have multiple classes with the same name (Blueprints in different folders), we have to use the class's fully qualified object path.

	#jira UE-28048

Change 3390914 on 2017/04/12 by Lukasz.Furman

	fixed missing navlink component's transform in exported navigation data
	#jira  UE-43688

Change 3391122 on 2017/04/12 by Ben.Zeigler

	Add new PreloadPrimaryAssets call to AssetManager that stream the desired assets without modifying the official load/unload state. This is useful if you want to preload things in case the might be used in the future, and it also supports recursion
	Fix crash calling GetAssetDataForPath with null path

Change 3391494 on 2017/04/12 by Dan.Oconnor

	Fix bad references in deep object (widget) hierarchies

	#jira UE-43802

Change 3391529 on 2017/04/12 by Dan.Oconnor

	Fix log spam, accidently submitted

	#rnx

Change 3391756 on 2017/04/12 by Dan.Oconnor

	LinkExternalDependencies needs to be performed before we RefreshVariables

	#jira UE-43843

Change 3392542 on 2017/04/13 by Marc.Audy

	Ensure that initialized actors get cleaned up when removed from world even if that world hasn't begun play.
	#jira UE-43879

Change 3392746 on 2017/04/13 by Marc.Audy

	(4.16) When duplicating a blueprint node, correctly make the new node a sibling of the duplicated node, not a child of it (unless duplicating the root component).
	Also resets scale of a duplicated root component to 1 to avoid a squaring of the scale for that component.
	#jira UE-40218
	#jira UE-42086

Change 3393253 on 2017/04/13 by Dan.Oconnor

	Make sure calculated meta data is correctly set on functions generated by the compilation manager (SKEL_ class functions)

	#jira UE-43883

Change 3393509 on 2017/04/13 by Mike.Beach

	Removing hack'ish ResetLoaders() call that was causing undesired side-effects (resetting of a loaded package that other objects were relying on). This was originally intended to release file handles so separate editor processes could make updates and save the file (from CL 1712376). Using ResetLoaders() for this is bad though, as it has too many side effects. Instead we have to wait for GC to run. This also makes sure that GC should run as intended as the CookOnTheFly sever is idling.

	#jira UE-37284

Change 3394350 on 2017/04/14 by Michael.Noland

	Core: Making FDateTime and FTimespan actually reflected, so they get duplicated properly in CopyPropertiesForUnrelatedObjects, etc...
	#jira UE-39921

Change 3395985 on 2017/04/17 by Phillip.Kavan

	#jira UE-38280 - Fix invalid custom type selections on member fields in the User-Defined Structure Editor after a reload.

	Change summary:
	- Ensure that the 'SubCategoryObject' member in a UDS variable descriptor has been loaded when converting to an FEdGraphPinType.

Change 3396152 on 2017/04/17 by Marc.Audy

	TickableGameObjects that have IsTickableInEditor false should not tick in the editor
	#jira UE-40421

Change 3396279 on 2017/04/17 by Phillip.Kavan

	#jira UE-43968 - Fix failed validation of bitmask enum types when serializing bitmask literal nodes.

Change 3396299 on 2017/04/17 by Dan.Oconnor

	Fix resintancing issues exposed by running TM-Gameplay with -game. We cannot reinstance actors in levels on load because the scene is not created.

	#jira UE-43859

Change 3396712 on 2017/04/17 by Marc.Audy

	Call PostLoad on subobjects before copying for unrelated properties to avoid cases where an out of date object patched over in the linker has not been brought up to date
	#jira UE-38234

Change 3396718 on 2017/04/17 by Mike.Beach

	Adding a search bar to the components tree for Blueprints.

	#epicfriday
	#jira UE-17620

Change 3396999 on 2017/04/17 by Mike.Beach

	In generated code, call event '_Implementation' functions directly for interface functions being invoked on self (avoids a UHT runtime error).

	#jira UE-44018

Change 3397700 on 2017/04/18 by Marc.Audy

	UT struct BlueprintType fixups

Change 3397701 on 2017/04/18 by Marc.Audy

	Odin struct BlueprintType fixups

Change 3397703 on 2017/04/18 by Marc.Audy

	Ocean struct BlueprintType fixups

Change 3397704 on 2017/04/18 by Marc.Audy

	WEX struct BlueprintType fixups

Change 3397705 on 2017/04/18 by Marc.Audy

	Additional UT blueprint type struct fixups

Change 3397706 on 2017/04/18 by Marc.Audy

	Fortnite struct BlueprintType fixups

Change 3397708 on 2017/04/18 by Marc.Audy

	Fixup Engine BlueprintType markup of structs

Change 3397709 on 2017/04/18 by Marc.Audy

	Sample Game struct BlueprintType fixups

Change 3397711 on 2017/04/18 by Marc.Audy

	Mark AnimNodes as BlueprintType and BlueprintInternalUseOnly

Change 3397712 on 2017/04/18 by Marc.Audy

	Paragon struct BlueprintType fixups

Change 3397735 on 2017/04/18 by Marc.Audy

	Definition pieces of BlueprintInternalUseOnly to fix UHT errors with structs already marked to use it

Change 3397912 on 2017/04/18 by Mike.Beach

	Fix for CIS warnings about shadowed variables (fallout from CL 3396718).

Change 3398455 on 2017/04/18 by Marc.Audy

	Make less critical errors log an error rather than immediately throwing allowing multiple errors to be reported in the same compile

Change 3398491 on 2017/04/18 by Marc.Audy

	BPRW/BPRO in a non-BlueprintType is now a UHT error

Change 3398539 on 2017/04/18 by Marc.Audy

	Fixup live link struct markups

Change 3399412 on 2017/04/19 by Marc.Audy

	Fix Match3 blueprint type struct markups

Change 3399509 on 2017/04/19 by Phillip.Kavan

	#jira UE-38574 - Fix AnimBlueprint function graphs marked as 'const' to treat 'self' as read-only when compiling.

	Change summary:
	- Modified FKismetCompilerContext::ProcessOneFunctionGraph() to use the function graph schema rather than the compiler context schema for both the function context's schema as well as testing the function for 'const'-ness. For AnimBPs, the compiler context and the function graph context can differ, so we need to make sure we are using the right one when making queries for a specific function context during compilation.
	- Minor cleanup: changed the function context schema to be 'const' in order to be consistent with the function graph GetSchema() API's result. Added a few 'const' qualifiers where needed to match.
	- Added a new object version in order to avoid breaking compilation of existing AnimBP function graphs that may already be violating the 'const' rule (this is the same thing that was done when 'const' was first added to "normal" BP function graphs). Just as with normal function graphs in place before the addition, a warning will be generated for existing AnimBP function graphs if they violate 'const' correctness, and an error will be generated for all new ones.

Change 3399749 on 2017/04/19 by Mike.Beach

	Hiding the Nativized Blueprints plugin from the in-editor browser (prevent users from disabling it).

Change 3399774 on 2017/04/19 by Marc.Audy

	ConditionalPostLoad is already called on StaticMesh earlier in the function

	#rnx

Change 3400313 on 2017/04/19 by Mike.Beach

	Mirroring CL 3398673 from 4.16

	Now, with ICWYU, making sure that the coresponding header gets included first in nativized Blueprint files (else we get a UHT error). Had to fixup some ShooterGame specific files as a result (they had missing includes and forward declarations).

	#jira UE-44124

Change 3400328 on 2017/04/19 by Mike.Beach

	Missing file from mirrored change (CL 3400313 - mirroring CL 3398673 from 4.16)

	#jira UE-44124

Change 3400415 on 2017/04/19 by Chad.Garyet

	adding physx switch build to framework

Change 3400514 on 2017/04/19 by Mike.Beach

	Back out changelist 3400313 / 3400328 (mirrored from CL 3398673 in 4.16), as it was producing "include PCH first" errors. Likely, CL 3398673 was a fix for a 4.16 specific change, altering the expected include order. We'll have to wait for this one to be integrated back.

Change 3400552 on 2017/04/19 by Marc.Audy

	Undo the calling of post load prior to the CPFUO as dependent objects may not yet be loaded. Instead copy the need load flag to the new CDO subobject, similarly to how the top level CDO object copies its flags over.
	#jira UE-44150

Change 3400815 on 2017/04/19 by Marc.Audy

	Spelling fix (part of PR #3490)

	#rnx

Change 3400918 on 2017/04/19 by Marc.Audy

	Partial pull of PR #3490: Improved remapping game controls support (Contributed by projectgheist)
	This portion brings in the exposure of the bindings to blueprint
	#jira UE-44122

Change 3401550 on 2017/04/20 by Marc.Audy

	fix kitedemo blueprint type markup

	#rnx

Change 3401702 on 2017/04/20 by Mike.Beach

	Make it so plugins added to a project through the .uproject's 'AdditionalPluginDirectories' list get folded into the generated code project (for visual studio, etc.).

Change 3401720 on 2017/04/20 by Mike.Beach

	Add white and black lists for target type (game, client, server, etc.) to plugin module descriptors.

Change 3401725 on 2017/04/20 by Mike.Beach

	Whitelisting the nativized Blueprint plugin for only the targets it was built for (game, server, or client).

Change 3401800 on 2017/04/20 by Ben.Zeigler

	Add Algo::BinarySearch, LowerBound, and UpperBound. These are setup to allow binary searching a presorted array, and allow for specifying projection and sort predicates. Convert some engine code to use it
	Add TSortedMap, which is a map data structure that has the same API as TMap, but is backed by a sorted array. It uses half the memory and performance is faster below n=10
	Add FName::CompareIndexes so a SortedMap with FNames can be used without doing very slow string compares, and FNameSortIndexes predicate to sort by it
	Add code to Algo and Container tests. Split up container tests so the new ones aren't run in smoketest as they are a bit slow
	Add RemoveCurrent and SetToEnd to ArrayIterator

Change 3401849 on 2017/04/20 by Marc.Audy

	Partial pull of PR #3490: Improved remapping game controls support (Contributed by projectgheist)

	This portion brings bug fixes and improvements to InputKeySelector UMG widgets.
	#jira UE-44122

Change 3402088 on 2017/04/20 by Marc.Audy

	Focus the search box when expanding the map value type
	#jira UE-44211

Change 3402251 on 2017/04/20 by Ben.Zeigler

	Fix issue where SortedMap needs to be resorted after serialization, because the sorting may have changed from when it was saved out

Change 3402335 on 2017/04/20 by Ben.Zeigler

	Significant changes to FAssetData serialization and memory, cuts memory significantly but will break code that was using some of the internal API that was not properly hidden before
	Both Editor and Runtime cache now use the same FAssetRegistryVersion, which is now registered as a custom version
	Rename FAssetData and FAssetPackage operator<< to SerializeForCache to make it clear that it isn't safe to use for general serialization
	Remove GroupNames from FAssetData, it has not been useful since the UE4 package structure changed around 4.0
	Rename generic-sounding but not actually generic SharedMapView class to AssetDataTagMapSharedView to indicate what it is actually used for
	Change TagsAndValues to use a new array-backed TSortedMap as the base structure instead of a hash map. Also, it only allocates the map on demand, which saves significant memory at runtime as many packages have no tags
	Add bFilterAssetDataWithNoTags to [AssetRegistry] ini section, if set it will only save cooked asset data if it has tags, off by default but saves significant memory if your whitelist is set up properly
	Fix issue where asset registry tags updated by loading assets during cook were not being reflected in the cooked registry
	Add AssetRegistry::GetAllocatedSize and add to MemReport output

Change 3402457 on 2017/04/20 by Ben.Zeigler

	Enable asset registry iteration and stripping unused asset data in Fortnite. Registry iteration is already on in //Fortnite/Main, stripping is a new feature I want to test

Change 3402498 on 2017/04/20 by Ben.Zeigler

	CIS fix. Why did this compile locally?

Change 3402537 on 2017/04/20 by Ben.Zeigler

	Remove ensure for making AssetData for subobjects, the editor does this for thumbnail creation in some cases

Change 3402600 on 2017/04/20 by Ben.Zeigler

	Add bShouldGuessTypeAndNameInEditor to manager settings, can be set false for games where type cannot be safely implied and content must be resaved
	Fix up some bool setting code inside asset manager, and fix const correctness and for iterator issues
	AssetManager can now discover any BlueprintCore type when bHasBlueprintClasses=true
	Add AssetManager.DumpAssetRegistryInfo to output detailed asset registry usage stats
	Add Primary Name to asset audit window by default

Change 3403556 on 2017/04/21 by Marc.Audy

	Fix Orion input key selector override class

	#rnx

Change 3404090 on 2017/04/21 by mason.seay

	Applying Forcefeedback to test map

Change 3404093 on 2017/04/21 by mason.seay

	Changing text in level

Change 3404139 on 2017/04/21 by mason.seay

	Added Force Feedback test and made some tweaks.

Change 3404146 on 2017/04/21 by mason.seay

	Added source reference to Instanced Variable test

Change 3404154 on 2017/04/21 by mason.seay

	More minor tweaks

Change 3404155 on 2017/04/21 by Marc.Audy

	Remove auto

	#rnx

Change 3404188 on 2017/04/21 by Marc.Audy

	Fixed crash changing variable type when any type other than map
	#jira UE-44249

	#rnx

Change 3404463 on 2017/04/21 by Ben.Zeigler

	Fix asset data code to not ensure when loading an object with invalid exports, and instead print warning with name of package that needs to be resaved
	Resave a map that had a redirector from a DIFFERENT package saved in it's exports. I do not understand how this happened, but it appears to be related to the lightmap BuiltData transition when old maps are opened

Change 3404465 on 2017/04/21 by Ben.Zeigler

	Fix issue with trying to load editor-only asset classes in a cooked build
	Fix issues with renaming or changing template Ids of assets from the editor
	Always print the Duplicate Asset ID error, as if you have more than one the ensuremsg only goes off once

Change 3404481 on 2017/04/21 by Dan.Oconnor

	Remove unneeded walk up hierarchy - prevent stale entries in action database if we compile a BP but don't compile its children

Change 3404510 on 2017/04/21 by Phillip.Kavan

	#jira UE-35727 - Collapsed graphs containing a local variable node will no longer cause a compile error when the parent graph is renamed.

Change 3404590 on 2017/04/21 by Michael.Noland

	Editor: Fixed incorrect filtering of abstract/deprecated UDeveloperSettings and UContentBrowserFrontEndFilterExtension classes caused by a typo (HasAnyCastFlags versus HasAnyClassFlags)

Change 3404593 on 2017/04/21 by Marc.Audy

	Fixed another crash to do with input pin secondary combo box
	#jira UE-44269

	#rnx

Change 3404600 on 2017/04/21 by Michael.Noland

	Core: Allow UE_GC_TRACK_OBJ_AVAILABLE to be set externally
	#rnx

Change 3404602 on 2017/04/21 by Michael.Noland

	Engine: Switched from an include to a forward declaration of SWidget in UDeveloperSettings to keep it slim
	#rnx

Change 3404608 on 2017/04/21 by Michael.Noland

	Core: Marked TNumericLimits as constexpr so they can be used in static asserts

Change 3404659 on 2017/04/21 by Michael.Noland

	Engine: Adding includes back to two UDeveloperSettings subclasses

Change 3405289 on 2017/04/24 by Marc.Audy

	Remove auto

	#rnx

Change 3405446 on 2017/04/24 by Marc.Audy

	Fix Win32 unsigned compile issue

Change 3405512 on 2017/04/24 by Mike.Beach

	Piping through NativizationOptions to filename generation (so we're able to gen different files names per target: client vs. server).

Change 3406080 on 2017/04/24 by Ben.Zeigler

	Deprecate UEngine::OnPostEngineInit and move to FCoreDelegates, clean up comments for the initialization delegates
	Call OnPostEngineInit from commandlet initialization as well as normal execution. I thought about making a wrapper function, but the commandlet calls EditorInit directly so it wouldn't work
	Bind delegate to refresh the AssetRegistry native class hierarchy after engine init so it picks up game/plugin classes. Undo ini change that was required to hack around this

Change 3406381 on 2017/04/24 by Ben.Zeigler

	#jira UE-23768 Enable Run Physics With No Controller for montage test pawn. The montage pawn has no controller so wasn't correctly running physics when the root motion stopped. This flag needs to be set to allow it to correctly stop after the montage is over

Change 3406438 on 2017/04/24 by Ben.Zeigler

	Fix deprecation warning

Change 3406519 on 2017/04/24 by Phillip.Kavan

	#jira UE-43612 - Suppress array "Get" node fixup notifications on load when the BP Compilation Manager is enabled.

	Change summary:
	- Wrapped BPCM calls to FBlueprintEditorUtils::ReconstructAllNodes() and ReplaceDeprecatedNodes() duirng compile-on-load with bIsRegeneratingOnLoad = true. This matches the BP's state during compile-on-load when the BPCM is not enabled.

Change 3406565 on 2017/04/24 by Dan.Oconnor

	Make sure all interface functions are added to skeleton

	#jira UE-44152

Change 3407489 on 2017/04/25 by Ben.Zeigler

	#jira UE-44317 Fix game-only TickableGameObjects to correctly tick in PIE

Change 3407558 on 2017/04/25 by Ben.Zeigler

	Fix Fortnite cook warnings, issue had to do with the CDO being registered as a Primary Asset in conflict with the Class being registered
	Fix issue with renaming a BP primary asset not finding the old name

Change 3407701 on 2017/04/25 by Dan.Oconnor

	Remove unneeded null check, static analysis doen't like the inconsistency

Change 3407995 on 2017/04/25 by Marc.Audy

	Fixed maps and sets not working correctly with split pin.
	#jira UE-43857

Change 3408124 on 2017/04/25 by Ben.Zeigler

	#jira UE-39586 Change it so the blueprint String/Name/Object to Text node creates culture invariant text, and also have them show as an expanded node with a comment explaining this
	Fix Transform to actually return in the format specified in the comment, and fix comments on many text conversions

Change 3408134 on 2017/04/25 by Marc.Audy

	Graph pin container type now represented by an enumeration (EPinContainerType) rather than 3 "independent" booleans.
	FEdGraphPinType constructor, UEdGraphNode::CreatePin, and FKismetCompilerContext::SpawnInternalVariable that took 3 booleans deprecated and replaced with a version that takes EPinContainerType.
	UEdGraphNode::CreatePin parameters reorganized so that PinName is before ContainerType and bIsReference, which default to None and false respectively

Change 3408256 on 2017/04/25 by Michael.Noland

	Core: Changed UClass::ClassFlags to be of type EClassFlags for improved type safety

Change 3408282 on 2017/04/25 by Marc.Audy

	(4.16) Fix incorrect positioning of instance components after duplication
	#jira UE-44314

Change 3408404 on 2017/04/25 by Mike.Beach

	Adding and removing the nativized plugin to/from the project when we alter the packaging nativization setting (so it gets picked up by project generation).

Change 3408445 on 2017/04/25 by Marc.Audy

	Fix up missed deprecation cases

	#rnx

Change 3409354 on 2017/04/26 by Marc.Audy

	Fix Linux CIS failure

	#rnx

Change 3409487 on 2017/04/26 by Marc.Audy

	When dragging assets in to the SCS create them as siblings, not nested
	#jira UE-43041

Change 3409776 on 2017/04/26 by Ben.Zeigler

	#jira UE-44401 Fix issue with cooking a map containing a reparented component. In that case the child component may think it's not editor only, but it's archetype is editor only. This is not allowed in EDL, so now the child is marked as editor only as well

Change 3410168 on 2017/04/26 by Dan.Oconnor

	Avoid calling virtual functions in the middle of compile
	#jira UE-44243

Change 3410252 on 2017/04/26 by Lukasz.Furman

	adjusted WITH_GAMEPLAY_DEBUGGER checks after IWYU changes
	#ue4

Change 3410385 on 2017/04/26 by Marc.Audy

	ChildActorComponent SetClass no longer fails when setting at runtime.
	#jira UE-43356

Change 3410466 on 2017/04/26 by Michael.Noland

	Core: Ensuring EClassFlags is 32 bit in a different way (underlying type of the enum is coming out signed even though all members are unsigned, long term fix is probably to move it to an enum class)
	#rnx

Change 3410476 on 2017/04/26 by Michael.Noland

	Automation: Deleting some commented out methods
	#rnx

Change 3411070 on 2017/04/27 by Marc.Audy

	Properly complete deprecation of old attachment API

Change 3411338 on 2017/04/27 by mason.seay

	Map for Latent Action Tick Bug

Change 3411637 on 2017/04/27 by Ben.Zeigler

	Back out CL #3381532 as it was causing crashes when adding new variables to blueprints, as the transaction array was being recursively modified while it was being added to

Change 3412052 on 2017/04/27 by mason.seay

	Updated jump test map and pawn

Change 3412231 on 2017/04/27 by Ben.Zeigler

	Fix issue where running SearchAllAssets multiple times after mounting new paths would throw away the asset registry cache, which slowed down incremental cooking substantially because the cooker mounts the autosave folder
	Duplicate of CL #3411860

Change 3412233 on 2017/04/27 by Ben.Zeigler

	Made FStreamableHandle::GetLoadedCount much faster by taking advantage of existing progress counter
	Duplicate of CL #3411778

Change 3412235 on 2017/04/27 by Ben.Zeigler

	Add code to FStringAssetReferenceThreadContext and FStringAssetReferenceSerializationScope which allows setting package name and collect options for string asset references serialized via something other than linker load
	Make RedirectCollector threadsafe to avoid issues with async loading asset references
	Fix it so ProcessStringAssetReferencePackageList will remove entries from the string asset array like resolve did, and rename function to indicate that
	Fix it so string asset references created by asset labels do not automatically get cooked, and significantly improve the speed of labels with lots of assets
	Add code to cooker and asset manager to explicitly mark non-cookable assets as NeverVook, this stops labels from ending up in the build if set that way
	Added option to not recurse package dependency changes more than one level when hashes change. This ended up not being significantly faster in a realistic case so left disabled
	Duplicate of CL #3412080

Change 3412352 on 2017/04/27 by Marc.Audy

	Refix lighting getting wrong position when getting component instance data

Change 3412426 on 2017/04/27 by Marc.Audy

	Take first steps to making ComponentToWorld private and force use of accessor
	Make bWorldToComponentUpdated private
	Make ComponentToWorld and bWorldToComponentUpdated mutable
	Add a SetComponentToWorld function for the (likely ill-advised) places that were setting it directly.

Change 3412468 on 2017/04/27 by Marc.Audy

	Remove last remnants of deprecated (4.11) custom location system

Change 3413398 on 2017/04/28 by Marc.Audy

	Fix up missed deprecated attachment API  uses

Change 3413403 on 2017/04/28 by Marc.Audy

	Fix Orion compile error

	#rnx

Change 3413448 on 2017/04/28 by Marc.Audy

	Fix up kite demo component to world privataization warnings

	#rnx

Change 3413792 on 2017/04/28 by Ben.Zeigler

	Fix many bugs with blueprint pin default values, and add "Reset to Default Value" option to pin context menu
	Deprecate and rename SetPinDefaultValue because it actually sets the Autogenerated default. This was being called in bad places and destroying the stored autogenerated defaults
	#jira UE-40101 Fix expose on spawn pins to correctly update when the spawned object's defaults change
	#jira UE-21642 Fix struct pin default values to properly update when the struct is changed
	#jira UE-39418 Fix changed function/macro default values to properly update in already placed call nodes

Change 3413839 on 2017/04/28 by samuel.proctor

	Added some Blueprint focused tests for TM-Gameplay

Change 3414030 on 2017/04/28 by Ben.Zeigler

	Enable use of AssetPtr variables with Config, for native and blueprint
	This incorporates CL #3302487 but also enables for blueprint usage as that code is new to framework branch

Change 3414229 on 2017/04/28 by Marc.Audy

	Fixup virtuals not calling their Super
	Remove some autos

	#rnx

Change 3414451 on 2017/04/28 by Lukasz.Furman

	static analysis fix for gameplay debugger

Change 3414482 on 2017/04/28 by Ben.Zeigler

	Fix crash found where changing pin type on ConvertAsset accessed an array while deleting it

Change 3414609 on 2017/04/28 by Ben.Zeigler

	#jira UE-18146 Refresh graph when disconnecting a resolve asset id node

Change 3415852 on 2017/05/01 by Marc.Audy

	Remove unused code

	#rnx

Change 3415856 on 2017/05/01 by Marc.Audy

	auto removal

	#rnx

Change 3415858 on 2017/05/01 by Marc.Audy

	Fix function taking an input as reference when unneeded and causing (still unclear why it suddenly started showing up) error in cooking

	#rnx

Change 3415946 on 2017/05/01 by Marc.Audy

	Have K2Node_StructOperation skip the K2Node_Variable validation as it doesn't need a property (per CL# 1756451)

	#rnx

Change 3415988 on 2017/05/01 by Lukasz.Furman

	renamed WorldContext param in AI related static blueprint functions to remove load/cook warnings
	#jira UE-44544

Change 3416030 on 2017/05/01 by Ben.Zeigler

	Fix issue with WorldContext pins being broken by my pin value refactor, partial paths like "WorldContext" need to be stored as strings and not as broken object references.

Change 3416230 on 2017/05/01 by Marc.Audy

	Fix spelling error

	#rnx

Change 3416419 on 2017/05/01 by Phillip.Kavan

	#jira UE-44213 - Nativizing a Blueprint class with a non-nativized Blueprint class subobject dependency will no longer lead to a crash at load time.

	Change summary:
	- Modified the FFakeImportTableHelper ctor to inject subobject CDOs into the 'SerializeBeforeCreateCDODependencies' array. This in turn ensures that EDL will serialize those subobject CDOs (if necessary) before we create the subobject's nativized owner's CDO at load time.
	- Modified FEmitDefaultValueHelper::GenerateCustomDynamicClassInitialization() to emit MiscConvertedSubobject instantiations AFTER we emit the FillUsedAssetsInDynamicClass() call. This is now consistent with the code emitted for other subobjects (all of which assumes that the UsedAssets array has been initialized).
	- Modified FFindAssetsToInclude::HandleObjectReference() to add UField owner CDOs in addition to the owner class to the asset dependency list. This ensures that owner CDOs will be emitted alongside the class to both the nativized asset dependency table as well as to the fake import table associated with the UDynamicClass linker for the nativized BP asset.

Change 3416425 on 2017/05/01 by Phillip.Kavan

	#jira UE-44219 - Nativizing a Blueprint class with a nativized DOBP class dependency will no longer lead to a compile error at cook/nativization time.

	- Modified the FGatherConvertedClassDependencies ctor to properly handle DOBPs in exclusive mode that have been explicitly enabled for nativization. Previously, this code wasn't taking that possibility into account, and as a result could lead to a missing header file in a dependent nativized class body's include set.
	- Modified FGatherConvertedClassDependencies::GetFirstNativeOrConvertedClass() to remove the 'bExcludeBPDataOnly' parameter, as it was primarily just being used for a redundant exclusion check when called from the FGatherConvertedClassDependencies ctor. That call site has now been modified to start searching from the super class instead. Additionally, any DOBPs will already fail the preceding WillClassBeConverted() check if they have not been explicitly enabled for nativization in exclusive mode, and will always fail if nativizing in inclusive mode. The extra check was breaking the explicitly-enabled case, so it was removed to allow explicitly-enabled DOBPs to pass.

	Notes:
	- Allowing for explicitly-enabled DOBPs in exclusive mode may be removed in a future change, but since it is currently supported, the changes noted above will at least ensure that the generated code will compile properly for now.

Change 3416570 on 2017/05/01 by mason.seay

	Added UMG test to map.  Tweaked force feedback test

Change 3416580 on 2017/05/01 by mason.seay

	Resubmitting sub levels

Change 3416597 on 2017/05/01 by Dan.Oconnor

	Compilation manager iteration, adds machinery for individual blueprint compilation, adds comments, cleans up duplicated code

Change 3416636 on 2017/05/01 by Phillip.Kavan

	#jira UE-44505 - Potential fix for a low-repro crash tied to the Blueprint graph context menu.

	Change summary:
	- Switched FBlueprintActionInfo::ActionOwner to be a weak object reference.

Change 3416960 on 2017/05/01 by Dan.Oconnor

	Use compilation manager when clicking the compile button, PIE'ing, etc

Change 3417207 on 2017/05/01 by Ben.Zeigler

	Fix issue with None strings causing default value parsing failures
	Add SetPinDefaultValueAtConstruction needed by some other changes

Change 3417519 on 2017/05/01 by Ben.Zeigler

	Fix BP compile errors caused by local variables with invalid default values. There's no reason to set autogenerated here because the nodes are transient and invisible in the UI.
	There is still a problem here, local variables are not getting their default values validated when type is changed, so you end up with an integer that has the default value of a struct.

Change 3418659 on 2017/05/02 by Ben.Zeigler

	#jira UE-44534 Fix it so animation node pins get properly created autogenerated default values that are based on the node struct defaults. This fixes issues when they are reset to other defaults
	#jira UE-44532 Fix it so connecting an animation asset pin on a node player resets the pin value to the autogenerated default instead of the cached asset. This was causing old unused assets to get unnecessarily cooked
	Fix it so any animation node with an exposed pin that is an object property will reset that object propery when the pin is exposed. This fixes UE-31015 in a generic way
	Change the OptionalPinManager to take a Defaults address as well as a current address, to allow setting autogenerated defaults properly
	Remove Import/ExportKismetDefaultValueToProperty as they were redundant with PropertyValueFromString and were using the wrong pin setting functions, replaced with PropertyValueFromString_Direct and calling the schema pin set functions
	I need to write some backward compatibility code to fix existing nodes, I'll do that in a later checkin

Change 3418700 on 2017/05/02 by Ben.Zeigler

	Actually fix None object paths for real this time. I did not test sufficiently before

Change 3418811 on 2017/05/02 by Ben.Zeigler

	Fix existing animation blueprint nodes with dead asset references duplicated by pins. This code can be applied independent of the other change to fix specific games

Change 3419165 on 2017/05/02 by Dan.Oconnor

	Add misc. functionality from FKismetEditorUtilities::CompileBlueprint

Change 3419202 on 2017/05/02 by Marc.Audy

	Merging //UE4/Dev-Main to Dev-Framework (//UE4/Dev-Framework) @ 3417825

	#rnx

Change 3419236 on 2017/05/02 by mason.seay

	Removed OnPressed event from Widget BP

Change 3419314 on 2017/05/02 by Marc.Audy

	Fix bad auto-resolve

	#rnx

Change 3419524 on 2017/05/02 by Marc.Audy

	PR #3528: Improved Input BP library node display names (Contributed by projectgheist)
	#jira UE-44587
	#rn Improved Input BP library node display names

Change 3419570 on 2017/05/02 by Zak.Middleton

	#ue4 - Fix typo in TFunctionRef comment/example.

Change 3419709 on 2017/05/02 by Dan.Oconnor

	Fix missing category metadata on SkeletonGeneratedClass when using compilation manager

Change 3419756 on 2017/05/02 by Dan.Oconnor

	Remove unintentional verbosity increase

Change 3420875 on 2017/05/03 by Marc.Audy

	Make IsExecPin static
	Minor optimization to IsMetaPin

	#rnx

Change 3420981 on 2017/05/03 by Marc.Audy

	Change tagging temporarily until other changes are done so that we don't have warnings in the meantime

	#rnx

Change 3421367 on 2017/05/03 by Marc.Audy

	Manually introduce changes from CL# 3398673 in 4.16 that failed to make it to Dev-Framework as a result of the integration submitted as CL# 3401725.

	#rnx

Change 3421685 on 2017/05/03 by Ben.Zeigler

	#jira UE-23001 Convert literal Asset ID/Class ID pins to store path as string instead of as hard object reference. Old pins are fixed on load, after resaving the hard references will go away
	Refactor the way that FStringAssetReference and FAssetPtr are serialized, it now does the various fixups in FStringAssetReference::SerializePath, which is called from archivers
	Change it so the asset registry reads in a list of all scanned redirectors and adds them to GRedirectCollector, this means that saving a string asset reference will automatically fix it up to point to the redirector destination
	Change the default behavior of FAssetPtr serialize on ArchiveUObject to match what most of it's children want, and remove several special case hacks. It now serializes as asset reference when saving/loading, and as object for other cases
	Deprecate StringAssetReferenceLoaded/StringAssetReferenceSaving delegates, replace with PreSavePath and PostLoadPath on FStringAssetReference
	Make AssetLongPathname private on FStringAssetReference, it was deprecated in 4.9

Change 3421728 on 2017/05/03 by Phillip.Kavan

	Mirror CL 3408285 from //UE4/Release-4.16.

	#jira UE-44124

	#rnx

Change 3422370 on 2017/05/03 by Dan.Oconnor

	Mirror 3422359

	Implement UBlueprintGeneratedClass::NeedsLoadForEditorGame to match UBlueprint, also tag a class's CDO as NeedsLoadForEditorGame.

	This prevents us from failing to load a UBlueprint's GeneratedClass when running the editor with -server.

	#jira UE-44659

Change 3423192 on 2017/05/04 by Ben.Zeigler

	CIS Fix

Change 3423305 on 2017/05/04 by Ben.Zeigler

	Fix "Missing opening parenthesis" warnings for Vector and Rotator the same way they were fixed for Transform

Change 3423358 on 2017/05/04 by Marc.Audy

	Merging //UE4/Dev-Main to Dev-Framework (//UE4/Dev-Framework) @ 3422809

	#rnx

Change 3423766 on 2017/05/04 by Ben.Zeigler

	#jira UE-44680 Delete some corrupted redirectors that are no longer in use

Change 3423804 on 2017/05/04 by Dan.Oconnor

	Honor SaveIntermediateCompilerResults when using compilation manager

Change 3424010 on 2017/05/04 by Marc.Audy

	Validate that switch string cases are unique

Change 3424011 on 2017/05/04 by Marc.Audy

	Re-fix switch node default pin not appearing as an exec output
	Remove unused boolean

Change 3424071 on 2017/05/04 by Ben.Zeigler

	Delete FixupRedirects commandlet, replace with -FixupRedirects/FixupRedirectors option on ResavePackages. This new method is much faster than the old commandlet as it uses the asset registry vs loading all packages, fixing up all redirectors in Fortnite only took about an hour vs 12+ hours the old way
	Removed some hacky bits in Core that only existed to support FixupRedirects
	Change it so the AssetRegistry listens to DirectoryWatcher callbacks in commandlets now that commandlets use the asset registry properly. This won't do anything unless you tick directory watcher the way that ResavePackages does

Change 3424313 on 2017/05/04 by Dan.Oconnor

	Address missing property flags on SkeletonGeneratedClass when using compilation manager
	#jira UE-44705

Change 3424325 on 2017/05/04 by Phillip.Kavan

	#jira UE-44222 - Move nativized UDS implementation details into its own .cpp file in order to avoid circular dependencies.

	Change summary:
	- Modified IKismetCompilerInterface::GenerateCppCodeForStruct() to include an output parameter for CPP source and modified FKismet2CompilerModule to match the updated API.
	- Modified IBlueprintCompilerCppBackend::GenerateCodeFromStruct() to include an output parameter for CPP source and modified FBlueprintCompilerCppBackendBase to match the updated API.
	- Modified FBlueprintNativeCodeGenUtils::GenerateCppCode() to adjust the call to GenerateCppCodeForStruct() to include CPP source output.
	- Modified FGatherConvertedClassDependencies::DependenciesForHeader() to switch UDS property dependencies to be forward declarations rather than includes (for default value init code).
	- Modified FEmitDefaultValueHelper::GenerateGetDefaultValue() to emit implementation details to the 'Body' container, and adjust the header content to be a declaration only.
	- Modified FIncludeHeaderHelper::EmitInner() to exclude a potentially-redundant line for the module's .h file, for the case when the caller has included the base filename in the 'AlreadyIncluded' set.
	- Modified FEmitterLocalContext::FindGloballyMappedObject() to limit the 'TryUsedAssetsList' path to UClass conversions only (since that requires a UDynamicClass target to work).
	- Modified FGatherConvertedClassDependencies::DependenciesForHeader() to only include BPGC fields if they are also being converted. Eliminates an issue with missing header files in generated code.

Change 3424359 on 2017/05/04 by Ben.Zeigler

	Fix issue where StreamableManager would break when requesting an async load that failed the first time. Because our game supports downloading assets during gameplay it's not safe to assume it will never load again.
	Port of CL #3424159

Change 3424367 on 2017/05/04 by Ben.Zeigler

	Fix some asset manager warnings to not go off in invalid cases

Change 3425270 on 2017/05/05 by Marc.Audy

	Pack booleans/enums in UEdGraphNode and FOptionalPinFromProperty

	#rnx

Change 3425696 on 2017/05/05 by Ben.Zeigler

	#jira UE-44672 Fix it so select node option pins get populated with default values properly
	#jira UE-43927 Fix it so select node opion pin type is correctly maintained accross node recreation, as opposed to deriving from the attached pins
	#jira UE-44675 Fix it to correctly refresh select node when switching from bool to integer index

Change 3425833 on 2017/05/05 by Ben.Zeigler

	#jira UE-31749 Fix it so Undo works properly when modifying a local variable
	#jira UE-44736 Fix it so changing the type of a local variable correctly resets the default value

Change 3425890 on 2017/05/05 by Marc.Audy

	Fix Copy/Paste of child actor components losing the template
	#jira UE-44566

Change 3425947 on 2017/05/05 by Ben.Zeigler

	This was meant to be part of last checkin

Change 3425959 on 2017/05/05 by Ben.Zeigler

	#jira UE-44692 Fix it so only the sequentially last node can be removed from a Switch On Int, and for Switch On Name stop it from removing an exec pin if it's the only non-default one

Change 3425979 on 2017/05/05 by Dan.Oconnor

	PVS fix

Change 3425985 on 2017/05/05 by Phillip.Kavan

	Fix an uninitialized variable.

	#rnx

Change 3426043 on 2017/05/05 by Ben.Zeigler

	#jira UE-35583 Correctly refresh array node UI when connecting pins that change it away from wildcard

Change 3426174 on 2017/05/05 by Zak.Middleton

	#ue4 - Avoid call to virtual getSimulationFilterData() to only use it when needed in PreFilter if we actually have items in the IgnoreComponents list (which is rare). The sim filter data 'word2' stores the component ID.

Change 3426621 on 2017/05/05 by Phillip.Kavan

	#jira UE-44708 - Fix an issue that re-introduced component data loss in a non-nativized child Blueprint class with a nativized parent Blueprint class.

	Change summary:
	- Removed an unnecessary additional check I had for the presence of "-NativizeAssets" switch on the command line in UBlueprint::BeginCacheForCookedPlatformData(). This check was failing because the usage was recently changed to include an optional value. It was not needed anyway so I just removed it.

	#rnx

Change 3426906 on 2017/05/05 by Ben.Zeigler

	#jira UE-11189 Fix function/macro input default values to show as a pin customization instead of as a broken text box that doesn't work correctly for most types. This fixes enums and provide validation for other types
	Types that don't have a customization (most structs) will now show any more, they did not work before either
	#jira UE-21754 Hide function default values if pass by reference is set
	Fix it so changing input parameter will also reset default value, to avoid having the wrong type value set and to work the same as local variables

Change 3426941 on 2017/05/05 by Dan.Oconnor

	Fix determinstic cooking of LoadAssetClass nodes in macros

Change 3427021 on 2017/05/05 by Dan.Oconnor

	Build fix, make initialization order in source match artifact

	#rnx

Change 3427135 on 2017/05/05 by Phillip.Kavan

	#jira UE-44702 - Restore code-based interface classes to Blueprint editor UI.

	Change summary:
	- Partially backed out CL# 3348513 to return to previous behavior for 4.16. The UI is no longer filtering on the __is_abstract() type trait for interface classes.
	- Modified FNativeClassHeaderGenerator::ExportClassFromSourceFileInner() to emit the _getUObject() declaration for native interface types as a default implementation that returns NULL rather than as a pure virtual declaration.

	#rnx

Change 3427144 on 2017/05/06 by Marc.Audy

	Fix init order

	#rnx

Change 3427146 on 2017/05/06 by Marc.Audy

	remove stray semicolon

	#rnx

Change 3427242 on 2017/05/06 by Phillip.Kavan

	#jira UE-44744 - Fix a regression in which a UMG Widget Blueprint property not explicitly marked as a variable would cause Blueprint nativization to fail at package time.

	Change summary:
	- Modified FWidgetBlueprintCompiler::CreateClassVariablesFromBlueprint() to only add 'Category' metadata when we set the 'CPF_BlueprintVisible' flag on the UProperty, which in is now tied to whether or not the property has been explcitly marked as a variable. This avoids a UHT warning when compiling the nativized codegen that would cause packaging to fail.

	#rnx

Change 3427720 on 2017/05/08 by Dan.Oconnor

	Backing out 3419202

	#rnx

Change 3427725 on 2017/05/08 by Dan.Oconnor

	SA fix

	#rnx

Change 3427734 on 2017/05/08 by Dan.Oconnor

	More exhaustive GEditor null checks, to appease SA

	#rnx

Change 3427882 on 2017/05/08 by Marc.Audy

	Properly order all booleans in intialization

	#rnx

Change 3428049 on 2017/05/08 by Marc.Audy

	Merging //UE4/Dev-Main to Dev-Framework (//UE4/Dev-Framework) @ 3427804

	#rnx

Change 3428523 on 2017/05/08 by Ben.Zeigler

	#jira UE-44781 Refresh function input UI when blueprint graph refreshes, needed as pins may have gone away

Change 3428563 on 2017/05/08 by Ben.Zeigler

	#jira UE-44783 If setting a hard reference pin type from a string, load the referenced object.

Change 3428595 on 2017/05/08 by Dan.Oconnor

	Avoid node reconstruction when we're compiling a blueprint with no linker (e.g., a duplicated blueprint)

	#jira UE-44777

Change 3428599 on 2017/05/08 by Ben.Zeigler

	#jira UE-44789 Fix string asset renamer to not mark IsPersistent becuase that crashes in lightmap code, change it so the path fixup doesn't require the persistent flag

Change 3428609 on 2017/05/08 by Dan.Oconnor

	Improved fix for UE-44777

	#jira UE-44777
	#rnx

Change 3429176 on 2017/05/08 by Phillip.Kavan

	#jira UE-44755 - Fix nativization build errors when packaging a game project that is not IWYU-compliant for a build target that disables PCH files.

	- Mirrored from //UE4/Release-4.16 (CL# 3429030).

	#rnx

Change 3429198 on 2017/05/08 by Phillip.Kavan

	CIS fix.

	#rnx

Change 3429583 on 2017/05/08 by Ben.Zeigler

	Fix SGraphPinClass to work properly after my changes to allow unloaded assets. For Class pins we need to store separate Runtime and Editor asset data objects, as one has _C and refers to the class, and the other doesn't and refers to the blueprint. The content browser wants the editor path, the pin defaults want the runtime path.
	Change default value widgets to look more like properties widgets by forcing them to act as highlighted and disabling black background

Change 3429640 on 2017/05/08 by Marc.Audy

	Fix issues with select nodes in macros connected to wildcard pins.
	#jira UE-44799

	#rnx

Change 3429890 on 2017/05/08 by Ben.Zeigler

	Fix function/macro defaults to properly propagate when changed using the new edit UI
	Refactor some code out of the details customization into the k2 schema
	Disable defaults UI for object/class/interface hard references as it is disabled in KismetCompiler

Change 3429947 on 2017/05/08 by Michael.Noland

	Core: Backing out CL# 3394352 (marking FDateTime and FTimespan nonexport member Tick with UPROPERTY()), which will re-break UE-39921 but fix UE-44418
	There appears to be a more serious underlying issue with how the CDO is instanced which needs to be addressed
	#jira UE-44418

	#reimplementing 3411681 from Release 4.16

Change 3429987 on 2017/05/08 by Ben.Zeigler

	#jira UE-44798 Do a better job of validating object paths saved as default values, due to an old bug with local variables some object paths are saved as struct exportext
	At load time clear invalid default value for local variables
	Add IsValidObjectPath to FPackageName that validates the passed in path would be valid to load with LoadObject

Change 3430392 on 2017/05/09 by Marc.Audy

	Fix SA CIS error

	#rnx

Change 3430747 on 2017/05/09 by Ben.Zeigler

	#jira UE-44836 Don't reconstruct node during callback for param value changing, this can happen during a reconstruction and recursive reconstruction is unsafe
	Don't call ModifyUserDefinedPinDefaultValue unless the default value has actually changed

Change 3431027 on 2017/05/09 by Marc.Audy

	Fix BPRW mark up causing Ocean warnings

	#rnx

Change 3431353 on 2017/05/09 by Marc.Audy

	Fix UHT error due to exposing FJsonObjectWrapper to blueprints

	#rnx

[CL 3431398 by Marc Audy in Main branch]
2017-05-09 17:15:32 -04:00

8257 lines
282 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "MeshUtilities.h"
#include "MeshUtilitiesPrivate.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopeLock.h"
#include "Containers/Ticker.h"
#include "Misc/FeedbackContext.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/ConfigCacheIni.h"
#include "Modules/ModuleManager.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "Textures/SlateIcon.h"
#include "Styling/SlateTypes.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Components/MeshComponent.h"
#include "RawIndexBuffer.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Materials/Material.h"
#include "RawMesh.h"
#include "StaticMeshResources.h"
#include "MeshBuild.h"
#include "NvTriStrip.h"
#include "forsythtriangleorderoptimizer.h"
#include "nvtess.h"
#include "SkeletalMeshTools.h"
#include "Engine/SkeletalMesh.h"
#include "Components/SkinnedMeshComponent.h"
#include "ImageUtils.h"
#include "LayoutUV.h"
#include "mikktspace.h"
#include "Misc/FbxErrors.h"
#include "Components/SplineMeshComponent.h"
#include "PhysicsEngine/ConvexElem.h"
#include "PhysicsEngine/AggregateGeom.h"
#include "PhysicsEngine/BodySetup.h"
#include "MaterialUtilities.h"
#include "IHierarchicalLODUtilities.h"
#include "HierarchicalLODUtilitiesModule.h"
#include "MeshBoneReduction.h"
#include "MeshMergeData.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "GPUSkinVertexFactory.h"
#include "Developer/AssetTools/Public/IAssetTools.h"
#include "Developer/AssetTools/Public/AssetToolsModule.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SComboButton.h"
#include "LandscapeProxy.h"
#include "Landscape.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "Engine/MeshMergeCullingVolume.h"
#include "ProxyMaterialUtilities.h"
#include "Toolkits/AssetEditorManager.h"
#include "LevelEditor.h"
#include "IAnimationBlueprintEditor.h"
#include "IAnimationBlueprintEditorModule.h"
#include "IAnimationEditor.h"
#include "IAnimationEditorModule.h"
#include "ISkeletalMeshEditor.h"
#include "ISkeletalMeshEditorModule.h"
#include "ISkeletonEditor.h"
#include "ISkeletonEditorModule.h"
#include "IPersonaToolkit.h"
#include "Dialogs/DlgPickAssetPath.h"
#include "SkeletalRenderPublic.h"
#include "AssetRegistryModule.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Engine/MeshSimplificationSettings.h"
#include "IDetailCustomization.h"
#include "EditorStyleSet.h"
#include "PropertyEditorModule.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "IDetailPropertyRow.h"
#include "DetailWidgetRow.h"
#if WITH_EDITOR
#include "Editor.h"
#include "UnrealEdMisc.h"
#endif
DEFINE_LOG_CATEGORY(LogMeshUtilities);
/*------------------------------------------------------------------------------
MeshUtilities module.
------------------------------------------------------------------------------*/
// The version string is a GUID. If you make a change to mesh utilities that
// causes meshes to be rebuilt you MUST generate a new GUID and replace this
// string with it.
#define MESH_UTILITIES_VER TEXT("228332BAE0224DD294E232B87D83948F")
#define LOCTEXT_NAMESPACE "MeshUtils"
// CVars
static TAutoConsoleVariable<int32> CVarTriangleOrderOptimization(
TEXT("r.TriangleOrderOptimization"),
1,
TEXT("Controls the algorithm to use when optimizing the triangle order for the post-transform cache.\n")
TEXT("0: Use NVTriStrip (slower)\n")
TEXT("1: Use Forsyth algorithm (fastest)(default)")
TEXT("2: No triangle order optimization. (least efficient, debugging purposes only)"),
ECVF_Default);
static FAutoConsoleVariable CVarMeshReductionModule(
TEXT("r.MeshReductionModule"),
TEXT("QuadricMeshReduction"),
TEXT("Name of what mesh reduction module to choose. If blank it chooses any that exist.\n"),
ECVF_ReadOnly);
void FMeshUtilities::UpdateMeshReductionModule()
{
TArray<FName> ModuleNames;
FModuleManager::Get().FindModules(TEXT("*MeshReduction"), ModuleNames);
for(int32 Index = 0; Index < ModuleNames.Num(); Index++)
{
FString String = CVarMeshReductionModule->GetString();
bool bIsChoosenModule = ModuleNames[Index].GetPlainNameString().Equals(String);
IMeshReductionModule& MeshReductionModule = FModuleManager::LoadModuleChecked<IMeshReductionModule>(ModuleNames[Index]);
// Look for MeshReduction interface
if(MeshReductionModule.GetStaticMeshReductionInterface())
{
if(bIsChoosenModule || StaticMeshReduction == NULL)
{
StaticMeshReduction = MeshReductionModule.GetStaticMeshReductionInterface();
UE_LOG(LogMeshUtilities, Log, TEXT("Using %s for automatic static mesh reduction"), *ModuleNames[Index].ToString());
}
}
// Look for MeshReduction interface
if(MeshReductionModule.GetSkeletalMeshReductionInterface())
{
if(bIsChoosenModule || SkeletalMeshReduction == NULL)
{
SkeletalMeshReduction = MeshReductionModule.GetSkeletalMeshReductionInterface();
UE_LOG(LogMeshUtilities, Log, TEXT("Using %s for automatic skeletal mesh reduction"), *ModuleNames[Index].ToString());
}
}
// Look for MeshMerging interface
if(MeshReductionModule.GetMeshMergingInterface())
{
if(bIsChoosenModule || MeshMerging == NULL)
{
MeshMerging = MeshReductionModule.GetMeshMergingInterface();
UE_LOG(LogMeshUtilities, Log, TEXT("Using %s for automatic mesh merging"), *ModuleNames[Index].ToString());
}
}
}
}
IMPLEMENT_MODULE(FMeshUtilities, MeshUtilities);
class FProxyGenerationProcessor : FTickerObjectBase
{
public:
FProxyGenerationProcessor()
{
#if WITH_EDITOR
FEditorDelegates::MapChange.AddRaw(this, &FProxyGenerationProcessor::OnMapChange);
FEditorDelegates::NewCurrentLevel.AddRaw(this, &FProxyGenerationProcessor::OnNewCurrentLevel);
#endif // WITH_EDITOR
}
~FProxyGenerationProcessor()
{
#if WITH_EDITOR
FEditorDelegates::MapChange.RemoveAll(this);
FEditorDelegates::NewCurrentLevel.RemoveAll(this);
#endif // WITH_EDITOR
}
void AddProxyJob(FGuid InJobGuid, FMergeCompleteData* InCompleteData)
{
FScopeLock Lock(&StateLock);
ProxyMeshJobs.Add(InJobGuid, InCompleteData);
}
virtual bool Tick(float DeltaTime) override
{
FScopeLock Lock(&StateLock);
for (const auto& Entry : ToProcessJobDataMap)
{
FGuid JobGuid = Entry.Key;
FProxyGenerationData* Data = Entry.Value;
// Process the job
ProcessJob(JobGuid, Data);
// Data retrieved so can now remove the job from the map
ProxyMeshJobs.Remove(JobGuid);
delete Data->MergeData;
delete Data;
}
ToProcessJobDataMap.Reset();
return true;
}
void ProxyGenerationComplete(FRawMesh& OutProxyMesh, struct FFlattenMaterial& OutMaterial, const FGuid OutJobGUID)
{
FScopeLock Lock(&StateLock);
FMergeCompleteData** FindData = ProxyMeshJobs.Find(OutJobGUID);
if (FindData && *FindData)
{
FMergeCompleteData* Data = *FindData;
FProxyGenerationData* GenerationData = new FProxyGenerationData();
GenerationData->Material = OutMaterial;
GenerationData->RawMesh = OutProxyMesh;
GenerationData->MergeData = Data;
ToProcessJobDataMap.Add(OutJobGUID, GenerationData);
}
}
//@third party BEGIN SIMPLYGON
void ProxyGenerationFailed(const FGuid OutJobGUID, const FString& ErrorMessage)
{
FScopeLock Lock(&StateLock);
FMergeCompleteData** FindData = ProxyMeshJobs.Find(OutJobGUID);
if (FindData && *FindData)
{
UE_LOG(LogMeshUtilities, Log, TEXT("Failed to generate proxy mesh for cluster %s, %s"), *(*FindData)->ProxyBasePackageName, *ErrorMessage);
ProxyMeshJobs.Remove(OutJobGUID);
}
}
//@third party END SIMPLYGON
protected:
/** Called when the map has changed*/
void OnMapChange(uint32 MapFlags)
{
ClearProcessingData();
}
/** Called when the current level has changed */
void OnNewCurrentLevel()
{
ClearProcessingData();
}
/** Clears the processing data array/map */
void ClearProcessingData()
{
FScopeLock Lock(&StateLock);
ProxyMeshJobs.Empty();
ToProcessJobDataMap.Empty();
}
protected:
/** Structure storing the data required during processing */
struct FProxyGenerationData
{
FRawMesh RawMesh;
FFlattenMaterial Material;
FMergeCompleteData* MergeData;
};
void ProcessJob(const FGuid& JobGuid, FProxyGenerationData* Data)
{
TArray<UObject*> OutAssetsToSync;
const FString AssetBaseName = FPackageName::GetShortName(Data->MergeData->ProxyBasePackageName);
const FString AssetBasePath = Data->MergeData->InOuter ? TEXT("") : FPackageName::GetLongPackagePath(Data->MergeData->ProxyBasePackageName) + TEXT("/");
// Retrieve flattened material data
FFlattenMaterial& FlattenMaterial = Data->Material;
// Resize flattened material
FMaterialUtilities::ResizeFlattenMaterial(FlattenMaterial, Data->MergeData->InProxySettings);
// Optimize flattened material
FMaterialUtilities::OptimizeFlattenMaterial(FlattenMaterial);
// Create a new proxy material instance
UMaterialInstanceConstant* ProxyMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(Data->MergeData->InOuter, Data->MergeData->InProxySettings.MaterialSettings, FlattenMaterial, AssetBasePath, AssetBaseName, OutAssetsToSync);
// Set material static lighting usage flag if project has static lighting enabled
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
if (bAllowStaticLighting)
{
ProxyMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
}
// Construct proxy static mesh
UPackage* MeshPackage = Data->MergeData->InOuter;
FString MeshAssetName = TEXT("SM_") + AssetBaseName;
if (MeshPackage == nullptr)
{
MeshPackage = CreatePackage(NULL, *(AssetBasePath + MeshAssetName));
MeshPackage->FullyLoad();
MeshPackage->Modify();
}
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(MeshPackage, FName(*MeshAssetName), RF_Public | RF_Standalone);
StaticMesh->InitResources();
FString OutputPath = StaticMesh->GetPathName();
// make sure it has a new lighting guid
StaticMesh->LightingGuid = FGuid::NewGuid();
// Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc).
StaticMesh->LightMapResolution = Data->MergeData->InProxySettings.LightMapResolution;
StaticMesh->LightMapCoordinateIndex = 1;
FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
/*Don't allow the engine to recalculate normals*/
SrcModel->BuildSettings.bRecomputeNormals = false;
SrcModel->BuildSettings.bRecomputeTangents = false;
SrcModel->BuildSettings.bRemoveDegenerates = true;
SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false;
SrcModel->BuildSettings.bUseFullPrecisionUVs = false;
SrcModel->RawMeshBulkData->SaveRawMesh(Data->RawMesh);
//Assign the proxy material to the static mesh
StaticMesh->StaticMaterials.Add(FStaticMaterial(ProxyMaterial));
//Set the Imported version before calling the build
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
StaticMesh->Build();
StaticMesh->PostEditChange();
OutAssetsToSync.Add(StaticMesh);
// Execute the delegate received from the user
Data->MergeData->CallbackDelegate.ExecuteIfBound(JobGuid, OutAssetsToSync);
}
protected:
/** Holds Proxy mesh job data together with the job Guid */
TMap<FGuid, FMergeCompleteData*> ProxyMeshJobs;
/** Holds Proxy generation data together with the job Guid */
TMap<FGuid, FProxyGenerationData*> ToProcessJobDataMap;
/** Critical section to keep ProxyMeshJobs/ToProcessJobDataMap access thread-safe */
FCriticalSection StateLock;
};
/*------------------------------------------------------------------------------
NVTriStrip for cache optimizing index buffers.
------------------------------------------------------------------------------*/
namespace NvTriStrip
{
/**
* Converts 16 bit indices to 32 bit prior to passing them into the real GenerateStrips util method
*/
void GenerateStrips(
const uint8* Indices,
bool Is32Bit,
const uint32 NumIndices,
PrimitiveGroup** PrimGroups,
uint32* NumGroups
)
{
if (Is32Bit)
{
GenerateStrips((uint32*)Indices, NumIndices, PrimGroups, NumGroups);
}
else
{
// convert to 32 bit
uint32 Idx;
TArray<uint32> NewIndices;
NewIndices.AddUninitialized(NumIndices);
for (Idx = 0; Idx < NumIndices; ++Idx)
{
NewIndices[Idx] = ((uint16*)Indices)[Idx];
}
GenerateStrips(NewIndices.GetData(), NumIndices, PrimGroups, NumGroups);
}
}
/**
* Orders a triangle list for better vertex cache coherency.
*
* *** WARNING: This is safe to call for multiple threads IF AND ONLY IF all
* threads call SetListsOnly(true) and SetCacheSize(CACHESIZE_GEFORCE3). If
* NvTriStrip is ever used with different settings the library will need
* some modifications to be thread-safe. ***
*/
template<typename IndexDataType, typename Allocator>
void CacheOptimizeIndexBuffer(TArray<IndexDataType, Allocator>& Indices)
{
static_assert(sizeof(IndexDataType) == 2 || sizeof(IndexDataType) == 4, "Indices must be short or int.");
PrimitiveGroup* PrimitiveGroups = NULL;
uint32 NumPrimitiveGroups = 0;
bool Is32Bit = sizeof(IndexDataType) == 4;
SetListsOnly(true);
SetCacheSize(CACHESIZE_GEFORCE3);
GenerateStrips((uint8*)Indices.GetData(), Is32Bit, Indices.Num(), &PrimitiveGroups, &NumPrimitiveGroups);
Indices.Empty();
Indices.AddUninitialized(PrimitiveGroups->numIndices);
if (Is32Bit)
{
FMemory::Memcpy(Indices.GetData(), PrimitiveGroups->indices, Indices.Num() * sizeof(IndexDataType));
}
else
{
for (uint32 I = 0; I < PrimitiveGroups->numIndices; ++I)
{
Indices[I] = (uint16)PrimitiveGroups->indices[I];
}
}
delete[] PrimitiveGroups;
}
}
/*------------------------------------------------------------------------------
Forsyth algorithm for cache optimizing index buffers.
------------------------------------------------------------------------------*/
namespace Forsyth
{
/**
* Converts 16 bit indices to 32 bit prior to passing them into the real OptimizeFaces util method
*/
void OptimizeFaces(
const uint8* Indices,
bool Is32Bit,
const uint32 NumIndices,
uint32 NumVertices,
uint32* OutIndices,
uint16 CacheSize
)
{
if (Is32Bit)
{
OptimizeFaces((uint32*)Indices, NumIndices, NumVertices, OutIndices, CacheSize);
}
else
{
// convert to 32 bit
uint32 Idx;
TArray<uint32> NewIndices;
NewIndices.AddUninitialized(NumIndices);
for (Idx = 0; Idx < NumIndices; ++Idx)
{
NewIndices[Idx] = ((uint16*)Indices)[Idx];
}
OptimizeFaces(NewIndices.GetData(), NumIndices, NumVertices, OutIndices, CacheSize);
}
}
/**
* Orders a triangle list for better vertex cache coherency.
*/
template<typename IndexDataType, typename Allocator>
void CacheOptimizeIndexBuffer(TArray<IndexDataType, Allocator>& Indices)
{
static_assert(sizeof(IndexDataType) == 2 || sizeof(IndexDataType) == 4, "Indices must be short or int.");
bool Is32Bit = sizeof(IndexDataType) == 4;
// Count the number of vertices
uint32 NumVertices = 0;
for (int32 Index = 0; Index < Indices.Num(); ++Index)
{
if (Indices[Index] > NumVertices)
{
NumVertices = Indices[Index];
}
}
NumVertices += 1;
TArray<uint32> OptimizedIndices;
OptimizedIndices.AddUninitialized(Indices.Num());
uint16 CacheSize = 32;
OptimizeFaces((uint8*)Indices.GetData(), Is32Bit, Indices.Num(), NumVertices, OptimizedIndices.GetData(), CacheSize);
if (Is32Bit)
{
FMemory::Memcpy(Indices.GetData(), OptimizedIndices.GetData(), Indices.Num() * sizeof(IndexDataType));
}
else
{
for (int32 I = 0; I < OptimizedIndices.Num(); ++I)
{
Indices[I] = (uint16)OptimizedIndices[I];
}
}
}
}
void FMeshUtilities::CacheOptimizeIndexBuffer(TArray<uint16>& Indices)
{
if (bUsingNvTriStrip)
{
NvTriStrip::CacheOptimizeIndexBuffer(Indices);
}
else if (!bDisableTriangleOrderOptimization)
{
Forsyth::CacheOptimizeIndexBuffer(Indices);
}
}
void FMeshUtilities::CacheOptimizeIndexBuffer(TArray<uint32>& Indices)
{
if (bUsingNvTriStrip)
{
NvTriStrip::CacheOptimizeIndexBuffer(Indices);
}
else if (!bDisableTriangleOrderOptimization)
{
Forsyth::CacheOptimizeIndexBuffer(Indices);
}
}
/*------------------------------------------------------------------------------
NVTessLib for computing adjacency used for tessellation.
------------------------------------------------------------------------------*/
/**
* Provides static mesh render data to the NVIDIA tessellation library.
*/
class FStaticMeshNvRenderBuffer : public nv::RenderBuffer
{
public:
/** Construct from static mesh render buffers. */
FStaticMeshNvRenderBuffer(
const FPositionVertexBuffer& InPositionVertexBuffer,
const FStaticMeshVertexBuffer& InVertexBuffer,
const TArray<uint32>& Indices)
: PositionVertexBuffer(InPositionVertexBuffer)
, VertexBuffer(InVertexBuffer)
{
check(PositionVertexBuffer.GetNumVertices() == VertexBuffer.GetNumVertices());
mIb = new nv::IndexBuffer((void*)Indices.GetData(), nv::IBT_U32, Indices.Num(), false);
}
/** Retrieve the position and first texture coordinate of the specified index. */
virtual nv::Vertex getVertex(unsigned int Index) const
{
nv::Vertex Vertex;
check(Index < PositionVertexBuffer.GetNumVertices());
const FVector& Position = PositionVertexBuffer.VertexPosition(Index);
Vertex.pos.x = Position.X;
Vertex.pos.y = Position.Y;
Vertex.pos.z = Position.Z;
if (VertexBuffer.GetNumTexCoords())
{
const FVector2D UV = VertexBuffer.GetVertexUV(Index, 0);
Vertex.uv.x = UV.X;
Vertex.uv.y = UV.Y;
}
else
{
Vertex.uv.x = 0.0f;
Vertex.uv.y = 0.0f;
}
return Vertex;
}
private:
/** The position vertex buffer for the static mesh. */
const FPositionVertexBuffer& PositionVertexBuffer;
/** The vertex buffer for the static mesh. */
const FStaticMeshVertexBuffer& VertexBuffer;
/** Copying is forbidden. */
FStaticMeshNvRenderBuffer(const FStaticMeshNvRenderBuffer&);
FStaticMeshNvRenderBuffer& operator=(const FStaticMeshNvRenderBuffer&);
};
/**
* Provides skeletal mesh render data to the NVIDIA tessellation library.
*/
class FSkeletalMeshNvRenderBuffer : public nv::RenderBuffer
{
public:
/** Construct from static mesh render buffers. */
FSkeletalMeshNvRenderBuffer(
const TArray<FSoftSkinVertex>& InVertexBuffer,
const uint32 InTexCoordCount,
const TArray<uint32>& Indices)
: VertexBuffer(InVertexBuffer)
, TexCoordCount(InTexCoordCount)
{
mIb = new nv::IndexBuffer((void*)Indices.GetData(), nv::IBT_U32, Indices.Num(), false);
}
/** Retrieve the position and first texture coordinate of the specified index. */
virtual nv::Vertex getVertex(unsigned int Index) const
{
nv::Vertex Vertex;
check(Index < (unsigned int)VertexBuffer.Num());
const FSoftSkinVertex& SrcVertex = VertexBuffer[Index];
Vertex.pos.x = SrcVertex.Position.X;
Vertex.pos.y = SrcVertex.Position.Y;
Vertex.pos.z = SrcVertex.Position.Z;
if (TexCoordCount > 0)
{
Vertex.uv.x = SrcVertex.UVs[0].X;
Vertex.uv.y = SrcVertex.UVs[0].Y;
}
else
{
Vertex.uv.x = 0.0f;
Vertex.uv.y = 0.0f;
}
return Vertex;
}
private:
/** The vertex buffer for the skeletal mesh. */
const TArray<FSoftSkinVertex>& VertexBuffer;
const uint32 TexCoordCount;
/** Copying is forbidden. */
FSkeletalMeshNvRenderBuffer(const FSkeletalMeshNvRenderBuffer&);
FSkeletalMeshNvRenderBuffer& operator=(const FSkeletalMeshNvRenderBuffer&);
};
static void BuildStaticAdjacencyIndexBuffer(
const FPositionVertexBuffer& PositionVertexBuffer,
const FStaticMeshVertexBuffer& VertexBuffer,
const TArray<uint32>& Indices,
TArray<uint32>& OutPnAenIndices
)
{
if (Indices.Num())
{
FStaticMeshNvRenderBuffer StaticMeshRenderBuffer(PositionVertexBuffer, VertexBuffer, Indices);
nv::IndexBuffer* PnAENIndexBuffer = nv::tess::buildTessellationBuffer(&StaticMeshRenderBuffer, nv::DBM_PnAenDominantCorner, true);
check(PnAENIndexBuffer);
const int32 IndexCount = (int32)PnAENIndexBuffer->getLength();
OutPnAenIndices.Empty(IndexCount);
OutPnAenIndices.AddUninitialized(IndexCount);
for (int32 Index = 0; Index < IndexCount; ++Index)
{
OutPnAenIndices[Index] = (*PnAENIndexBuffer)[Index];
}
delete PnAENIndexBuffer;
}
else
{
OutPnAenIndices.Empty();
}
}
void FMeshUtilities::BuildSkeletalAdjacencyIndexBuffer(
const TArray<FSoftSkinVertex>& VertexBuffer,
const uint32 TexCoordCount,
const TArray<uint32>& Indices,
TArray<uint32>& OutPnAenIndices
)
{
if (Indices.Num())
{
FSkeletalMeshNvRenderBuffer SkeletalMeshRenderBuffer(VertexBuffer, TexCoordCount, Indices);
nv::IndexBuffer* PnAENIndexBuffer = nv::tess::buildTessellationBuffer(&SkeletalMeshRenderBuffer, nv::DBM_PnAenDominantCorner, true);
check(PnAENIndexBuffer);
const int32 IndexCount = (int32)PnAENIndexBuffer->getLength();
OutPnAenIndices.Empty(IndexCount);
OutPnAenIndices.AddUninitialized(IndexCount);
for (int32 Index = 0; Index < IndexCount; ++Index)
{
OutPnAenIndices[Index] = (*PnAENIndexBuffer)[Index];
}
delete PnAENIndexBuffer;
}
else
{
OutPnAenIndices.Empty();
}
}
void FMeshUtilities::RechunkSkeletalMeshModels(USkeletalMesh* SrcMesh, int32 MaxBonesPerChunk)
{
#if WITH_EDITORONLY_DATA
TIndirectArray<FStaticLODModel> DestModels;
TIndirectArray<FSkinnedModelData> ModelData;
FReferenceSkeleton RefSkeleton = SrcMesh->RefSkeleton;
uint32 VertexBufferBuildFlags = SrcMesh->GetVertexBufferFlags();
FSkeletalMeshResource* SrcMeshResource = SrcMesh->GetImportedResource();
FVector TriangleSortCenter;
bool bHaveTriangleSortCenter = SrcMesh->GetSortCenterPoint(TriangleSortCenter);
for (int32 ModelIndex = 0; ModelIndex < SrcMeshResource->LODModels.Num(); ++ModelIndex)
{
FSkinnedModelData& TmpModelData = *new(ModelData)FSkinnedModelData();
SkeletalMeshTools::CopySkinnedModelData(TmpModelData, SrcMeshResource->LODModels[ModelIndex]);
}
for (int32 ModelIndex = 0; ModelIndex < ModelData.Num(); ++ModelIndex)
{
TArray<FSkinnedMeshChunk*> Chunks;
TArray<int32> PointToOriginalMap;
TArray<ETriangleSortOption> SectionSortOptions;
const FSkinnedModelData& SrcModel = ModelData[ModelIndex];
FStaticLODModel& DestModel = *new(DestModels)FStaticLODModel();
SkeletalMeshTools::UnchunkSkeletalModel(Chunks, PointToOriginalMap, SrcModel);
SkeletalMeshTools::ChunkSkinnedVertices(Chunks, MaxBonesPerChunk);
for (int32 ChunkIndex = 0; ChunkIndex < Chunks.Num(); ++ChunkIndex)
{
int32 SectionIndex = Chunks[ChunkIndex]->OriginalSectionIndex;
SectionSortOptions.Add(SrcModel.Sections[SectionIndex].TriangleSorting);
}
check(SectionSortOptions.Num() == Chunks.Num());
BuildSkeletalModelFromChunks(DestModel, RefSkeleton, Chunks, PointToOriginalMap);
check(DestModel.Sections.Num() == SectionSortOptions.Num());
DestModel.NumTexCoords = SrcModel.NumTexCoords;
DestModel.BuildVertexBuffers(VertexBufferBuildFlags);
for (int32 SectionIndex = 0; SectionIndex < DestModel.Sections.Num(); ++SectionIndex)
{
DestModel.SortTriangles(TriangleSortCenter, bHaveTriangleSortCenter, SectionIndex, SectionSortOptions[SectionIndex]);
}
}
//@todo-rco: Swap() doesn't seem to work
Exchange(SrcMeshResource->LODModels, DestModels);
// TODO: Also need to patch bEnableShadowCasting in the LODInfo struct.
#endif // #if WITH_EDITORONLY_DATA
}
void FMeshUtilities::CalcBoneVertInfos(USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo>& Infos, bool bOnlyDominant)
{
SkeletalMeshTools::CalcBoneVertInfos(SkeletalMesh, Infos, bOnlyDominant);
}
// Helper function for ConvertMeshesToStaticMesh
static void AddOrDuplicateMaterial(UMaterialInterface* InMaterialInterface, const FString& InPackageName, TArray<UMaterialInterface*>& OutMaterials)
{
if (InMaterialInterface && !InMaterialInterface->GetOuter()->IsA<UPackage>())
{
// Convert runtime material instances to new concrete material instances
// Create new package
FString OriginalMaterialName = InMaterialInterface->GetName();
FString MaterialPath = FPackageName::GetLongPackagePath(InPackageName) / OriginalMaterialName;
FString MaterialName;
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(MaterialPath, TEXT(""), MaterialPath, MaterialName);
UPackage* MaterialPackage = CreatePackage(NULL, *MaterialPath);
// Duplicate the object into the new package
UMaterialInterface* NewMaterialInterface = DuplicateObject<UMaterialInterface>(InMaterialInterface, MaterialPackage, *MaterialName);
NewMaterialInterface->SetFlags(RF_Public | RF_Standalone);
if (UMaterialInstanceDynamic* MaterialInstanceDynamic = Cast<UMaterialInstanceDynamic>(NewMaterialInterface))
{
UMaterialInstanceDynamic* OldMaterialInstanceDynamic = CastChecked<UMaterialInstanceDynamic>(InMaterialInterface);
MaterialInstanceDynamic->K2_CopyMaterialInstanceParameters(OldMaterialInstanceDynamic);
}
NewMaterialInterface->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewMaterialInterface);
InMaterialInterface = NewMaterialInterface;
}
OutMaterials.Add(InMaterialInterface);
}
// Helper function for ConvertMeshesToStaticMesh
template <typename ComponentType>
static void ProcessMaterials(ComponentType* InComponent, const FString& InPackageName, TArray<UMaterialInterface*>& OutMaterials)
{
const int32 NumMaterials = InComponent->GetNumMaterials();
for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; MaterialIndex++)
{
UMaterialInterface* MaterialInterface = InComponent->GetMaterial(MaterialIndex);
AddOrDuplicateMaterial(MaterialInterface, InPackageName, OutMaterials);
}
}
// Helper function for ConvertMeshesToStaticMesh
static bool IsValidSkinnedMeshComponent(USkinnedMeshComponent* InComponent)
{
return InComponent && InComponent->MeshObject && InComponent->IsVisible();
}
/** Helper struct for tracking validity of optional buffers */
struct FRawMeshTracker
{
FRawMeshTracker()
: bValidColors(false)
{
FMemory::Memset(bValidTexCoords, 0);
}
bool bValidTexCoords[MAX_MESH_TEXTURE_COORDS];
bool bValidColors;
};
// Helper function for ConvertMeshesToStaticMesh
static void SkinnedMeshToRawMeshes(USkinnedMeshComponent* InSkinnedMeshComponent, int32 InOverallMaxLODs, const FMatrix& InComponentToWorld, const FString& InPackageName, TArray<FRawMeshTracker>& OutRawMeshTrackers, TArray<FRawMesh>& OutRawMeshes, TArray<UMaterialInterface*>& OutMaterials)
{
const int32 BaseMaterialIndex = OutMaterials.Num();
// Export all LODs to raw meshes
const int32 NumLODs = InSkinnedMeshComponent->MeshObject->GetSkeletalMeshResource().LODModels.Num();
for (int32 OverallLODIndex = 0; OverallLODIndex < InOverallMaxLODs; OverallLODIndex++)
{
int32 LODIndexRead = FMath::Min(OverallLODIndex, NumLODs - 1);
FRawMesh& RawMesh = OutRawMeshes[OverallLODIndex];
FRawMeshTracker& RawMeshTracker = OutRawMeshTrackers[OverallLODIndex];
const int32 BaseVertexIndex = RawMesh.VertexPositions.Num();
FSkeletalMeshLODInfo& SrcLODInfo = InSkinnedMeshComponent->SkeletalMesh->LODInfo[LODIndexRead];
// Get the CPU skinned verts for this LOD
TArray<FFinalSkinVertex> FinalVertices;
InSkinnedMeshComponent->GetCPUSkinnedVertices(FinalVertices, LODIndexRead);
FSkeletalMeshResource& SkeletalMeshResource = InSkinnedMeshComponent->MeshObject->GetSkeletalMeshResource();
FStaticLODModel& StaticLODModel = SkeletalMeshResource.LODModels[LODIndexRead];
// Copy skinned vertex positions
for (int32 VertIndex = 0; VertIndex < FinalVertices.Num(); ++VertIndex)
{
RawMesh.VertexPositions.Add(InComponentToWorld.TransformPosition(FinalVertices[VertIndex].Position));
}
const uint32 NumTexCoords = FMath::Min(StaticLODModel.VertexBufferGPUSkin.GetNumTexCoords(), (uint32)MAX_MESH_TEXTURE_COORDS);
const int32 NumSections = StaticLODModel.Sections.Num();
FRawStaticIndexBuffer16or32Interface& IndexBuffer = *StaticLODModel.MultiSizeIndexContainer.GetIndexBuffer();
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
const FSkelMeshSection& SkelMeshSection = StaticLODModel.Sections[SectionIndex];
if (!SkelMeshSection.bDisabled)
{
// Build 'wedge' info
const int32 NumWedges = SkelMeshSection.NumTriangles * 3;
for(int32 WedgeIndex = 0; WedgeIndex < NumWedges; WedgeIndex++)
{
const int32 VertexIndexForWedge = IndexBuffer.Get(SkelMeshSection.BaseIndex + WedgeIndex);
RawMesh.WedgeIndices.Add(BaseVertexIndex + VertexIndexForWedge);
const FFinalSkinVertex& SkinnedVertex = FinalVertices[VertexIndexForWedge];
const FVector TangentX = InComponentToWorld.TransformVector(SkinnedVertex.TangentX);
const FVector TangentZ = InComponentToWorld.TransformVector(SkinnedVertex.TangentZ);
const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ;
const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * UnpackedTangentZ.W;
RawMesh.WedgeTangentX.Add(TangentX);
RawMesh.WedgeTangentY.Add(TangentY);
RawMesh.WedgeTangentZ.Add(TangentZ);
for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
{
if (TexCoordIndex >= NumTexCoords)
{
RawMesh.WedgeTexCoords[TexCoordIndex].AddDefaulted();
}
else
{
RawMesh.WedgeTexCoords[TexCoordIndex].Add(StaticLODModel.VertexBufferGPUSkin.GetVertexUV(VertexIndexForWedge, TexCoordIndex));
RawMeshTracker.bValidTexCoords[TexCoordIndex] = true;
}
}
if (StaticLODModel.ColorVertexBuffer.IsInitialized())
{
RawMesh.WedgeColors.Add(StaticLODModel.ColorVertexBuffer.VertexColor(VertexIndexForWedge));
RawMeshTracker.bValidColors = true;
}
else
{
RawMesh.WedgeColors.Add(FColor::White);
}
}
int32 MaterialIndex = SkelMeshSection.MaterialIndex;
// use the remapping of material indices for all LODs besides the base LOD
if (LODIndexRead > 0 && SrcLODInfo.LODMaterialMap.IsValidIndex(SkelMeshSection.MaterialIndex))
{
MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SkelMeshSection.MaterialIndex], 0, InSkinnedMeshComponent->SkeletalMesh->Materials.Num());
}
// copy face info
for (uint32 TriIndex = 0; TriIndex < SkelMeshSection.NumTriangles; TriIndex++)
{
RawMesh.FaceMaterialIndices.Add(BaseMaterialIndex + MaterialIndex);
RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
}
}
}
}
ProcessMaterials<USkinnedMeshComponent>(InSkinnedMeshComponent, InPackageName, OutMaterials);
}
// Helper function for ConvertMeshesToStaticMesh
static bool IsValidStaticMeshComponent(UStaticMeshComponent* InComponent)
{
return InComponent && InComponent->GetStaticMesh() && InComponent->GetStaticMesh()->RenderData && InComponent->IsVisible();
}
// Helper function for ConvertMeshesToStaticMesh
static void StaticMeshToRawMeshes(UStaticMeshComponent* InStaticMeshComponent, int32 InOverallMaxLODs, const FMatrix& InComponentToWorld, const FString& InPackageName, TArray<FRawMeshTracker>& OutRawMeshTrackers, TArray<FRawMesh>& OutRawMeshes, TArray<UMaterialInterface*>& OutMaterials)
{
const int32 BaseMaterialIndex = OutMaterials.Num();
const int32 NumLODs = InStaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num();
for (int32 OverallLODIndex = 0; OverallLODIndex < InOverallMaxLODs; OverallLODIndex++)
{
int32 LODIndexRead = FMath::Min(OverallLODIndex, NumLODs - 1);
FRawMesh& RawMesh = OutRawMeshes[OverallLODIndex];
FRawMeshTracker& RawMeshTracker = OutRawMeshTrackers[OverallLODIndex];
const FStaticMeshLODResources& LODResource = InStaticMeshComponent->GetStaticMesh()->RenderData->LODResources[LODIndexRead];
const int32 BaseVertexIndex = RawMesh.VertexPositions.Num();
for (int32 VertIndex = 0; VertIndex < LODResource.GetNumVertices(); ++VertIndex)
{
RawMesh.VertexPositions.Add(InComponentToWorld.TransformPosition(LODResource.PositionVertexBuffer.VertexPosition((uint32)VertIndex)));
}
const FIndexArrayView IndexArrayView = LODResource.IndexBuffer.GetArrayView();
const FStaticMeshVertexBuffer& StaticMeshVertexBuffer = LODResource.VertexBuffer;
const int32 NumTexCoords = FMath::Min(StaticMeshVertexBuffer.GetNumTexCoords(), (uint32)MAX_MESH_TEXTURE_COORDS);
const int32 NumSections = LODResource.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
const FStaticMeshSection& StaticMeshSection = LODResource.Sections[SectionIndex];
const int32 NumIndices = StaticMeshSection.NumTriangles * 3;
for (int32 IndexIndex = 0; IndexIndex < NumIndices; IndexIndex++)
{
int32 Index = IndexArrayView[StaticMeshSection.FirstIndex + IndexIndex];
RawMesh.WedgeIndices.Add(BaseVertexIndex + Index);
RawMesh.WedgeTangentX.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentX(Index)));
RawMesh.WedgeTangentY.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentY(Index)));
RawMesh.WedgeTangentZ.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentZ(Index)));
for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
{
if (TexCoordIndex >= NumTexCoords)
{
RawMesh.WedgeTexCoords[TexCoordIndex].AddDefaulted();
}
else
{
RawMesh.WedgeTexCoords[TexCoordIndex].Add(StaticMeshVertexBuffer.GetVertexUV(Index, TexCoordIndex));
RawMeshTracker.bValidTexCoords[TexCoordIndex] = true;
}
}
if (LODResource.ColorVertexBuffer.IsInitialized())
{
RawMesh.WedgeColors.Add(LODResource.ColorVertexBuffer.VertexColor(Index));
RawMeshTracker.bValidColors = true;
}
else
{
RawMesh.WedgeColors.Add(FColor::White);
}
}
// copy face info
for (uint32 TriIndex = 0; TriIndex < StaticMeshSection.NumTriangles; TriIndex++)
{
RawMesh.FaceMaterialIndices.Add(BaseMaterialIndex + StaticMeshSection.MaterialIndex);
RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
}
}
}
ProcessMaterials<UStaticMeshComponent>(InStaticMeshComponent, InPackageName, OutMaterials);
}
UStaticMesh* FMeshUtilities::ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform, const FString& InPackageName)
{
// Build a package name to use
FString MeshName;
FString PackageName;
if (InPackageName.IsEmpty())
{
FString NewNameSuggestion = FString(TEXT("StaticMesh"));
FString PackageNameSuggestion = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion;
FString Name;
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(PackageNameSuggestion, TEXT(""), PackageNameSuggestion, Name);
TSharedPtr<SDlgPickAssetPath> PickAssetPathWidget =
SNew(SDlgPickAssetPath)
.Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location"))
.DefaultAssetPath(FText::FromString(PackageNameSuggestion));
if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok)
{
// Get the full name of where we want to create the mesh asset.
PackageName = PickAssetPathWidget->GetFullAssetPath().ToString();
MeshName = FPackageName::GetLongPackageAssetName(PackageName);
// Check if the user inputed a valid asset name, if they did not, give it the generated default name
if (MeshName.IsEmpty())
{
// Use the defaults that were already generated.
PackageName = PackageNameSuggestion;
MeshName = *Name;
}
}
}
else
{
PackageName = InPackageName;
MeshName = *FPackageName::GetLongPackageAssetName(PackageName);
}
if(!PackageName.IsEmpty() && !MeshName.IsEmpty())
{
TArray<FRawMesh> RawMeshes;
TArray<UMaterialInterface*> Materials;
TArray<FRawMeshTracker> RawMeshTrackers;
FMatrix WorldToRoot = InRootTransform.ToMatrixWithScale().Inverse();
// first do a pass to determine the max LOD level we will be combining meshes into
int32 OverallMaxLODs = 0;
for (UMeshComponent* MeshComponent : InMeshComponents)
{
USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent);
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
if (IsValidSkinnedMeshComponent(SkinnedMeshComponent))
{
OverallMaxLODs = FMath::Max(SkinnedMeshComponent->MeshObject->GetSkeletalMeshResource().LODModels.Num(), OverallMaxLODs);
}
else if(IsValidStaticMeshComponent(StaticMeshComponent))
{
OverallMaxLODs = FMath::Max(StaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num(), OverallMaxLODs);
}
}
// Resize raw meshes to accommodate the number of LODs we will need
RawMeshes.SetNum(OverallMaxLODs);
RawMeshTrackers.SetNum(OverallMaxLODs);
// Export all visible components
for (UMeshComponent* MeshComponent : InMeshComponents)
{
FMatrix ComponentToWorld = MeshComponent->GetComponentTransform().ToMatrixWithScale() * WorldToRoot;
USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent);
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
if (IsValidSkinnedMeshComponent(SkinnedMeshComponent))
{
SkinnedMeshToRawMeshes(SkinnedMeshComponent, OverallMaxLODs, ComponentToWorld, PackageName, RawMeshTrackers, RawMeshes, Materials);
}
else if (IsValidStaticMeshComponent(StaticMeshComponent))
{
StaticMeshToRawMeshes(StaticMeshComponent, OverallMaxLODs, ComponentToWorld, PackageName, RawMeshTrackers, RawMeshes, Materials);
}
}
uint32 MaxInUseTextureCoordinate = 0;
// scrub invalid vert color & tex coord data
check(RawMeshes.Num() == RawMeshTrackers.Num());
for (int32 RawMeshIndex = 0; RawMeshIndex < RawMeshes.Num(); RawMeshIndex++)
{
if (!RawMeshTrackers[RawMeshIndex].bValidColors)
{
RawMeshes[RawMeshIndex].WedgeColors.Empty();
}
for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
{
if (!RawMeshTrackers[RawMeshIndex].bValidTexCoords[TexCoordIndex])
{
RawMeshes[RawMeshIndex].WedgeTexCoords[TexCoordIndex].Empty();
}
else
{
// Store first texture coordinate index not in use
MaxInUseTextureCoordinate = FMath::Max(MaxInUseTextureCoordinate, TexCoordIndex);
}
}
}
// Check if we got some valid data.
bool bValidData = false;
for (FRawMesh& RawMesh : RawMeshes)
{
if (RawMesh.IsValidOrFixable())
{
bValidData = true;
break;
}
}
if (bValidData)
{
// Then find/create it.
UPackage* Package = CreatePackage(NULL, *PackageName);
check(Package);
// Create StaticMesh object
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, *MeshName, RF_Public | RF_Standalone);
StaticMesh->InitResources();
StaticMesh->LightingGuid = FGuid::NewGuid();
// Determine which texture coordinate map should be used for storing/generating the lightmap UVs
const uint32 LightMapIndex = FMath::Min(MaxInUseTextureCoordinate + 1, (uint32)MAX_MESH_TEXTURE_COORDS - 1);
// Add source to new StaticMesh
for (FRawMesh& RawMesh : RawMeshes)
{
if (RawMesh.IsValidOrFixable())
{
FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
SrcModel->BuildSettings.bRecomputeNormals = false;
SrcModel->BuildSettings.bRecomputeTangents = false;
SrcModel->BuildSettings.bRemoveDegenerates = true;
SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false;
SrcModel->BuildSettings.bUseFullPrecisionUVs = false;
SrcModel->BuildSettings.bGenerateLightmapUVs = true;
SrcModel->BuildSettings.SrcLightmapIndex = 0;
SrcModel->BuildSettings.DstLightmapIndex = LightMapIndex;
SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh);
}
}
// Copy materials to new mesh
for(UMaterialInterface* Material : Materials)
{
StaticMesh->StaticMaterials.Add(FStaticMaterial(Material));
}
//Set the Imported version before calling the build
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
// Set light map coordinate index to match DstLightmapIndex
StaticMesh->LightMapCoordinateIndex = LightMapIndex;
// setup section info map
for (int32 RawMeshLODIndex = 0; RawMeshLODIndex < RawMeshes.Num(); RawMeshLODIndex++)
{
const FRawMesh& RawMesh = RawMeshes[RawMeshLODIndex];
TArray<int32> UniqueMaterialIndices;
for (int32 MaterialIndex : RawMesh.FaceMaterialIndices)
{
UniqueMaterialIndices.AddUnique(MaterialIndex);
}
int32 SectionIndex = 0;
for (int32 UniqueMaterialIndex : UniqueMaterialIndices)
{
StaticMesh->SectionInfoMap.Set(RawMeshLODIndex, SectionIndex, FMeshSectionInfo(UniqueMaterialIndex));
SectionIndex++;
}
}
// Build mesh from source
StaticMesh->Build(false);
StaticMesh->PostEditChange();
StaticMesh->MarkPackageDirty();
// Notify asset registry of new asset
FAssetRegistryModule::AssetCreated(StaticMesh);
// Display notification so users can quickly access the mesh
if (GIsEditor)
{
FNotificationInfo Info(FText::Format(LOCTEXT("SkeletalMeshConverted", "Successfully Converted Mesh"), FText::FromString(StaticMesh->GetName())));
Info.ExpireDuration = 8.0f;
Info.bUseLargeFont = false;
Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray<UObject*>({ StaticMesh })); });
Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(StaticMesh->GetName()));
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if ( Notification.IsValid() )
{
Notification->SetCompletionState( SNotificationItem::CS_Success );
}
}
}
}
return nullptr;
}
/**
* Builds a renderable skeletal mesh LOD model. Note that the array of chunks
* will be destroyed during this process!
* @param LODModel Upon return contains a renderable skeletal mesh LOD model.
* @param RefSkeleton The reference skeleton associated with the model.
* @param Chunks Skinned mesh chunks from which to build the renderable model.
* @param PointToOriginalMap Maps a vertex's RawPointIdx to its index at import time.
*/
void FMeshUtilities::BuildSkeletalModelFromChunks(FStaticLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, TArray<FSkinnedMeshChunk*>& Chunks, const TArray<int32>& PointToOriginalMap)
{
#if WITH_EDITORONLY_DATA
// Clear out any data currently held in the LOD model.
LODModel.Sections.Empty();
LODModel.NumVertices = 0;
if (LODModel.MultiSizeIndexContainer.IsIndexBufferValid())
{
LODModel.MultiSizeIndexContainer.GetIndexBuffer()->Empty();
}
// Setup the section and chunk arrays on the model.
for (int32 ChunkIndex = 0; ChunkIndex < Chunks.Num(); ++ChunkIndex)
{
FSkinnedMeshChunk* SrcChunk = Chunks[ChunkIndex];
FSkelMeshSection& Section = *new(LODModel.Sections) FSkelMeshSection();
Section.MaterialIndex = SrcChunk->MaterialIndex;
Exchange(Section.BoneMap, SrcChunk->BoneMap);
// Update the active bone indices on the LOD model.
for (int32 BoneIndex = 0; BoneIndex < Section.BoneMap.Num(); ++BoneIndex)
{
LODModel.ActiveBoneIndices.AddUnique(Section.BoneMap[BoneIndex]);
}
}
// ensure parent exists with incoming active bone indices, and the result should be sorted
RefSkeleton.EnsureParentExists(LODModel.ActiveBoneIndices);
// Reset 'final vertex to import vertex' map info
LODModel.MeshToImportVertexMap.Empty();
LODModel.MaxImportVertex = 0;
// Keep track of index mapping to chunk vertex offsets
TArray< TArray<uint32> > VertexIndexRemap;
VertexIndexRemap.Empty(LODModel.Sections.Num());
// Pack the chunk vertices into a single vertex buffer.
TArray<uint32> RawPointIndices;
LODModel.NumVertices = 0;
int32 PrevMaterialIndex = -1;
int32 CurrentChunkBaseVertexIndex = -1; // base vertex index for all chunks of the same material
int32 CurrentChunkVertexCount = -1; // total vertex count for all chunks of the same material
int32 CurrentVertexIndex = 0; // current vertex index added to the index buffer for all chunks of the same material
// rearrange the vert order to minimize the data fetched by the GPU
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
if (IsInGameThread())
{
GWarn->StatusUpdate(SectionIndex, LODModel.Sections.Num(), NSLOCTEXT("UnrealEd", "ProcessingSections", "Processing Sections"));
}
FSkinnedMeshChunk* SrcChunk = Chunks[SectionIndex];
FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
TArray<FSoftSkinBuildVertex>& ChunkVertices = SrcChunk->Vertices;
TArray<uint32>& ChunkIndices = SrcChunk->Indices;
// Reorder the section index buffer for better vertex cache efficiency.
CacheOptimizeIndexBuffer(ChunkIndices);
// Calculate the number of triangles in the section. Note that CacheOptimize may change the number of triangles in the index buffer!
Section.NumTriangles = ChunkIndices.Num() / 3;
TArray<FSoftSkinBuildVertex> OriginalVertices;
Exchange(ChunkVertices, OriginalVertices);
ChunkVertices.AddUninitialized(OriginalVertices.Num());
TArray<int32> IndexCache;
IndexCache.AddUninitialized(ChunkVertices.Num());
FMemory::Memset(IndexCache.GetData(), INDEX_NONE, IndexCache.Num() * IndexCache.GetTypeSize());
int32 NextAvailableIndex = 0;
// Go through the indices and assign them new values that are coherent where possible
for (int32 Index = 0; Index < ChunkIndices.Num(); Index++)
{
const int32 OriginalIndex = ChunkIndices[Index];
const int32 CachedIndex = IndexCache[OriginalIndex];
if (CachedIndex == INDEX_NONE)
{
// No new index has been allocated for this existing index, assign a new one
ChunkIndices[Index] = NextAvailableIndex;
// Mark what this index has been assigned to
IndexCache[OriginalIndex] = NextAvailableIndex;
NextAvailableIndex++;
}
else
{
// Reuse an existing index assignment
ChunkIndices[Index] = CachedIndex;
}
// Reorder the vertices based on the new index assignment
ChunkVertices[ChunkIndices[Index]] = OriginalVertices[OriginalIndex];
}
}
// Build the arrays of rigid and soft vertices on the model's chunks.
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
TArray<FSoftSkinBuildVertex>& ChunkVertices = Chunks[SectionIndex]->Vertices;
if (IsInGameThread())
{
// Only update status if in the game thread. When importing morph targets, this function can run in another thread
GWarn->StatusUpdate(SectionIndex, LODModel.Sections.Num(), NSLOCTEXT("UnrealEd", "ProcessingChunks", "Processing Chunks"));
}
CurrentVertexIndex = 0;
CurrentChunkVertexCount = 0;
PrevMaterialIndex = Section.MaterialIndex;
// Calculate the offset to this chunk's vertices in the vertex buffer.
Section.BaseVertexIndex = CurrentChunkBaseVertexIndex = LODModel.NumVertices;
// Update the size of the vertex buffer.
LODModel.NumVertices += ChunkVertices.Num();
// Separate the section's vertices into rigid and soft vertices.
TArray<uint32>& ChunkVertexIndexRemap = *new(VertexIndexRemap)TArray<uint32>();
ChunkVertexIndexRemap.AddUninitialized(ChunkVertices.Num());
for (int32 VertexIndex = 0; VertexIndex < ChunkVertices.Num(); VertexIndex++)
{
const FSoftSkinBuildVertex& SoftVertex = ChunkVertices[VertexIndex];
FSoftSkinVertex NewVertex;
NewVertex.Position = SoftVertex.Position;
NewVertex.TangentX = SoftVertex.TangentX;
NewVertex.TangentY = SoftVertex.TangentY;
NewVertex.TangentZ = SoftVertex.TangentZ;
FMemory::Memcpy(NewVertex.UVs, SoftVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
NewVertex.Color = SoftVertex.Color;
for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; ++i)
{
// it only adds to the bone map if it has weight on it
// BoneMap contains only the bones that has influence with weight of >0.f
// so here, just make sure it is included before setting the data
if (Section.BoneMap.IsValidIndex(SoftVertex.InfluenceBones[i]))
{
NewVertex.InfluenceBones[i] = SoftVertex.InfluenceBones[i];
NewVertex.InfluenceWeights[i] = SoftVertex.InfluenceWeights[i];
}
}
Section.SoftVertices.Add(NewVertex);
ChunkVertexIndexRemap[VertexIndex] = (uint32)(Section.BaseVertexIndex + CurrentVertexIndex);
CurrentVertexIndex++;
// add the index to the original wedge point source of this vertex
RawPointIndices.Add(SoftVertex.PointWedgeIdx);
// Also remember import index
const int32 RawVertIndex = PointToOriginalMap[SoftVertex.PointWedgeIdx];
LODModel.MeshToImportVertexMap.Add(RawVertIndex);
LODModel.MaxImportVertex = FMath::Max<float>(LODModel.MaxImportVertex, RawVertIndex);
}
// update NumVertices
Section.NumVertices = Section.SoftVertices.Num();
// update max bone influences
Section.CalcMaxBoneInfluences();
// Log info about the chunk.
UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: %u vertices, %u active bones"),
SectionIndex,
Section.GetNumVertices(),
Section.BoneMap.Num()
);
}
// Copy raw point indices to LOD model.
LODModel.RawPointIndices.RemoveBulkData();
if (RawPointIndices.Num())
{
LODModel.RawPointIndices.Lock(LOCK_READ_WRITE);
void* Dest = LODModel.RawPointIndices.Realloc(RawPointIndices.Num());
FMemory::Memcpy(Dest, RawPointIndices.GetData(), LODModel.RawPointIndices.GetBulkDataSize());
LODModel.RawPointIndices.Unlock();
}
#if DISALLOW_32BIT_INDICES
LODModel.MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16));
#else
LODModel.MultiSizeIndexContainer.CreateIndexBuffer((LODModel.NumVertices < MAX_uint16) ? sizeof(uint16) : sizeof(uint32));
#endif
// Finish building the sections.
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
const TArray<uint32>& SectionIndices = Chunks[SectionIndex]->Indices;
FRawStaticIndexBuffer16or32Interface* IndexBuffer = LODModel.MultiSizeIndexContainer.GetIndexBuffer();
Section.BaseIndex = IndexBuffer->Num();
const int32 NumIndices = SectionIndices.Num();
const TArray<uint32>& SectionVertexIndexRemap = VertexIndexRemap[SectionIndex];
for (int32 Index = 0; Index < NumIndices; Index++)
{
uint32 VertexIndex = SectionVertexIndexRemap[SectionIndices[Index]];
IndexBuffer->AddItem(VertexIndex);
}
}
// Free the skinned mesh chunks which are no longer needed.
for (int32 i = 0; i < Chunks.Num(); ++i)
{
delete Chunks[i];
Chunks[i] = NULL;
}
Chunks.Empty();
// Build the adjacency index buffer used for tessellation.
{
TArray<FSoftSkinVertex> Vertices;
LODModel.GetVertices(Vertices);
FMultiSizeIndexContainerData IndexData;
LODModel.MultiSizeIndexContainer.GetIndexBufferData(IndexData);
FMultiSizeIndexContainerData AdjacencyIndexData;
AdjacencyIndexData.DataTypeSize = IndexData.DataTypeSize;
BuildSkeletalAdjacencyIndexBuffer(Vertices, LODModel.NumTexCoords, IndexData.Indices, AdjacencyIndexData.Indices);
LODModel.AdjacencyMultiSizeIndexContainer.RebuildIndexBuffer(AdjacencyIndexData);
}
// Compute the required bones for this model.
USkeletalMesh::CalculateRequiredBones(LODModel, RefSkeleton, NULL);
#endif // #if WITH_EDITORONLY_DATA
}
/*------------------------------------------------------------------------------
Common functionality.
------------------------------------------------------------------------------*/
/** Helper struct for building acceleration structures. */
struct FIndexAndZ
{
float Z;
int32 Index;
/** Default constructor. */
FIndexAndZ() {}
/** Initialization constructor. */
FIndexAndZ(int32 InIndex, FVector V)
{
Z = 0.30f * V.X + 0.33f * V.Y + 0.37f * V.Z;
Index = InIndex;
}
};
/** Sorting function for vertex Z/index pairs. */
struct FCompareIndexAndZ
{
FORCEINLINE bool operator()(FIndexAndZ const& A, FIndexAndZ const& B) const { return A.Z < B.Z; }
};
static int32 ComputeNumTexCoords(FRawMesh const& RawMesh, int32 MaxSupportedTexCoords)
{
int32 NumWedges = RawMesh.WedgeIndices.Num();
int32 NumTexCoords = 0;
for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; ++TexCoordIndex)
{
if (RawMesh.WedgeTexCoords[TexCoordIndex].Num() != NumWedges)
{
break;
}
NumTexCoords++;
}
return FMath::Min(NumTexCoords, MaxSupportedTexCoords);
}
/**
* Returns true if the specified points are about equal
*/
inline bool PointsEqual(const FVector& V1, const FVector& V2, float ComparisonThreshold)
{
if (FMath::Abs(V1.X - V2.X) > ComparisonThreshold
|| FMath::Abs(V1.Y - V2.Y) > ComparisonThreshold
|| FMath::Abs(V1.Z - V2.Z) > ComparisonThreshold)
{
return false;
}
return true;
}
static inline FVector GetPositionForWedge(FRawMesh const& Mesh, int32 WedgeIndex)
{
int32 VertexIndex = Mesh.WedgeIndices[WedgeIndex];
return Mesh.VertexPositions[VertexIndex];
}
struct FMeshEdge
{
int32 Vertices[2];
int32 Faces[2];
};
/**
* This helper class builds the edge list for a mesh. It uses a hash of vertex
* positions to edges sharing that vertex to remove the n^2 searching of all
* previously added edges. This class is templatized so it can be used with
* either static mesh or skeletal mesh vertices
*/
template <class VertexClass> class TEdgeBuilder
{
protected:
/**
* The list of indices to build the edge data from
*/
const TArray<uint32>& Indices;
/**
* The array of verts for vertex position comparison
*/
const TArray<VertexClass>& Vertices;
/**
* The array of edges to create
*/
TArray<FMeshEdge>& Edges;
/**
* List of edges that start with a given vertex
*/
TMultiMap<FVector, FMeshEdge*> VertexToEdgeList;
/**
* This function determines whether a given edge matches or not. It must be
* provided by derived classes since they have the specific information that
* this class doesn't know about (vertex info, influences, etc)
*
* @param Index1 The first index of the edge being checked
* @param Index2 The second index of the edge
* @param OtherEdge The edge to compare. Was found via the map
*
* @return true if the edge is a match, false otherwise
*/
virtual bool DoesEdgeMatch(int32 Index1, int32 Index2, FMeshEdge* OtherEdge) = 0;
/**
* Searches the list of edges to see if this one matches an existing and
* returns a pointer to it if it does
*
* @param Index1 the first index to check for
* @param Index2 the second index to check for
*
* @return NULL if no edge was found, otherwise the edge that was found
*/
inline FMeshEdge* FindOppositeEdge(int32 Index1, int32 Index2)
{
FMeshEdge* Edge = NULL;
TArray<FMeshEdge*> EdgeList;
// Search the hash for a corresponding vertex
VertexToEdgeList.MultiFind(Vertices[Index2].Position, EdgeList);
// Now search through the array for a match or not
for (int32 EdgeIndex = 0; EdgeIndex < EdgeList.Num() && Edge == NULL;
EdgeIndex++)
{
FMeshEdge* OtherEdge = EdgeList[EdgeIndex];
// See if this edge matches the passed in edge
if (OtherEdge != NULL && DoesEdgeMatch(Index1, Index2, OtherEdge))
{
// We have a match
Edge = OtherEdge;
}
}
return Edge;
}
/**
* Updates an existing edge if found or adds the new edge to the list
*
* @param Index1 the first index in the edge
* @param Index2 the second index in the edge
* @param Triangle the triangle that this edge was found in
*/
inline void AddEdge(int32 Index1, int32 Index2, int32 Triangle)
{
// If this edge matches another then just fill the other triangle
// otherwise add it
FMeshEdge* OtherEdge = FindOppositeEdge(Index1, Index2);
if (OtherEdge == NULL)
{
// Add a new edge to the array
int32 EdgeIndex = Edges.AddZeroed();
Edges[EdgeIndex].Vertices[0] = Index1;
Edges[EdgeIndex].Vertices[1] = Index2;
Edges[EdgeIndex].Faces[0] = Triangle;
Edges[EdgeIndex].Faces[1] = -1;
// Also add this edge to the hash for faster searches
// NOTE: This relies on the array never being realloced!
VertexToEdgeList.Add(Vertices[Index1].Position, &Edges[EdgeIndex]);
}
else
{
OtherEdge->Faces[1] = Triangle;
}
}
public:
/**
* Initializes the values for the code that will build the mesh edge list
*/
TEdgeBuilder(const TArray<uint32>& InIndices,
const TArray<VertexClass>& InVertices,
TArray<FMeshEdge>& OutEdges) :
Indices(InIndices), Vertices(InVertices), Edges(OutEdges)
{
// Presize the array so that there are no extra copies being done
// when adding edges to it
Edges.Empty(Indices.Num());
}
/**
* Virtual dtor
*/
virtual ~TEdgeBuilder(){}
/**
* Uses a hash of indices to edge lists so that it can avoid the n^2 search
* through the full edge list
*/
void FindEdges(void)
{
// @todo Handle something other than trilists when building edges
int32 TriangleCount = Indices.Num() / 3;
int32 EdgeCount = 0;
// Work through all triangles building the edges
for (int32 Triangle = 0; Triangle < TriangleCount; Triangle++)
{
// Determine the starting index
int32 TriangleIndex = Triangle * 3;
// Get the indices for the triangle
int32 Index1 = Indices[TriangleIndex];
int32 Index2 = Indices[TriangleIndex + 1];
int32 Index3 = Indices[TriangleIndex + 2];
// Add the first to second edge
AddEdge(Index1, Index2, Triangle);
// Now add the second to third
AddEdge(Index2, Index3, Triangle);
// Add the third to first edge
AddEdge(Index3, Index1, Triangle);
}
}
};
/**
* This is the static mesh specific version for finding edges
*/
class FStaticMeshEdgeBuilder : public TEdgeBuilder<FStaticMeshBuildVertex>
{
public:
/**
* Constructor that passes all work to the parent class
*/
FStaticMeshEdgeBuilder(const TArray<uint32>& InIndices,
const TArray<FStaticMeshBuildVertex>& InVertices,
TArray<FMeshEdge>& OutEdges) :
TEdgeBuilder<FStaticMeshBuildVertex>(InIndices, InVertices, OutEdges)
{
}
/**
* This function determines whether a given edge matches or not for a static mesh
*
* @param Index1 The first index of the edge being checked
* @param Index2 The second index of the edge
* @param OtherEdge The edge to compare. Was found via the map
*
* @return true if the edge is a match, false otherwise
*/
bool DoesEdgeMatch(int32 Index1, int32 Index2, FMeshEdge* OtherEdge)
{
return Vertices[OtherEdge->Vertices[1]].Position == Vertices[Index1].Position &&
OtherEdge->Faces[1] == -1;
}
};
static void ComputeTriangleTangents(
const TArray<FVector>& InVertices,
const TArray<uint32>& InIndices,
const TArray<FVector2D>& InUVs,
TArray<FVector>& OutTangentX,
TArray<FVector>& OutTangentY,
TArray<FVector>& OutTangentZ,
float ComparisonThreshold
)
{
const int32 NumTriangles = InIndices.Num() / 3;
OutTangentX.Empty(NumTriangles);
OutTangentY.Empty(NumTriangles);
OutTangentZ.Empty(NumTriangles);
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
{
int32 UVIndex = 0;
FVector P[3];
for (int32 i = 0; i < 3; ++i)
{
P[i] = InVertices[InIndices[TriangleIndex * 3 + i]];
}
const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
FMatrix ParameterToLocal(
FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
FPlane(P[0].X, P[0].Y, P[0].Z, 0),
FPlane(0, 0, 0, 1)
);
const FVector2D T1 = InUVs[TriangleIndex * 3 + 0];
const FVector2D T2 = InUVs[TriangleIndex * 3 + 1];
const FVector2D T3 = InUVs[TriangleIndex * 3 + 2];
FMatrix ParameterToTexture(
FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
FPlane(T1.X, T1.Y, 1, 0),
FPlane(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
OutTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
OutTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
OutTangentZ.Add(Normal);
FVector::CreateOrthonormalBasis(
OutTangentX[TriangleIndex],
OutTangentY[TriangleIndex],
OutTangentZ[TriangleIndex]
);
}
check(OutTangentX.Num() == NumTriangles);
check(OutTangentY.Num() == NumTriangles);
check(OutTangentZ.Num() == NumTriangles);
}
static void ComputeTriangleTangents(
TArray<FVector>& OutTangentX,
TArray<FVector>& OutTangentY,
TArray<FVector>& OutTangentZ,
FRawMesh const& RawMesh,
float ComparisonThreshold
)
{
ComputeTriangleTangents(RawMesh.VertexPositions, RawMesh.WedgeIndices, RawMesh.WedgeTexCoords[0], OutTangentX, OutTangentY, OutTangentZ, ComparisonThreshold);
/*int32 NumTriangles = RawMesh.WedgeIndices.Num() / 3;
TriangleTangentX.Empty(NumTriangles);
TriangleTangentY.Empty(NumTriangles);
TriangleTangentZ.Empty(NumTriangles);
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
{
int32 UVIndex = 0;
FVector P[3];
for (int32 i = 0; i < 3; ++i)
{
P[i] = GetPositionForWedge(RawMesh, TriangleIndex * 3 + i);
}
const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
FMatrix ParameterToLocal(
FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
FPlane(P[0].X, P[0].Y, P[0].Z, 0),
FPlane(0, 0, 0, 1)
);
FVector2D T1 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 0];
FVector2D T2 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 1];
FVector2D T3 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 2];
FMatrix ParameterToTexture(
FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
FPlane(T1.X, T1.Y, 1, 0),
FPlane(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
TriangleTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
TriangleTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
TriangleTangentZ.Add(Normal);
FVector::CreateOrthonormalBasis(
TriangleTangentX[TriangleIndex],
TriangleTangentY[TriangleIndex],
TriangleTangentZ[TriangleIndex]
);
}
check(TriangleTangentX.Num() == NumTriangles);
check(TriangleTangentY.Num() == NumTriangles);
check(TriangleTangentZ.Num() == NumTriangles);*/
}
/**
* Create a table that maps the corner of each face to its overlapping corners.
* @param OutOverlappingCorners - Maps a corner index to the indices of all overlapping corners.
* @param RawMesh - The mesh for which to compute overlapping corners.
*/
static void FindOverlappingCorners(
TMultiMap<int32, int32>& OutOverlappingCorners,
const TArray<FVector>& InVertices,
const TArray<uint32>& InIndices,
float ComparisonThreshold
)
{
const int32 NumWedges = InIndices.Num();
// Create a list of vertex Z/index pairs
TArray<FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumWedges);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; WedgeIndex++)
{
new(VertIndexAndZ)FIndexAndZ(WedgeIndex, InVertices[InIndices[WedgeIndex]]);
}
// Sort the vertices by z value
VertIndexAndZ.Sort(FCompareIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > ComparisonThreshold)
break; // can't be any more dups
const FVector& PositionA = InVertices[InIndices[VertIndexAndZ[i].Index]];
const FVector& PositionB = InVertices[InIndices[VertIndexAndZ[j].Index]];
if (PointsEqual(PositionA, PositionB, ComparisonThreshold))
{
OutOverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
OutOverlappingCorners.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
}
}
}
}
/**
* Create a table that maps the corner of each face to its overlapping corners.
* @param OutOverlappingCorners - Maps a corner index to the indices of all overlapping corners.
* @param RawMesh - The mesh for which to compute overlapping corners.
*/
static void FindOverlappingCorners(
TMultiMap<int32, int32>& OutOverlappingCorners,
FRawMesh const& RawMesh,
float ComparisonThreshold
)
{
FindOverlappingCorners(OutOverlappingCorners, RawMesh.VertexPositions, RawMesh.WedgeIndices, ComparisonThreshold);
}
/**
* Smoothing group interpretation helper structure.
*/
struct FFanFace
{
int32 FaceIndex;
int32 LinkedVertexIndex;
bool bFilled;
bool bBlendTangents;
bool bBlendNormals;
};
static void ComputeTangents(
const TArray<FVector>& InVertices,
const TArray<uint32>& InIndices,
const TArray<FVector2D>& InUVs,
const TArray<uint32>& SmoothingGroupIndices,
TMultiMap<int32, int32> const& OverlappingCorners,
TArray<FVector>& OutTangentX,
TArray<FVector>& OutTangentY,
TArray<FVector>& OutTangentZ,
const uint32 TangentOptions
)
{
bool bBlendOverlappingNormals = (TangentOptions & ETangentOptions::BlendOverlappingNormals) != 0;
bool bIgnoreDegenerateTriangles = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) != 0;
float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
// Compute per-triangle tangents.
TArray<FVector> TriangleTangentX;
TArray<FVector> TriangleTangentY;
TArray<FVector> TriangleTangentZ;
ComputeTriangleTangents(
InVertices,
InIndices,
InUVs,
TriangleTangentX,
TriangleTangentY,
TriangleTangentZ,
bIgnoreDegenerateTriangles ? SMALL_NUMBER : 0.0f
);
// Declare these out here to avoid reallocations.
TArray<FFanFace> RelevantFacesForCorner[3];
TArray<int32> AdjacentFaces;
TArray<int32> DupVerts;
int32 NumWedges = InIndices.Num();
int32 NumFaces = NumWedges / 3;
// Allocate storage for tangents if none were provided.
if (OutTangentX.Num() != NumWedges)
{
OutTangentX.Empty(NumWedges);
OutTangentX.AddZeroed(NumWedges);
}
if (OutTangentY.Num() != NumWedges)
{
OutTangentY.Empty(NumWedges);
OutTangentY.AddZeroed(NumWedges);
}
if (OutTangentZ.Num() != NumWedges)
{
OutTangentZ.Empty(NumWedges);
OutTangentZ.AddZeroed(NumWedges);
}
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
int32 WedgeOffset = FaceIndex * 3;
FVector CornerPositions[3];
FVector CornerTangentX[3];
FVector CornerTangentY[3];
FVector CornerTangentZ[3];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerTangentX[CornerIndex] = FVector::ZeroVector;
CornerTangentY[CornerIndex] = FVector::ZeroVector;
CornerTangentZ[CornerIndex] = FVector::ZeroVector;
CornerPositions[CornerIndex] = InVertices[InIndices[WedgeOffset + CornerIndex]];
RelevantFacesForCorner[CornerIndex].Reset();
}
// Don't process degenerate triangles.
if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
|| PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
|| PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
{
continue;
}
// No need to process triangles if tangents already exist.
bool bCornerHasTangents[3] = { 0 };
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
bCornerHasTangents[CornerIndex] = !OutTangentX[WedgeOffset + CornerIndex].IsZero()
&& !OutTangentY[WedgeOffset + CornerIndex].IsZero()
&& !OutTangentZ[WedgeOffset + CornerIndex].IsZero();
}
if (bCornerHasTangents[0] && bCornerHasTangents[1] && bCornerHasTangents[2])
{
continue;
}
// Calculate smooth vertex normals.
float Determinant = FVector::Triple(
TriangleTangentX[FaceIndex],
TriangleTangentY[FaceIndex],
TriangleTangentZ[FaceIndex]
);
// Start building a list of faces adjacent to this face.
AdjacentFaces.Reset();
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
int32 ThisCornerIndex = WedgeOffset + CornerIndex;
DupVerts.Reset();
OverlappingCorners.MultiFind(ThisCornerIndex, DupVerts);
DupVerts.Add(ThisCornerIndex); // I am a "dup" of myself
for (int32 k = 0; k < DupVerts.Num(); k++)
{
AdjacentFaces.AddUnique(DupVerts[k] / 3);
}
}
// We need to sort these here because the criteria for point equality is
// exact, so we must ensure the exact same order for all dups.
AdjacentFaces.Sort();
// Process adjacent faces
for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
{
int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
for (int32 OurCornerIndex = 0; OurCornerIndex < 3; OurCornerIndex++)
{
if (bCornerHasTangents[OurCornerIndex])
continue;
FFanFace NewFanFace;
int32 CommonIndexCount = 0;
// Check for vertices in common.
if (FaceIndex == OtherFaceIndex)
{
CommonIndexCount = 3;
NewFanFace.LinkedVertexIndex = OurCornerIndex;
}
else
{
// Check matching vertices against main vertex .
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
if (PointsEqual(
CornerPositions[OurCornerIndex],
InVertices[InIndices[OtherFaceIndex * 3 + OtherCornerIndex]],
ComparisonThreshold
))
{
CommonIndexCount++;
NewFanFace.LinkedVertexIndex = OtherCornerIndex;
}
}
}
// Add if connected by at least one point. Smoothing matches are considered later.
if (CommonIndexCount > 0)
{
NewFanFace.FaceIndex = OtherFaceIndex;
NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
NewFanFace.bBlendTangents = NewFanFace.bFilled;
NewFanFace.bBlendNormals = NewFanFace.bFilled;
RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
}
}
}
// Find true relevance of faces for a vertex normal by traversing
// smoothing-group-compatible connected triangle fans around common vertices.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasTangents[CornerIndex])
continue;
int32 NewConnections;
do
{
NewConnections = 0;
for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
{
FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
// The vertex' own face is initially the only face with bFilled == true.
if (OtherFace.bFilled)
{
for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
{
FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
{
if ((NextFaceIndex != OtherFaceIdx)
&& (SmoothingGroupIndices[NextFace.FaceIndex] & SmoothingGroupIndices[OtherFace.FaceIndex]))
{
int32 CommonVertices = 0;
int32 CommonTangentVertices = 0;
int32 CommonNormalVertices = 0;
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
for (int32 NextCornerIndex = 0; NextCornerIndex < 3; NextCornerIndex++)
{
int32 NextVertexIndex = InIndices[NextFace.FaceIndex * 3 + NextCornerIndex];
int32 OtherVertexIndex = InIndices[OtherFace.FaceIndex * 3 + OtherCornerIndex];
if (PointsEqual(
InVertices[NextVertexIndex],
InVertices[OtherVertexIndex],
ComparisonThreshold))
{
CommonVertices++;
const FVector2D& UVOne = InUVs[NextFace.FaceIndex * 3 + NextCornerIndex];
const FVector2D& UVTwo = InUVs[OtherFace.FaceIndex * 3 + OtherCornerIndex];
if (UVsEqual(UVOne, UVTwo))
{
CommonTangentVertices++;
}
if (bBlendOverlappingNormals
|| NextVertexIndex == OtherVertexIndex)
{
CommonNormalVertices++;
}
}
}
}
// Flood fill faces with more than one common vertices which must be touching edges.
if (CommonVertices > 1)
{
NextFace.bFilled = true;
NextFace.bBlendNormals = (CommonNormalVertices > 1);
NewConnections++;
// Only blend tangents if there is no UV seam along the edge with this face.
if (OtherFace.bBlendTangents && CommonTangentVertices > 1)
{
float OtherDeterminant = FVector::Triple(
TriangleTangentX[NextFace.FaceIndex],
TriangleTangentY[NextFace.FaceIndex],
TriangleTangentZ[NextFace.FaceIndex]
);
if ((Determinant * OtherDeterminant) > 0.0f)
{
NextFace.bBlendTangents = true;
}
}
}
}
}
}
}
}
} while (NewConnections > 0);
}
// Vertex normal construction.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasTangents[CornerIndex])
{
CornerTangentX[CornerIndex] = OutTangentX[WedgeOffset + CornerIndex];
CornerTangentY[CornerIndex] = OutTangentY[WedgeOffset + CornerIndex];
CornerTangentZ[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
}
else
{
for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
{
FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
if (RelevantFace.bFilled)
{
int32 OtherFaceIndex = RelevantFace.FaceIndex;
if (RelevantFace.bBlendTangents)
{
CornerTangentX[CornerIndex] += TriangleTangentX[OtherFaceIndex];
CornerTangentY[CornerIndex] += TriangleTangentY[OtherFaceIndex];
}
if (RelevantFace.bBlendNormals)
{
CornerTangentZ[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
}
}
}
if (!OutTangentX[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentX[CornerIndex] = OutTangentX[WedgeOffset + CornerIndex];
}
if (!OutTangentY[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentY[CornerIndex] = OutTangentY[WedgeOffset + CornerIndex];
}
if (!OutTangentZ[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentZ[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
}
}
}
// Normalization.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerTangentX[CornerIndex].Normalize();
CornerTangentY[CornerIndex].Normalize();
CornerTangentZ[CornerIndex].Normalize();
// Gram-Schmidt orthogonalization
CornerTangentY[CornerIndex] -= CornerTangentX[CornerIndex] * (CornerTangentX[CornerIndex] | CornerTangentY[CornerIndex]);
CornerTangentY[CornerIndex].Normalize();
CornerTangentX[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentX[CornerIndex]);
CornerTangentX[CornerIndex].Normalize();
CornerTangentY[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentY[CornerIndex]);
CornerTangentY[CornerIndex].Normalize();
}
// Copy back to the mesh.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
OutTangentX[WedgeOffset + CornerIndex] = CornerTangentX[CornerIndex];
OutTangentY[WedgeOffset + CornerIndex] = CornerTangentY[CornerIndex];
OutTangentZ[WedgeOffset + CornerIndex] = CornerTangentZ[CornerIndex];
}
}
check(OutTangentX.Num() == NumWedges);
check(OutTangentY.Num() == NumWedges);
check(OutTangentZ.Num() == NumWedges);
}
static void ComputeTangents(
FRawMesh& RawMesh,
TMultiMap<int32, int32> const& OverlappingCorners,
uint32 TangentOptions
)
{
const float ComparisonThreshold = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) ? THRESH_POINTS_ARE_SAME : 0.0f;
ComputeTangents(RawMesh.VertexPositions, RawMesh.WedgeIndices, RawMesh.WedgeTexCoords[0], RawMesh.FaceSmoothingMasks, OverlappingCorners, RawMesh.WedgeTangentX, RawMesh.WedgeTangentY, RawMesh.WedgeTangentZ, TangentOptions);
}
/*------------------------------------------------------------------------------
MikkTSpace for computing tangents.
------------------------------------------------------------------------------*/
static int MikkGetNumFaces(const SMikkTSpaceContext* Context)
{
FRawMesh *UserData = (FRawMesh*)(Context->m_pUserData);
return UserData->WedgeIndices.Num() / 3;
}
static int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx)
{
// All of our meshes are triangles.
return 3;
}
static void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
{
FRawMesh *UserData = (FRawMesh*)(Context->m_pUserData);
FVector VertexPosition = UserData->GetWedgePosition(FaceIdx * 3 + VertIdx);
Position[0] = VertexPosition.X;
Position[1] = VertexPosition.Y;
Position[2] = VertexPosition.Z;
}
static void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
{
FRawMesh *UserData = (FRawMesh*)(Context->m_pUserData);
FVector &VertexNormal = UserData->WedgeTangentZ[FaceIdx * 3 + VertIdx];
for (int32 i = 0; i < 3; ++i)
{
Normal[i] = VertexNormal[i];
}
}
static void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
{
FRawMesh *UserData = (FRawMesh*)(Context->m_pUserData);
FVector &VertexTangent = UserData->WedgeTangentX[FaceIdx * 3 + VertIdx];
for (int32 i = 0; i < 3; ++i)
{
VertexTangent[i] = Tangent[i];
}
FVector Bitangent = BitangentSign * FVector::CrossProduct(UserData->WedgeTangentZ[FaceIdx * 3 + VertIdx], VertexTangent);
FVector &VertexBitangent = UserData->WedgeTangentY[FaceIdx * 3 + VertIdx];
for (int32 i = 0; i < 3; ++i)
{
VertexBitangent[i] = -Bitangent[i];
}
}
static void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
{
FRawMesh *UserData = (FRawMesh*)(Context->m_pUserData);
FVector2D &TexCoord = UserData->WedgeTexCoords[0][FaceIdx * 3 + VertIdx];
UV[0] = TexCoord.X;
UV[1] = TexCoord.Y;
}
// MikkTSpace implementations for skeletal meshes, where tangents/bitangents are ultimately derived from lists of attributes.
// Holder for skeletal data to be passed to MikkTSpace.
// Holds references to the wedge, face and points vectors that BuildSkeletalMesh is given.
// Holds reference to the calculated normals array, which will be fleshed out if they've been calculated.
// Holds reference to the newly created tangent and bitangent arrays, which MikkTSpace will fleshed out if required.
class MikkTSpace_Skeletal_Mesh
{
public:
const TArray<FMeshWedge> &wedges; //Reference to wedge list.
const TArray<FMeshFace> &faces; //Reference to face list. Also contains normal/tangent/bitanget/UV coords for each vertex of the face.
const TArray<FVector> &points; //Reference to position list.
bool bComputeNormals; //Copy of bComputeNormals.
TArray<FVector> &TangentsX; //Reference to newly created tangents list.
TArray<FVector> &TangentsY; //Reference to newly created bitangents list.
TArray<FVector> &TangentsZ; //Reference to computed normals, will be empty otherwise.
MikkTSpace_Skeletal_Mesh(
const TArray<FMeshWedge> &Wedges,
const TArray<FMeshFace> &Faces,
const TArray<FVector> &Points,
bool bInComputeNormals,
TArray<FVector> &VertexTangentsX,
TArray<FVector> &VertexTangentsY,
TArray<FVector> &VertexTangentsZ
)
:
wedges(Wedges),
faces(Faces),
points(Points),
bComputeNormals(bInComputeNormals),
TangentsX(VertexTangentsX),
TangentsY(VertexTangentsY),
TangentsZ(VertexTangentsZ)
{
}
};
static int MikkGetNumFaces_Skeletal(const SMikkTSpaceContext* Context)
{
MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
return UserData->faces.Num();
}
static int MikkGetNumVertsOfFace_Skeletal(const SMikkTSpaceContext* Context, const int FaceIdx)
{
// Confirmed?
return 3;
}
static void MikkGetPosition_Skeletal(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
{
MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
const FVector &VertexPosition = UserData->points[UserData->wedges[UserData->faces[FaceIdx].iWedge[VertIdx]].iVertex];
Position[0] = VertexPosition.X;
Position[1] = VertexPosition.Y;
Position[2] = VertexPosition.Z;
}
static void MikkGetNormal_Skeletal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
{
MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
// Get different normals depending on whether they've been calculated or not.
if (UserData->bComputeNormals) {
FVector &VertexNormal = UserData->TangentsZ[FaceIdx * 3 + VertIdx];
Normal[0] = VertexNormal.X;
Normal[1] = VertexNormal.Y;
Normal[2] = VertexNormal.Z;
}
else
{
const FVector &VertexNormal = UserData->faces[FaceIdx].TangentZ[VertIdx];
Normal[0] = VertexNormal.X;
Normal[1] = VertexNormal.Y;
Normal[2] = VertexNormal.Z;
}
}
static void MikkSetTSpaceBasic_Skeletal(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
{
MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
FVector &VertexTangent = UserData->TangentsX[FaceIdx * 3 + VertIdx];
VertexTangent.X = Tangent[0];
VertexTangent.Y = Tangent[1];
VertexTangent.Z = Tangent[2];
FVector Bitangent;
// Get different normals depending on whether they've been calculated or not.
if (UserData->bComputeNormals) {
Bitangent = BitangentSign * FVector::CrossProduct(UserData->TangentsZ[FaceIdx * 3 + VertIdx], VertexTangent);
}
else
{
Bitangent = BitangentSign * FVector::CrossProduct(UserData->faces[FaceIdx].TangentZ[VertIdx], VertexTangent);
}
FVector &VertexBitangent = UserData->TangentsY[FaceIdx * 3 + VertIdx];
// Switch the tangent space swizzle to X+Y-Z+ for legacy reasons.
VertexBitangent.X = -Bitangent[0];
VertexBitangent.Y = -Bitangent[1];
VertexBitangent.Z = -Bitangent[2];
}
static void MikkGetTexCoord_Skeletal(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
{
MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
const FVector2D &TexCoord = UserData->wedges[UserData->faces[FaceIdx].iWedge[VertIdx]].UVs[0];
UV[0] = TexCoord.X;
UV[1] = TexCoord.Y;
}
static void ComputeTangents_MikkTSpace(
FRawMesh& RawMesh,
TMultiMap<int32, int32> const& OverlappingCorners,
uint32 TangentOptions
)
{
bool bBlendOverlappingNormals = (TangentOptions & ETangentOptions::BlendOverlappingNormals) != 0;
bool bIgnoreDegenerateTriangles = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) != 0;
float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
// Compute per-triangle tangents.
TArray<FVector> TriangleTangentX;
TArray<FVector> TriangleTangentY;
TArray<FVector> TriangleTangentZ;
ComputeTriangleTangents(
TriangleTangentX,
TriangleTangentY,
TriangleTangentZ,
RawMesh,
bIgnoreDegenerateTriangles ? SMALL_NUMBER : 0.0f
);
// Declare these out here to avoid reallocations.
TArray<FFanFace> RelevantFacesForCorner[3];
TArray<int32> AdjacentFaces;
TArray<int32> DupVerts;
int32 NumWedges = RawMesh.WedgeIndices.Num();
int32 NumFaces = NumWedges / 3;
bool bWedgeNormals = true;
bool bWedgeTSpace = false;
for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeTangentZ.Num(); ++WedgeIdx)
{
bWedgeNormals = bWedgeNormals && (!RawMesh.WedgeTangentZ[WedgeIdx].IsNearlyZero());
}
if (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0)
{
bWedgeTSpace = true;
for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeTangentX.Num()
&& WedgeIdx < RawMesh.WedgeTangentY.Num(); ++WedgeIdx)
{
bWedgeTSpace = bWedgeTSpace && (!RawMesh.WedgeTangentX[WedgeIdx].IsNearlyZero()) && (!RawMesh.WedgeTangentY[WedgeIdx].IsNearlyZero());
}
}
// Allocate storage for tangents if none were provided, and calculate normals for MikkTSpace.
if (RawMesh.WedgeTangentZ.Num() != NumWedges || !bWedgeNormals)
{
// normals are not included, so we should calculate them
RawMesh.WedgeTangentZ.Empty(NumWedges);
RawMesh.WedgeTangentZ.AddZeroed(NumWedges);
// we need to calculate normals for MikkTSpace
UE_LOG(LogMeshUtilities, Log, TEXT("Invalid vertex normals found for mesh. Forcing recomputation of vertex normals for MikkTSpace. Fix mesh or disable \"Use MikkTSpace Tangent Space\" to avoid forced recomputation of normals."));
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
int32 WedgeOffset = FaceIndex * 3;
FVector CornerPositions[3];
FVector CornerNormal[3];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerNormal[CornerIndex] = FVector::ZeroVector;
CornerPositions[CornerIndex] = GetPositionForWedge(RawMesh, WedgeOffset + CornerIndex);
RelevantFacesForCorner[CornerIndex].Reset();
}
// Don't process degenerate triangles.
if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
|| PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
|| PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
{
continue;
}
// No need to process triangles if tangents already exist.
bool bCornerHasNormal[3] = { 0 };
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
bCornerHasNormal[CornerIndex] = !RawMesh.WedgeTangentZ[WedgeOffset + CornerIndex].IsZero();
}
if (bCornerHasNormal[0] && bCornerHasNormal[1] && bCornerHasNormal[2])
{
continue;
}
// Start building a list of faces adjacent to this face.
AdjacentFaces.Reset();
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
int32 ThisCornerIndex = WedgeOffset + CornerIndex;
DupVerts.Reset();
OverlappingCorners.MultiFind(ThisCornerIndex, DupVerts);
DupVerts.Add(ThisCornerIndex); // I am a "dup" of myself
for (int32 k = 0; k < DupVerts.Num(); k++)
{
AdjacentFaces.AddUnique(DupVerts[k] / 3);
}
}
// We need to sort these here because the criteria for point equality is
// exact, so we must ensure the exact same order for all dups.
AdjacentFaces.Sort();
// Process adjacent faces
for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
{
int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
for (int32 OurCornerIndex = 0; OurCornerIndex < 3; OurCornerIndex++)
{
if (bCornerHasNormal[OurCornerIndex])
continue;
FFanFace NewFanFace;
int32 CommonIndexCount = 0;
// Check for vertices in common.
if (FaceIndex == OtherFaceIndex)
{
CommonIndexCount = 3;
NewFanFace.LinkedVertexIndex = OurCornerIndex;
}
else
{
// Check matching vertices against main vertex .
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
if (PointsEqual(
CornerPositions[OurCornerIndex],
GetPositionForWedge(RawMesh, OtherFaceIndex * 3 + OtherCornerIndex),
ComparisonThreshold
))
{
CommonIndexCount++;
NewFanFace.LinkedVertexIndex = OtherCornerIndex;
}
}
}
// Add if connected by at least one point. Smoothing matches are considered later.
if (CommonIndexCount > 0)
{
NewFanFace.FaceIndex = OtherFaceIndex;
NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
NewFanFace.bBlendTangents = NewFanFace.bFilled;
NewFanFace.bBlendNormals = NewFanFace.bFilled;
RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
}
}
}
// Find true relevance of faces for a vertex normal by traversing
// smoothing-group-compatible connected triangle fans around common vertices.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasNormal[CornerIndex])
continue;
int32 NewConnections;
do
{
NewConnections = 0;
for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
{
FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
// The vertex' own face is initially the only face with bFilled == true.
if (OtherFace.bFilled)
{
for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
{
FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
{
if ((NextFaceIndex != OtherFaceIdx)
&& (RawMesh.FaceSmoothingMasks[NextFace.FaceIndex] & RawMesh.FaceSmoothingMasks[OtherFace.FaceIndex]))
{
int32 CommonVertices = 0;
int32 CommonNormalVertices = 0;
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
for (int32 NextCornerIndex = 0; NextCornerIndex < 3; NextCornerIndex++)
{
int32 NextVertexIndex = RawMesh.WedgeIndices[NextFace.FaceIndex * 3 + NextCornerIndex];
int32 OtherVertexIndex = RawMesh.WedgeIndices[OtherFace.FaceIndex * 3 + OtherCornerIndex];
if (PointsEqual(
RawMesh.VertexPositions[NextVertexIndex],
RawMesh.VertexPositions[OtherVertexIndex],
ComparisonThreshold))
{
CommonVertices++;
if (bBlendOverlappingNormals
|| NextVertexIndex == OtherVertexIndex)
{
CommonNormalVertices++;
}
}
}
}
// Flood fill faces with more than one common vertices which must be touching edges.
if (CommonVertices > 1)
{
NextFace.bFilled = true;
NextFace.bBlendNormals = (CommonNormalVertices > 1);
NewConnections++;
}
}
}
}
}
}
}
while (NewConnections > 0);
}
// Vertex normal construction.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasNormal[CornerIndex])
{
CornerNormal[CornerIndex] = RawMesh.WedgeTangentZ[WedgeOffset + CornerIndex];
}
else
{
for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
{
FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
if (RelevantFace.bFilled)
{
int32 OtherFaceIndex = RelevantFace.FaceIndex;
if (RelevantFace.bBlendNormals)
{
CornerNormal[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
}
}
}
if (!RawMesh.WedgeTangentZ[WedgeOffset + CornerIndex].IsZero())
{
CornerNormal[CornerIndex] = RawMesh.WedgeTangentZ[WedgeOffset + CornerIndex];
}
}
}
// Normalization.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerNormal[CornerIndex].Normalize();
}
// Copy back to the mesh.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
RawMesh.WedgeTangentZ[WedgeOffset + CornerIndex] = CornerNormal[CornerIndex];
}
}
}
if (RawMesh.WedgeTangentX.Num() != NumWedges)
{
RawMesh.WedgeTangentX.Empty(NumWedges);
RawMesh.WedgeTangentX.AddZeroed(NumWedges);
}
if (RawMesh.WedgeTangentY.Num() != NumWedges)
{
RawMesh.WedgeTangentY.Empty(NumWedges);
RawMesh.WedgeTangentY.AddZeroed(NumWedges);
}
if (!bWedgeTSpace)
{
// we can use mikktspace to calculate the tangents
SMikkTSpaceInterface MikkTInterface;
MikkTInterface.m_getNormal = MikkGetNormal;
MikkTInterface.m_getNumFaces = MikkGetNumFaces;
MikkTInterface.m_getNumVerticesOfFace = MikkGetNumVertsOfFace;
MikkTInterface.m_getPosition = MikkGetPosition;
MikkTInterface.m_getTexCoord = MikkGetTexCoord;
MikkTInterface.m_setTSpaceBasic = MikkSetTSpaceBasic;
MikkTInterface.m_setTSpace = nullptr;
SMikkTSpaceContext MikkTContext;
MikkTContext.m_pInterface = &MikkTInterface;
MikkTContext.m_pUserData = (void*)(&RawMesh);
MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
genTangSpaceDefault(&MikkTContext);
}
check(RawMesh.WedgeTangentX.Num() == NumWedges);
check(RawMesh.WedgeTangentY.Num() == NumWedges);
check(RawMesh.WedgeTangentZ.Num() == NumWedges);
}
static void BuildDepthOnlyIndexBuffer(
TArray<uint32>& OutDepthIndices,
const TArray<FStaticMeshBuildVertex>& InVertices,
const TArray<uint32>& InIndices,
const TArray<FStaticMeshSection>& InSections
)
{
int32 NumVertices = InVertices.Num();
if (InIndices.Num() <= 0 || NumVertices <= 0)
{
OutDepthIndices.Empty();
return;
}
// Create a mapping of index -> first overlapping index to accelerate the construction of the shadow index buffer.
TArray<FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Empty(NumVertices);
for (int32 VertIndex = 0; VertIndex < NumVertices; VertIndex++)
{
new(VertIndexAndZ)FIndexAndZ(VertIndex, InVertices[VertIndex].Position);
}
VertIndexAndZ.Sort(FCompareIndexAndZ());
// Setup the index map. 0xFFFFFFFF == not set.
TArray<uint32> IndexMap;
IndexMap.AddUninitialized(NumVertices);
FMemory::Memset(IndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32));
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
uint32 SrcIndex = VertIndexAndZ[i].Index;
float Z = VertIndexAndZ[i].Z;
IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], SrcIndex);
// Search forward since we add pairs both ways.
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - Z) > THRESH_POINTS_ARE_SAME * 4.01f)
break; // can't be any more dups
uint32 OtherIndex = VertIndexAndZ[j].Index;
if (PointsEqual(InVertices[SrcIndex].Position, InVertices[OtherIndex].Position,/*bUseEpsilonCompare=*/ false))
{
IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], OtherIndex);
IndexMap[OtherIndex] = FMath::Min(IndexMap[OtherIndex], SrcIndex);
}
}
}
// Build the depth-only index buffer by remapping all indices to the first overlapping
// vertex in the vertex buffer.
OutDepthIndices.Empty();
for (int32 SectionIndex = 0; SectionIndex < InSections.Num(); ++SectionIndex)
{
const FStaticMeshSection& Section = InSections[SectionIndex];
int32 FirstIndex = Section.FirstIndex;
int32 LastIndex = FirstIndex + Section.NumTriangles * 3;
for (int32 SrcIndex = FirstIndex; SrcIndex < LastIndex; ++SrcIndex)
{
uint32 VertIndex = InIndices[SrcIndex];
OutDepthIndices.Add(IndexMap[VertIndex]);
}
}
}
static float GetComparisonThreshold(FMeshBuildSettings const& BuildSettings)
{
return BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
}
/*------------------------------------------------------------------------------
Static mesh building.
------------------------------------------------------------------------------*/
static FStaticMeshBuildVertex BuildStaticMeshVertex(FRawMesh const& RawMesh, int32 WedgeIndex, FVector BuildScale)
{
FStaticMeshBuildVertex Vertex;
Vertex.Position = GetPositionForWedge(RawMesh, WedgeIndex) * BuildScale;
const FMatrix ScaleMatrix = FScaleMatrix(BuildScale).Inverse().GetTransposed();
Vertex.TangentX = ScaleMatrix.TransformVector(RawMesh.WedgeTangentX[WedgeIndex]).GetSafeNormal();
Vertex.TangentY = ScaleMatrix.TransformVector(RawMesh.WedgeTangentY[WedgeIndex]).GetSafeNormal();
Vertex.TangentZ = ScaleMatrix.TransformVector(RawMesh.WedgeTangentZ[WedgeIndex]).GetSafeNormal();
if (RawMesh.WedgeColors.IsValidIndex(WedgeIndex))
{
Vertex.Color = RawMesh.WedgeColors[WedgeIndex];
}
else
{
Vertex.Color = FColor::White;
}
int32 NumTexCoords = FMath::Min<int32>(MAX_MESH_TEXTURE_COORDS, MAX_STATIC_TEXCOORDS);
for (int32 i = 0; i < NumTexCoords; ++i)
{
if (RawMesh.WedgeTexCoords[i].IsValidIndex(WedgeIndex))
{
Vertex.UVs[i] = RawMesh.WedgeTexCoords[i][WedgeIndex];
}
else
{
Vertex.UVs[i] = FVector2D(0.0f, 0.0f);
}
}
return Vertex;
}
static bool AreVerticesEqual(
FStaticMeshBuildVertex const& A,
FStaticMeshBuildVertex const& B,
float ComparisonThreshold
)
{
if (!PointsEqual(A.Position, B.Position, ComparisonThreshold)
|| !NormalsEqual(A.TangentX, B.TangentX)
|| !NormalsEqual(A.TangentY, B.TangentY)
|| !NormalsEqual(A.TangentZ, B.TangentZ)
|| A.Color != B.Color)
{
return false;
}
// UVs
for (int32 UVIndex = 0; UVIndex < MAX_STATIC_TEXCOORDS; UVIndex++)
{
if (!UVsEqual(A.UVs[UVIndex], B.UVs[UVIndex]))
{
return false;
}
}
return true;
}
void FMeshUtilities::BuildStaticMeshVertexAndIndexBuffers(
TArray<FStaticMeshBuildVertex>& OutVertices,
TArray<TArray<uint32> >& OutPerSectionIndices,
TArray<int32>& OutWedgeMap,
const FRawMesh& RawMesh,
const TMultiMap<int32, int32>& OverlappingCorners,
const TMap<uint32, uint32>& MaterialToSectionMapping,
float ComparisonThreshold,
FVector BuildScale,
int32 ImportVersion
)
{
TMap<int32, int32> FinalVerts;
TArray<int32> DupVerts;
int32 NumFaces = RawMesh.WedgeIndices.Num() / 3;
// Process each face, build vertex buffer and per-section index buffers.
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
int32 VertexIndices[3];
FVector CornerPositions[3];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerPositions[CornerIndex] = GetPositionForWedge(RawMesh, FaceIndex * 3 + CornerIndex);
}
// Don't process degenerate triangles.
if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
|| PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
|| PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
{
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
OutWedgeMap.Add(INDEX_NONE);
}
continue;
}
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
int32 WedgeIndex = FaceIndex * 3 + CornerIndex;
FStaticMeshBuildVertex ThisVertex = BuildStaticMeshVertex(RawMesh, WedgeIndex, BuildScale);
DupVerts.Reset();
OverlappingCorners.MultiFind(WedgeIndex, DupVerts);
DupVerts.Sort();
int32 Index = INDEX_NONE;
for (int32 k = 0; k < DupVerts.Num(); k++)
{
if (DupVerts[k] >= WedgeIndex)
{
// the verts beyond me haven't been placed yet, so these duplicates are not relevant
break;
}
int32 *Location = FinalVerts.Find(DupVerts[k]);
if (Location != NULL
&& AreVerticesEqual(ThisVertex, OutVertices[*Location], ComparisonThreshold))
{
Index = *Location;
break;
}
}
if (Index == INDEX_NONE)
{
Index = OutVertices.Add(ThisVertex);
FinalVerts.Add(WedgeIndex, Index);
}
VertexIndices[CornerIndex] = Index;
}
// Reject degenerate triangles.
if (VertexIndices[0] == VertexIndices[1]
|| VertexIndices[1] == VertexIndices[2]
|| VertexIndices[0] == VertexIndices[2])
{
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
OutWedgeMap.Add(INDEX_NONE);
}
continue;
}
// Put the indices in the material index buffer.
uint32 SectionIndex = 0;
if (ImportVersion < RemoveStaticMeshSkinxxWorkflow)
{
SectionIndex = FMath::Clamp(RawMesh.FaceMaterialIndices[FaceIndex], 0, OutPerSectionIndices.Num() - 1);
}
else
{
SectionIndex = MaterialToSectionMapping.FindChecked(RawMesh.FaceMaterialIndices[FaceIndex]);
}
TArray<uint32>& SectionIndices = OutPerSectionIndices[SectionIndex];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
SectionIndices.Add(VertexIndices[CornerIndex]);
OutWedgeMap.Add(VertexIndices[CornerIndex]);
}
}
}
void FMeshUtilities::CacheOptimizeVertexAndIndexBuffer(
TArray<FStaticMeshBuildVertex>& Vertices,
TArray<TArray<uint32> >& PerSectionIndices,
TArray<int32>& WedgeMap
)
{
// Copy the vertices since we will be reordering them
TArray<FStaticMeshBuildVertex> OriginalVertices = Vertices;
// Initialize a cache that stores which indices have been assigned
TArray<int32> IndexCache;
IndexCache.AddUninitialized(Vertices.Num());
FMemory::Memset(IndexCache.GetData(), INDEX_NONE, IndexCache.Num() * IndexCache.GetTypeSize());
int32 NextAvailableIndex = 0;
// Iterate through the section index buffers,
// Optimizing index order for the post transform cache (minimizes the number of vertices transformed),
// And vertex order for the pre transform cache (minimizes the amount of vertex data fetched by the GPU).
for (int32 SectionIndex = 0; SectionIndex < PerSectionIndices.Num(); SectionIndex++)
{
TArray<uint32>& Indices = PerSectionIndices[SectionIndex];
if (Indices.Num())
{
// Optimize the index buffer for the post transform cache with.
CacheOptimizeIndexBuffer(Indices);
// Copy the index buffer since we will be reordering it
TArray<uint32> OriginalIndices = Indices;
// Go through the indices and assign them new values that are coherent where possible
for (int32 Index = 0; Index < Indices.Num(); Index++)
{
const int32 CachedIndex = IndexCache[OriginalIndices[Index]];
if (CachedIndex == INDEX_NONE)
{
// No new index has been allocated for this existing index, assign a new one
Indices[Index] = NextAvailableIndex;
// Mark what this index has been assigned to
IndexCache[OriginalIndices[Index]] = NextAvailableIndex;
NextAvailableIndex++;
}
else
{
// Reuse an existing index assignment
Indices[Index] = CachedIndex;
}
// Reorder the vertices based on the new index assignment
Vertices[Indices[Index]] = OriginalVertices[OriginalIndices[Index]];
}
}
}
for (int32 i = 0; i < WedgeMap.Num(); i++)
{
int32 MappedIndex = WedgeMap[i];
if (MappedIndex != INDEX_NONE)
{
WedgeMap[i] = IndexCache[MappedIndex];
}
}
}
class FStaticMeshUtilityBuilder
{
public:
FStaticMeshUtilityBuilder() : Stage(EStage::Uninit), NumValidLODs(0) {}
bool GatherSourceMeshesPerLOD(TArray<FStaticMeshSourceModel>& SourceModels, IMeshReduction* MeshReduction, ELightmapUVVersion LightmapUVVersion)
{
check(Stage == EStage::Uninit);
// Gather source meshes for each LOD.
for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); ++LODIndex)
{
FStaticMeshSourceModel& SrcModel = SourceModels[LODIndex];
FRawMesh& RawMesh = *new(LODMeshes)FRawMesh;
TMultiMap<int32, int32>& OverlappingCorners = *new(LODOverlappingCorners)TMultiMap<int32, int32>;
if (!SrcModel.RawMeshBulkData->IsEmpty())
{
SrcModel.RawMeshBulkData->LoadRawMesh(RawMesh);
// Make sure the raw mesh is not irreparably malformed.
if (!RawMesh.IsValidOrFixable())
{
UE_LOG(LogMeshUtilities, Error, TEXT("Raw mesh is corrupt for LOD%d."), LODIndex);
return false;
}
LODBuildSettings[LODIndex] = SrcModel.BuildSettings;
float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[LODIndex]);
int32 NumWedges = RawMesh.WedgeIndices.Num();
// Find overlapping corners to accelerate adjacency.
FindOverlappingCorners(OverlappingCorners, RawMesh, ComparisonThreshold);
// Figure out if we should recompute normals and tangents.
bool bRecomputeNormals = SrcModel.BuildSettings.bRecomputeNormals || RawMesh.WedgeTangentZ.Num() != NumWedges;
bool bRecomputeTangents = SrcModel.BuildSettings.bRecomputeTangents || RawMesh.WedgeTangentX.Num() != NumWedges || RawMesh.WedgeTangentY.Num() != NumWedges;
// Dump normals and tangents if we are recomputing them.
if (bRecomputeTangents)
{
RawMesh.WedgeTangentX.Empty(NumWedges);
RawMesh.WedgeTangentX.AddZeroed(NumWedges);
RawMesh.WedgeTangentY.Empty(NumWedges);
RawMesh.WedgeTangentY.AddZeroed(NumWedges);
}
if (bRecomputeNormals)
{
RawMesh.WedgeTangentZ.Empty(NumWedges);
RawMesh.WedgeTangentZ.AddZeroed(NumWedges);
}
// Compute any missing tangents.
{
// Static meshes always blend normals of overlapping corners.
uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
if (SrcModel.BuildSettings.bRemoveDegenerates)
{
// If removing degenerate triangles, ignore them when computing tangents.
TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
}
//MikkTSpace should be use only when the user want to recompute the normals or tangents otherwise should always fallback on builtin
if (SrcModel.BuildSettings.bUseMikkTSpace && (SrcModel.BuildSettings.bRecomputeNormals || SrcModel.BuildSettings.bRecomputeTangents))
{
ComputeTangents_MikkTSpace(RawMesh, OverlappingCorners, TangentOptions);
}
else
{
ComputeTangents(RawMesh, OverlappingCorners, TangentOptions);
}
}
// At this point the mesh will have valid tangents.
check(RawMesh.WedgeTangentX.Num() == NumWedges);
check(RawMesh.WedgeTangentY.Num() == NumWedges);
check(RawMesh.WedgeTangentZ.Num() == NumWedges);
// Generate lightmap UVs
if (SrcModel.BuildSettings.bGenerateLightmapUVs)
{
if (RawMesh.WedgeTexCoords[SrcModel.BuildSettings.SrcLightmapIndex].Num() == 0)
{
SrcModel.BuildSettings.SrcLightmapIndex = 0;
}
FLayoutUV Packer(&RawMesh, SrcModel.BuildSettings.SrcLightmapIndex, SrcModel.BuildSettings.DstLightmapIndex, SrcModel.BuildSettings.MinLightmapResolution);
Packer.SetVersion(LightmapUVVersion);
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
}
}
HasRawMesh[LODIndex] = true;
}
else if (LODIndex > 0 && MeshReduction)
{
// If a raw mesh is not explicitly provided, use the raw mesh of the
// next highest LOD.
RawMesh = LODMeshes[LODIndex - 1];
OverlappingCorners = LODOverlappingCorners[LODIndex - 1];
LODBuildSettings[LODIndex] = LODBuildSettings[LODIndex - 1];
HasRawMesh[LODIndex] = false;
}
}
check(LODMeshes.Num() == SourceModels.Num());
check(LODOverlappingCorners.Num() == SourceModels.Num());
// Bail if there is no raw mesh data from which to build a renderable mesh.
if (LODMeshes.Num() == 0 || LODMeshes[0].WedgeIndices.Num() == 0)
{
return false;
}
Stage = EStage::Gathered;
return true;
}
bool ReduceLODs(TArray<FStaticMeshSourceModel>& SourceModels, const FStaticMeshLODGroup& LODGroup, IMeshReduction* MeshReduction, bool& bOutWasReduced)
{
check(Stage == EStage::Gathered);
// Reduce each LOD mesh according to its reduction settings.
for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); ++LODIndex)
{
const FStaticMeshSourceModel& SrcModel = SourceModels[LODIndex];
FMeshReductionSettings ReductionSettings = LODGroup.GetSettings(SrcModel.ReductionSettings, LODIndex);
LODMaxDeviation[NumValidLODs] = 0.0f;
if (LODIndex != NumValidLODs)
{
LODBuildSettings[NumValidLODs] = LODBuildSettings[LODIndex];
LODOverlappingCorners[NumValidLODs] = LODOverlappingCorners[LODIndex];
}
if (MeshReduction && (ReductionSettings.PercentTriangles < 1.0f || ReductionSettings.MaxDeviation > 0.0f))
{
FRawMesh& InMesh = LODMeshes[ReductionSettings.BaseLODModel];
FRawMesh& DestMesh = LODMeshes[NumValidLODs];
TMultiMap<int32, int32>& InOverlappingCorners = LODOverlappingCorners[ReductionSettings.BaseLODModel];
TMultiMap<int32, int32>& DestOverlappingCorners = LODOverlappingCorners[NumValidLODs];
MeshReduction->Reduce(DestMesh, LODMaxDeviation[NumValidLODs], InMesh, InOverlappingCorners, ReductionSettings);
if (DestMesh.WedgeIndices.Num() > 0 && !DestMesh.IsValid())
{
UE_LOG(LogMeshUtilities, Error, TEXT("Mesh reduction produced a corrupt mesh for LOD%d"), LODIndex);
return false;
}
bOutWasReduced = true;
// Recompute adjacency information.
DestOverlappingCorners.Reset();
float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[NumValidLODs]);
FindOverlappingCorners(DestOverlappingCorners, DestMesh, ComparisonThreshold);
}
if (LODMeshes[NumValidLODs].WedgeIndices.Num() > 0)
{
NumValidLODs++;
}
}
if (NumValidLODs < 1)
{
return false;
}
Stage = EStage::Reduce;
return true;
}
bool GenerateRenderingMeshes(FMeshUtilities& MeshUtilities, FStaticMeshRenderData& OutRenderData, TArray<FStaticMeshSourceModel>& InOutModels, int32 ImportVersion)
{
check(Stage == EStage::Reduce);
// Generate per-LOD rendering data.
OutRenderData.AllocateLODResources(NumValidLODs);
for (int32 LODIndex = 0; LODIndex < NumValidLODs; ++LODIndex)
{
FStaticMeshLODResources& LODModel = OutRenderData.LODResources[LODIndex];
FRawMesh& RawMesh = LODMeshes[LODIndex];
LODModel.MaxDeviation = LODMaxDeviation[LODIndex];
TArray<FStaticMeshBuildVertex> Vertices;
TArray<TArray<uint32> > PerSectionIndices;
TMap<uint32, uint32> MaterialToSectionMapping;
// Find out how many sections are in the mesh.
TArray<int32> MaterialIndices;
for ( const int32 MaterialIndex : RawMesh.FaceMaterialIndices )
{
// Find all unique material indices
MaterialIndices.AddUnique(MaterialIndex);
}
// Need X number of sections for X number of material indices
//for (const int32 MaterialIndex : MaterialIndices)
for ( int32 Index = 0; Index < MaterialIndices.Num(); ++Index)
{
const int32 MaterialIndex = MaterialIndices[Index];
FStaticMeshSection* Section = new(LODModel.Sections) FStaticMeshSection();
Section->MaterialIndex = MaterialIndex;
if (ImportVersion < RemoveStaticMeshSkinxxWorkflow)
{
MaterialToSectionMapping.Add(MaterialIndex, MaterialIndex);
}
else
{
MaterialToSectionMapping.Add(MaterialIndex, Index);
}
new(PerSectionIndices)TArray<uint32>;
}
// Build and cache optimize vertex and index buffers.
{
// TODO_STATICMESH: The wedge map is only valid for LODIndex 0 if no reduction has been performed.
// We can compute an approximate one instead for other LODs.
TArray<int32> TempWedgeMap;
TArray<int32>& WedgeMap = (LODIndex == 0 && InOutModels[0].ReductionSettings.PercentTriangles >= 1.0f) ? OutRenderData.WedgeMap : TempWedgeMap;
float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[LODIndex]);
MeshUtilities.BuildStaticMeshVertexAndIndexBuffers(Vertices, PerSectionIndices, WedgeMap, RawMesh, LODOverlappingCorners[LODIndex], MaterialToSectionMapping, ComparisonThreshold, LODBuildSettings[LODIndex].BuildScale3D, ImportVersion);
check(WedgeMap.Num() == RawMesh.WedgeIndices.Num());
if (RawMesh.WedgeIndices.Num() < 100000 * 3)
{
MeshUtilities.CacheOptimizeVertexAndIndexBuffer(Vertices, PerSectionIndices, WedgeMap);
check(WedgeMap.Num() == RawMesh.WedgeIndices.Num());
}
}
verifyf(Vertices.Num() != 0, TEXT("No valid vertices found for the mesh."));
// Initialize the vertex buffer.
int32 NumTexCoords = ComputeNumTexCoords(RawMesh, MAX_STATIC_TEXCOORDS);
LODModel.VertexBuffer.SetUseHighPrecisionTangentBasis(LODBuildSettings[LODIndex].bUseHighPrecisionTangentBasis);
LODModel.VertexBuffer.SetUseFullPrecisionUVs(LODBuildSettings[LODIndex].bUseFullPrecisionUVs);
LODModel.VertexBuffer.Init(Vertices, NumTexCoords);
LODModel.PositionVertexBuffer.Init(Vertices);
LODModel.ColorVertexBuffer.Init(Vertices);
// Concatenate the per-section index buffers.
TArray<uint32> CombinedIndices;
bool bNeeds32BitIndices = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
TArray<uint32> const& SectionIndices = PerSectionIndices[SectionIndex];
Section.FirstIndex = 0;
Section.NumTriangles = 0;
Section.MinVertexIndex = 0;
Section.MaxVertexIndex = 0;
if (SectionIndices.Num())
{
Section.FirstIndex = CombinedIndices.Num();
Section.NumTriangles = SectionIndices.Num() / 3;
CombinedIndices.AddUninitialized(SectionIndices.Num());
uint32* DestPtr = &CombinedIndices[Section.FirstIndex];
uint32 const* SrcPtr = SectionIndices.GetData();
Section.MinVertexIndex = *SrcPtr;
Section.MaxVertexIndex = *SrcPtr;
for (int32 Index = 0; Index < SectionIndices.Num(); Index++)
{
uint32 VertIndex = *SrcPtr++;
bNeeds32BitIndices |= (VertIndex > MAX_uint16);
Section.MinVertexIndex = FMath::Min<uint32>(VertIndex, Section.MinVertexIndex);
Section.MaxVertexIndex = FMath::Max<uint32>(VertIndex, Section.MaxVertexIndex);
*DestPtr++ = VertIndex;
}
}
}
LODModel.IndexBuffer.SetIndices(CombinedIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
// Build the reversed index buffer.
if (InOutModels[0].BuildSettings.bBuildReversedIndexBuffer)
{
TArray<uint32> InversedIndices;
const int32 IndexCount = CombinedIndices.Num();
InversedIndices.AddUninitialized(IndexCount);
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); ++SectionIndex)
{
const FStaticMeshSection& SectionInfo = LODModel.Sections[SectionIndex];
const int32 SectionIndexCount = SectionInfo.NumTriangles * 3;
for (int32 i = 0; i < SectionIndexCount; ++i)
{
InversedIndices[SectionInfo.FirstIndex + i] = CombinedIndices[SectionInfo.FirstIndex + SectionIndexCount - 1 - i];
}
}
LODModel.ReversedIndexBuffer.SetIndices(InversedIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
}
// Build the depth-only index buffer.
TArray<uint32> DepthOnlyIndices;
{
BuildDepthOnlyIndexBuffer(
DepthOnlyIndices,
Vertices,
CombinedIndices,
LODModel.Sections
);
if (DepthOnlyIndices.Num() < 50000 * 3)
{
MeshUtilities.CacheOptimizeIndexBuffer(DepthOnlyIndices);
}
LODModel.DepthOnlyIndexBuffer.SetIndices(DepthOnlyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
}
// Build the inversed depth only index buffer.
if (InOutModels[0].BuildSettings.bBuildReversedIndexBuffer)
{
TArray<uint32> ReversedDepthOnlyIndices;
const int32 IndexCount = DepthOnlyIndices.Num();
ReversedDepthOnlyIndices.AddUninitialized(IndexCount);
for (int32 i = 0; i < IndexCount; ++i)
{
ReversedDepthOnlyIndices[i] = DepthOnlyIndices[IndexCount - 1 - i];
}
LODModel.ReversedDepthOnlyIndexBuffer.SetIndices(ReversedDepthOnlyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
}
// Build a list of wireframe edges in the static mesh.
{
TArray<FMeshEdge> Edges;
TArray<uint32> WireframeIndices;
FStaticMeshEdgeBuilder(CombinedIndices, Vertices, Edges).FindEdges();
WireframeIndices.Empty(2 * Edges.Num());
for (int32 EdgeIndex = 0; EdgeIndex < Edges.Num(); EdgeIndex++)
{
FMeshEdge& Edge = Edges[EdgeIndex];
WireframeIndices.Add(Edge.Vertices[0]);
WireframeIndices.Add(Edge.Vertices[1]);
}
LODModel.WireframeIndexBuffer.SetIndices(WireframeIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
}
// Build the adjacency index buffer used for tessellation.
if (InOutModels[0].BuildSettings.bBuildAdjacencyBuffer)
{
TArray<uint32> AdjacencyIndices;
BuildStaticAdjacencyIndexBuffer(
LODModel.PositionVertexBuffer,
LODModel.VertexBuffer,
CombinedIndices,
AdjacencyIndices
);
LODModel.AdjacencyIndexBuffer.SetIndices(AdjacencyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
}
}
// Copy the original material indices to fixup meshes before compacting of materials was done.
if (NumValidLODs > 0)
{
OutRenderData.MaterialIndexToImportIndex = LODMeshes[0].MaterialIndexToImportIndex;
}
// Calculate the bounding box.
FBox BoundingBox(ForceInit);
FPositionVertexBuffer& BasePositionVertexBuffer = OutRenderData.LODResources[0].PositionVertexBuffer;
for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
{
BoundingBox += BasePositionVertexBuffer.VertexPosition(VertexIndex);
}
BoundingBox.GetCenterAndExtents(OutRenderData.Bounds.Origin, OutRenderData.Bounds.BoxExtent);
// Calculate the bounding sphere, using the center of the bounding box as the origin.
OutRenderData.Bounds.SphereRadius = 0.0f;
for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
{
OutRenderData.Bounds.SphereRadius = FMath::Max(
(BasePositionVertexBuffer.VertexPosition(VertexIndex) - OutRenderData.Bounds.Origin).Size(),
OutRenderData.Bounds.SphereRadius
);
}
Stage = EStage::GenerateRendering;
return true;
}
bool ReplaceRawMeshModels(TArray<FStaticMeshSourceModel>& SourceModels)
{
check(Stage == EStage::Reduce);
check(HasRawMesh[0]);
check(SourceModels.Num() >= NumValidLODs);
bool bDirty = false;
for (int32 Index = 1; Index < NumValidLODs; ++Index)
{
if (!HasRawMesh[Index])
{
SourceModels[Index].RawMeshBulkData->SaveRawMesh(LODMeshes[Index]);
bDirty = true;
}
}
Stage = EStage::ReplaceRaw;
return true;
}
private:
enum class EStage
{
Uninit,
Gathered,
Reduce,
GenerateRendering,
ReplaceRaw,
};
EStage Stage;
int32 NumValidLODs;
TIndirectArray<FRawMesh> LODMeshes;
TIndirectArray<TMultiMap<int32, int32> > LODOverlappingCorners;
float LODMaxDeviation[MAX_STATIC_MESH_LODS];
FMeshBuildSettings LODBuildSettings[MAX_STATIC_MESH_LODS];
bool HasRawMesh[MAX_STATIC_MESH_LODS];
};
bool FMeshUtilities::BuildStaticMesh(FStaticMeshRenderData& OutRenderData, TArray<FStaticMeshSourceModel>& SourceModels, const FStaticMeshLODGroup& LODGroup, int32 LightmapUVVersion, int32 ImportVersion)
{
FStaticMeshUtilityBuilder Builder;
if (!Builder.GatherSourceMeshesPerLOD(SourceModels, StaticMeshReduction, (ELightmapUVVersion)LightmapUVVersion))
{
return false;
}
bool bWasReduced = false;
if (!Builder.ReduceLODs(SourceModels, LODGroup, StaticMeshReduction, bWasReduced))
{
return false;
}
return Builder.GenerateRenderingMeshes(*this, OutRenderData, SourceModels, ImportVersion);
}
bool FMeshUtilities::GenerateStaticMeshLODs(TArray<FStaticMeshSourceModel>& Models, const FStaticMeshLODGroup& LODGroup, int32 LightmapUVVersion)
{
FStaticMeshUtilityBuilder Builder;
if (!Builder.GatherSourceMeshesPerLOD(Models, StaticMeshReduction, (ELightmapUVVersion)LightmapUVVersion))
{
return false;
}
bool bWasReduced = false;
if (!Builder.ReduceLODs(Models, LODGroup, StaticMeshReduction, bWasReduced))
{
return false;
}
if (bWasReduced)
{
return Builder.ReplaceRawMeshModels(Models);
}
return false;
}
class IMeshBuildData
{
public:
virtual ~IMeshBuildData() { }
virtual uint32 GetWedgeIndex(uint32 FaceIndex, uint32 TriIndex) = 0;
virtual uint32 GetVertexIndex(uint32 WedgeIndex) = 0;
virtual uint32 GetVertexIndex(uint32 FaceIndex, uint32 TriIndex) = 0;
virtual FVector GetVertexPosition(uint32 WedgeIndex) = 0;
virtual FVector GetVertexPosition(uint32 FaceIndex, uint32 TriIndex) = 0;
virtual FVector2D GetVertexUV(uint32 FaceIndex, uint32 TriIndex, uint32 UVIndex) = 0;
virtual uint32 GetFaceSmoothingGroups(uint32 FaceIndex) = 0;
virtual uint32 GetNumFaces() = 0;
virtual uint32 GetNumWedges() = 0;
virtual TArray<FVector>& GetTangentArray(uint32 Axis) = 0;
virtual void ValidateTangentArraySize() = 0;
virtual SMikkTSpaceInterface* GetMikkTInterface() = 0;
virtual void* GetMikkTUserData() = 0;
const IMeshUtilities::MeshBuildOptions& BuildOptions;
TArray<FText>* OutWarningMessages;
TArray<FName>* OutWarningNames;
bool bTooManyVerts;
protected:
IMeshBuildData(
const IMeshUtilities::MeshBuildOptions& InBuildOptions,
TArray<FText>* InWarningMessages,
TArray<FName>* InWarningNames)
: BuildOptions(InBuildOptions)
, OutWarningMessages(InWarningMessages)
, OutWarningNames(InWarningNames)
, bTooManyVerts(false)
{
}
};
class SkeletalMeshBuildData final : public IMeshBuildData
{
public:
SkeletalMeshBuildData(
FStaticLODModel& InLODModel,
const FReferenceSkeleton& InRefSkeleton,
const TArray<FVertInfluence>& InInfluences,
const TArray<FMeshWedge>& InWedges,
const TArray<FMeshFace>& InFaces,
const TArray<FVector>& InPoints,
const TArray<int32>& InPointToOriginalMap,
const IMeshUtilities::MeshBuildOptions& InBuildOptions,
TArray<FText>* InWarningMessages,
TArray<FName>* InWarningNames)
: IMeshBuildData(InBuildOptions, InWarningMessages, InWarningNames)
, MikkTUserData(InWedges, InFaces, InPoints, InBuildOptions.bComputeNormals, TangentX, TangentY, TangentZ)
, LODModel(InLODModel)
, RefSkeleton(InRefSkeleton)
, Influences(InInfluences)
, Wedges(InWedges)
, Faces(InFaces)
, Points(InPoints)
, PointToOriginalMap(InPointToOriginalMap)
{
MikkTInterface.m_getNormal = MikkGetNormal_Skeletal;
MikkTInterface.m_getNumFaces = MikkGetNumFaces_Skeletal;
MikkTInterface.m_getNumVerticesOfFace = MikkGetNumVertsOfFace_Skeletal;
MikkTInterface.m_getPosition = MikkGetPosition_Skeletal;
MikkTInterface.m_getTexCoord = MikkGetTexCoord_Skeletal;
MikkTInterface.m_setTSpaceBasic = MikkSetTSpaceBasic_Skeletal;
MikkTInterface.m_setTSpace = nullptr;
}
virtual uint32 GetWedgeIndex(uint32 FaceIndex, uint32 TriIndex) override
{
return Faces[FaceIndex].iWedge[TriIndex];
}
virtual uint32 GetVertexIndex(uint32 WedgeIndex) override
{
return Wedges[WedgeIndex].iVertex;
}
virtual uint32 GetVertexIndex(uint32 FaceIndex, uint32 TriIndex) override
{
return Wedges[Faces[FaceIndex].iWedge[TriIndex]].iVertex;
}
virtual FVector GetVertexPosition(uint32 WedgeIndex) override
{
return Points[Wedges[WedgeIndex].iVertex];
}
virtual FVector GetVertexPosition(uint32 FaceIndex, uint32 TriIndex) override
{
return Points[Wedges[Faces[FaceIndex].iWedge[TriIndex]].iVertex];
}
virtual FVector2D GetVertexUV(uint32 FaceIndex, uint32 TriIndex, uint32 UVIndex) override
{
return Wedges[Faces[FaceIndex].iWedge[TriIndex]].UVs[UVIndex];
}
virtual uint32 GetFaceSmoothingGroups(uint32 FaceIndex)
{
return Faces[FaceIndex].SmoothingGroups;
}
virtual uint32 GetNumFaces() override
{
return Faces.Num();
}
virtual uint32 GetNumWedges() override
{
return Wedges.Num();
}
virtual TArray<FVector>& GetTangentArray(uint32 Axis) override
{
if (Axis == 0)
{
return TangentX;
}
else if (Axis == 1)
{
return TangentY;
}
return TangentZ;
}
virtual void ValidateTangentArraySize() override
{
check(TangentX.Num() == Wedges.Num());
check(TangentY.Num() == Wedges.Num());
check(TangentZ.Num() == Wedges.Num());
}
virtual SMikkTSpaceInterface* GetMikkTInterface() override
{
return &MikkTInterface;
}
virtual void* GetMikkTUserData() override
{
return (void*)&MikkTUserData;
}
TArray<FVector> TangentX;
TArray<FVector> TangentY;
TArray<FVector> TangentZ;
TArray<FSkinnedMeshChunk*> Chunks;
SMikkTSpaceInterface MikkTInterface;
MikkTSpace_Skeletal_Mesh MikkTUserData;
FStaticLODModel& LODModel;
const FReferenceSkeleton& RefSkeleton;
const TArray<FVertInfluence>& Influences;
const TArray<FMeshWedge>& Wedges;
const TArray<FMeshFace>& Faces;
const TArray<FVector>& Points;
const TArray<int32>& PointToOriginalMap;
};
class FSkeletalMeshUtilityBuilder
{
public:
FSkeletalMeshUtilityBuilder()
: Stage(EStage::Uninit)
{
}
public:
void Skeletal_FindOverlappingCorners(
TMultiMap<int32, int32>& OutOverlappingCorners,
IMeshBuildData* BuildData,
float ComparisonThreshold
)
{
int32 NumFaces = BuildData->GetNumFaces();
int32 NumWedges = BuildData->GetNumWedges();
check(NumFaces * 3 <= NumWedges);
// Create a list of vertex Z/index pairs
TArray<FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Empty(NumWedges);
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
for (int32 TriIndex = 0; TriIndex < 3; ++TriIndex)
{
uint32 Index = BuildData->GetWedgeIndex(FaceIndex, TriIndex);
new(VertIndexAndZ)FIndexAndZ(Index, BuildData->GetVertexPosition(Index));
}
}
// Sort the vertices by z value
VertIndexAndZ.Sort(FCompareIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > ComparisonThreshold)
break; // can't be any more dups
FVector PositionA = BuildData->GetVertexPosition(VertIndexAndZ[i].Index);
FVector PositionB = BuildData->GetVertexPosition(VertIndexAndZ[j].Index);
if (PointsEqual(PositionA, PositionB, ComparisonThreshold))
{
OutOverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
OutOverlappingCorners.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
}
}
}
}
void Skeletal_ComputeTriangleTangents(
TArray<FVector>& TriangleTangentX,
TArray<FVector>& TriangleTangentY,
TArray<FVector>& TriangleTangentZ,
IMeshBuildData* BuildData,
float ComparisonThreshold
)
{
int32 NumTriangles = BuildData->GetNumFaces();
TriangleTangentX.Empty(NumTriangles);
TriangleTangentY.Empty(NumTriangles);
TriangleTangentZ.Empty(NumTriangles);
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
{
const int32 UVIndex = 0;
FVector P[3];
for (int32 i = 0; i < 3; ++i)
{
P[i] = BuildData->GetVertexPosition(TriangleIndex, i);
}
const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
FMatrix ParameterToLocal(
FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
FPlane(P[0].X, P[0].Y, P[0].Z, 0),
FPlane(0, 0, 0, 1)
);
FVector2D T1 = BuildData->GetVertexUV(TriangleIndex, 0, UVIndex);
FVector2D T2 = BuildData->GetVertexUV(TriangleIndex, 1, UVIndex);
FVector2D T3 = BuildData->GetVertexUV(TriangleIndex, 2, UVIndex);
FMatrix ParameterToTexture(
FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
FPlane(T1.X, T1.Y, 1, 0),
FPlane(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
TriangleTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
TriangleTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
TriangleTangentZ.Add(Normal);
FVector::CreateOrthonormalBasis(
TriangleTangentX[TriangleIndex],
TriangleTangentY[TriangleIndex],
TriangleTangentZ[TriangleIndex]
);
}
}
void Skeletal_ComputeTangents(
IMeshBuildData* BuildData,
TMultiMap<int32, int32> const& OverlappingCorners
)
{
bool bBlendOverlappingNormals = true;
bool bIgnoreDegenerateTriangles = BuildData->BuildOptions.bRemoveDegenerateTriangles;
float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
// Compute per-triangle tangents.
TArray<FVector> TriangleTangentX;
TArray<FVector> TriangleTangentY;
TArray<FVector> TriangleTangentZ;
Skeletal_ComputeTriangleTangents(
TriangleTangentX,
TriangleTangentY,
TriangleTangentZ,
BuildData,
bIgnoreDegenerateTriangles ? SMALL_NUMBER : 0.0f
);
TArray<FVector>& WedgeTangentX = BuildData->GetTangentArray(0);
TArray<FVector>& WedgeTangentY = BuildData->GetTangentArray(1);
TArray<FVector>& WedgeTangentZ = BuildData->GetTangentArray(2);
// Declare these out here to avoid reallocations.
TArray<FFanFace> RelevantFacesForCorner[3];
TArray<int32> AdjacentFaces;
TArray<int32> DupVerts;
int32 NumFaces = BuildData->GetNumFaces();
int32 NumWedges = BuildData->GetNumWedges();
check(NumFaces * 3 <= NumWedges);
// Allocate storage for tangents if none were provided.
if (WedgeTangentX.Num() != NumWedges)
{
WedgeTangentX.Empty(NumWedges);
WedgeTangentX.AddZeroed(NumWedges);
}
if (WedgeTangentY.Num() != NumWedges)
{
WedgeTangentY.Empty(NumWedges);
WedgeTangentY.AddZeroed(NumWedges);
}
if (WedgeTangentZ.Num() != NumWedges)
{
WedgeTangentZ.Empty(NumWedges);
WedgeTangentZ.AddZeroed(NumWedges);
}
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
int32 WedgeOffset = FaceIndex * 3;
FVector CornerPositions[3];
FVector CornerTangentX[3];
FVector CornerTangentY[3];
FVector CornerTangentZ[3];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerTangentX[CornerIndex] = FVector::ZeroVector;
CornerTangentY[CornerIndex] = FVector::ZeroVector;
CornerTangentZ[CornerIndex] = FVector::ZeroVector;
CornerPositions[CornerIndex] = BuildData->GetVertexPosition(FaceIndex, CornerIndex);
RelevantFacesForCorner[CornerIndex].Reset();
}
// Don't process degenerate triangles.
if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
|| PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
|| PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
{
continue;
}
// No need to process triangles if tangents already exist.
bool bCornerHasTangents[3] = { 0 };
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
bCornerHasTangents[CornerIndex] = !WedgeTangentX[WedgeOffset + CornerIndex].IsZero()
&& !WedgeTangentY[WedgeOffset + CornerIndex].IsZero()
&& !WedgeTangentZ[WedgeOffset + CornerIndex].IsZero();
}
if (bCornerHasTangents[0] && bCornerHasTangents[1] && bCornerHasTangents[2])
{
continue;
}
// Calculate smooth vertex normals.
float Determinant = FVector::Triple(
TriangleTangentX[FaceIndex],
TriangleTangentY[FaceIndex],
TriangleTangentZ[FaceIndex]
);
// Start building a list of faces adjacent to this face.
AdjacentFaces.Reset();
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
int32 ThisCornerIndex = WedgeOffset + CornerIndex;
DupVerts.Reset();
OverlappingCorners.MultiFind(ThisCornerIndex, DupVerts);
DupVerts.Add(ThisCornerIndex); // I am a "dup" of myself
for (int32 k = 0; k < DupVerts.Num(); k++)
{
AdjacentFaces.AddUnique(DupVerts[k] / 3);
}
}
// We need to sort these here because the criteria for point equality is
// exact, so we must ensure the exact same order for all dups.
AdjacentFaces.Sort();
// Process adjacent faces
for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
{
int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
for (int32 OurCornerIndex = 0; OurCornerIndex < 3; OurCornerIndex++)
{
if (bCornerHasTangents[OurCornerIndex])
continue;
FFanFace NewFanFace;
int32 CommonIndexCount = 0;
// Check for vertices in common.
if (FaceIndex == OtherFaceIndex)
{
CommonIndexCount = 3;
NewFanFace.LinkedVertexIndex = OurCornerIndex;
}
else
{
// Check matching vertices against main vertex .
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
if (PointsEqual(
CornerPositions[OurCornerIndex],
BuildData->GetVertexPosition(OtherFaceIndex, OtherCornerIndex),
ComparisonThreshold
))
{
CommonIndexCount++;
NewFanFace.LinkedVertexIndex = OtherCornerIndex;
}
}
}
// Add if connected by at least one point. Smoothing matches are considered later.
if (CommonIndexCount > 0)
{
NewFanFace.FaceIndex = OtherFaceIndex;
NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
NewFanFace.bBlendTangents = NewFanFace.bFilled;
NewFanFace.bBlendNormals = NewFanFace.bFilled;
RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
}
}
}
// Find true relevance of faces for a vertex normal by traversing
// smoothing-group-compatible connected triangle fans around common vertices.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasTangents[CornerIndex])
continue;
int32 NewConnections;
do
{
NewConnections = 0;
for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
{
FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
// The vertex' own face is initially the only face with bFilled == true.
if (OtherFace.bFilled)
{
for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
{
FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
{
if (NextFaceIndex != OtherFaceIdx)
//&& (RawMesh.FaceSmoothingMasks[NextFace.FaceIndex] & RawMesh.FaceSmoothingMasks[OtherFace.FaceIndex]))
{
int32 CommonVertices = 0;
int32 CommonTangentVertices = 0;
int32 CommonNormalVertices = 0;
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
for (int32 NextCornerIndex = 0; NextCornerIndex < 3; NextCornerIndex++)
{
int32 NextVertexIndex = BuildData->GetVertexIndex(NextFace.FaceIndex, NextCornerIndex);
int32 OtherVertexIndex = BuildData->GetVertexIndex(OtherFace.FaceIndex, OtherCornerIndex);
if (PointsEqual(
BuildData->GetVertexPosition(NextFace.FaceIndex, NextCornerIndex),
BuildData->GetVertexPosition(OtherFace.FaceIndex, OtherCornerIndex),
ComparisonThreshold))
{
CommonVertices++;
if (UVsEqual(
BuildData->GetVertexUV(NextFace.FaceIndex, NextCornerIndex, 0),
BuildData->GetVertexUV(OtherFace.FaceIndex, OtherCornerIndex, 0)))
{
CommonTangentVertices++;
}
if (bBlendOverlappingNormals
|| NextVertexIndex == OtherVertexIndex)
{
CommonNormalVertices++;
}
}
}
}
// Flood fill faces with more than one common vertices which must be touching edges.
if (CommonVertices > 1)
{
NextFace.bFilled = true;
NextFace.bBlendNormals = (CommonNormalVertices > 1);
NewConnections++;
// Only blend tangents if there is no UV seam along the edge with this face.
if (OtherFace.bBlendTangents && CommonTangentVertices > 1)
{
float OtherDeterminant = FVector::Triple(
TriangleTangentX[NextFace.FaceIndex],
TriangleTangentY[NextFace.FaceIndex],
TriangleTangentZ[NextFace.FaceIndex]
);
if ((Determinant * OtherDeterminant) > 0.0f)
{
NextFace.bBlendTangents = true;
}
}
}
}
}
}
}
}
}
while (NewConnections > 0);
}
// Vertex normal construction.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasTangents[CornerIndex])
{
CornerTangentX[CornerIndex] = WedgeTangentX[WedgeOffset + CornerIndex];
CornerTangentY[CornerIndex] = WedgeTangentY[WedgeOffset + CornerIndex];
CornerTangentZ[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
}
else
{
for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
{
FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
if (RelevantFace.bFilled)
{
int32 OtherFaceIndex = RelevantFace.FaceIndex;
if (RelevantFace.bBlendTangents)
{
CornerTangentX[CornerIndex] += TriangleTangentX[OtherFaceIndex];
CornerTangentY[CornerIndex] += TriangleTangentY[OtherFaceIndex];
}
if (RelevantFace.bBlendNormals)
{
CornerTangentZ[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
}
}
}
if (!WedgeTangentX[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentX[CornerIndex] = WedgeTangentX[WedgeOffset + CornerIndex];
}
if (!WedgeTangentY[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentY[CornerIndex] = WedgeTangentY[WedgeOffset + CornerIndex];
}
if (!WedgeTangentZ[WedgeOffset + CornerIndex].IsZero())
{
CornerTangentZ[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
}
}
}
// Normalization.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerTangentX[CornerIndex].Normalize();
CornerTangentY[CornerIndex].Normalize();
CornerTangentZ[CornerIndex].Normalize();
// Gram-Schmidt orthogonalization
CornerTangentY[CornerIndex] -= CornerTangentX[CornerIndex] * (CornerTangentX[CornerIndex] | CornerTangentY[CornerIndex]);
CornerTangentY[CornerIndex].Normalize();
CornerTangentX[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentX[CornerIndex]);
CornerTangentX[CornerIndex].Normalize();
CornerTangentY[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentY[CornerIndex]);
CornerTangentY[CornerIndex].Normalize();
}
// Copy back to the mesh.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
WedgeTangentX[WedgeOffset + CornerIndex] = CornerTangentX[CornerIndex];
WedgeTangentY[WedgeOffset + CornerIndex] = CornerTangentY[CornerIndex];
WedgeTangentZ[WedgeOffset + CornerIndex] = CornerTangentZ[CornerIndex];
}
}
check(WedgeTangentX.Num() == NumWedges);
check(WedgeTangentY.Num() == NumWedges);
check(WedgeTangentZ.Num() == NumWedges);
}
void Skeletal_ComputeTangents_MikkTSpace(
IMeshBuildData* BuildData,
TMultiMap<int32, int32> const& OverlappingCorners
)
{
bool bBlendOverlappingNormals = true;
bool bIgnoreDegenerateTriangles = BuildData->BuildOptions.bRemoveDegenerateTriangles;
float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
// Compute per-triangle tangents.
TArray<FVector> TriangleTangentX;
TArray<FVector> TriangleTangentY;
TArray<FVector> TriangleTangentZ;
Skeletal_ComputeTriangleTangents(
TriangleTangentX,
TriangleTangentY,
TriangleTangentZ,
BuildData,
bIgnoreDegenerateTriangles ? SMALL_NUMBER : 0.0f
);
TArray<FVector>& WedgeTangentX = BuildData->GetTangentArray(0);
TArray<FVector>& WedgeTangentY = BuildData->GetTangentArray(1);
TArray<FVector>& WedgeTangentZ = BuildData->GetTangentArray(2);
// Declare these out here to avoid reallocations.
TArray<FFanFace> RelevantFacesForCorner[3];
TArray<int32> AdjacentFaces;
TArray<int32> DupVerts;
int32 NumFaces = BuildData->GetNumFaces();
int32 NumWedges = BuildData->GetNumWedges();
check(NumFaces * 3 == NumWedges);
bool bWedgeNormals = true;
bool bWedgeTSpace = false;
for (int32 WedgeIdx = 0; WedgeIdx < WedgeTangentZ.Num(); ++WedgeIdx)
{
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
bWedgeNormals = bWedgeNormals && (!WedgeTangentZ[WedgeIdx].IsNearlyZero());
}
if (WedgeTangentX.Num() > 0 && WedgeTangentY.Num() > 0)
{
bWedgeTSpace = true;
for (int32 WedgeIdx = 0; WedgeIdx < WedgeTangentX.Num()
&& WedgeIdx < WedgeTangentY.Num(); ++WedgeIdx)
{
bWedgeTSpace = bWedgeTSpace && (!WedgeTangentX[WedgeIdx].IsNearlyZero()) && (!WedgeTangentY[WedgeIdx].IsNearlyZero());
}
}
// Allocate storage for tangents if none were provided, and calculate normals for MikkTSpace.
if (WedgeTangentZ.Num() != NumWedges || !bWedgeNormals)
{
// normals are not included, so we should calculate them
WedgeTangentZ.Empty(NumWedges);
WedgeTangentZ.AddZeroed(NumWedges);
// we need to calculate normals for MikkTSpace
for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
{
int32 WedgeOffset = FaceIndex * 3;
FVector CornerPositions[3];
FVector CornerNormal[3];
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerNormal[CornerIndex] = FVector::ZeroVector;
CornerPositions[CornerIndex] = BuildData->GetVertexPosition(FaceIndex, CornerIndex);
RelevantFacesForCorner[CornerIndex].Reset();
}
// Don't process degenerate triangles.
if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
|| PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
|| PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
{
continue;
}
// No need to process triangles if tangents already exist.
bool bCornerHasNormal[3] = { 0 };
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
bCornerHasNormal[CornerIndex] = !WedgeTangentZ[WedgeOffset + CornerIndex].IsZero();
}
if (bCornerHasNormal[0] && bCornerHasNormal[1] && bCornerHasNormal[2])
{
continue;
}
// Start building a list of faces adjacent to this face.
AdjacentFaces.Reset();
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
int32 ThisCornerIndex = WedgeOffset + CornerIndex;
DupVerts.Reset();
OverlappingCorners.MultiFind(ThisCornerIndex, DupVerts);
DupVerts.Add(ThisCornerIndex); // I am a "dup" of myself
for (int32 k = 0; k < DupVerts.Num(); k++)
{
AdjacentFaces.AddUnique(DupVerts[k] / 3);
}
}
// We need to sort these here because the criteria for point equality is
// exact, so we must ensure the exact same order for all dups.
AdjacentFaces.Sort();
// Process adjacent faces
for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
{
int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
for (int32 OurCornerIndex = 0; OurCornerIndex < 3; OurCornerIndex++)
{
if (bCornerHasNormal[OurCornerIndex])
continue;
FFanFace NewFanFace;
int32 CommonIndexCount = 0;
// Check for vertices in common.
if (FaceIndex == OtherFaceIndex)
{
CommonIndexCount = 3;
NewFanFace.LinkedVertexIndex = OurCornerIndex;
}
else
{
// Check matching vertices against main vertex .
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
if (PointsEqual(
CornerPositions[OurCornerIndex],
BuildData->GetVertexPosition(OtherFaceIndex, OtherCornerIndex),
ComparisonThreshold
))
{
CommonIndexCount++;
NewFanFace.LinkedVertexIndex = OtherCornerIndex;
}
}
}
// Add if connected by at least one point. Smoothing matches are considered later.
if (CommonIndexCount > 0)
{
NewFanFace.FaceIndex = OtherFaceIndex;
NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
NewFanFace.bBlendTangents = NewFanFace.bFilled;
NewFanFace.bBlendNormals = NewFanFace.bFilled;
RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
}
}
}
// Find true relevance of faces for a vertex normal by traversing
// smoothing-group-compatible connected triangle fans around common vertices.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasNormal[CornerIndex])
continue;
int32 NewConnections;
do
{
NewConnections = 0;
for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
{
FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
// The vertex' own face is initially the only face with bFilled == true.
if (OtherFace.bFilled)
{
for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
{
FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
{
if ((NextFaceIndex != OtherFaceIdx)
&& (BuildData->GetFaceSmoothingGroups(NextFace.FaceIndex) & BuildData->GetFaceSmoothingGroups(OtherFace.FaceIndex)))
{
int32 CommonVertices = 0;
int32 CommonNormalVertices = 0;
for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
{
for (int32 NextCornerIndex = 0; NextCornerIndex < 3; NextCornerIndex++)
{
int32 NextVertexIndex = BuildData->GetVertexIndex(NextFace.FaceIndex, NextCornerIndex);
int32 OtherVertexIndex = BuildData->GetVertexIndex(OtherFace.FaceIndex, OtherCornerIndex);
if (PointsEqual(
BuildData->GetVertexPosition(NextFace.FaceIndex, NextCornerIndex),
BuildData->GetVertexPosition(OtherFace.FaceIndex, OtherCornerIndex),
ComparisonThreshold))
{
CommonVertices++;
if (bBlendOverlappingNormals
|| NextVertexIndex == OtherVertexIndex)
{
CommonNormalVertices++;
}
}
}
}
// Flood fill faces with more than one common vertices which must be touching edges.
if (CommonVertices > 1)
{
NextFace.bFilled = true;
NextFace.bBlendNormals = (CommonNormalVertices > 1);
NewConnections++;
}
}
}
}
}
}
}
while (NewConnections > 0);
}
// Vertex normal construction.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
if (bCornerHasNormal[CornerIndex])
{
CornerNormal[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
}
else
{
for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
{
FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
if (RelevantFace.bFilled)
{
int32 OtherFaceIndex = RelevantFace.FaceIndex;
if (RelevantFace.bBlendNormals)
{
CornerNormal[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
}
}
}
if (!WedgeTangentZ[WedgeOffset + CornerIndex].IsZero())
{
CornerNormal[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
}
}
}
// Normalization.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
CornerNormal[CornerIndex].Normalize();
}
// Copy back to the mesh.
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
{
WedgeTangentZ[WedgeOffset + CornerIndex] = CornerNormal[CornerIndex];
}
}
}
if (WedgeTangentX.Num() != NumWedges)
{
WedgeTangentX.Empty(NumWedges);
WedgeTangentX.AddZeroed(NumWedges);
}
if (WedgeTangentY.Num() != NumWedges)
{
WedgeTangentY.Empty(NumWedges);
WedgeTangentY.AddZeroed(NumWedges);
}
//if (!bWedgeTSpace)
{
// we can use mikktspace to calculate the tangents
SMikkTSpaceContext MikkTContext;
MikkTContext.m_pInterface = BuildData->GetMikkTInterface();
MikkTContext.m_pUserData = BuildData->GetMikkTUserData();
//MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
genTangSpaceDefault(&MikkTContext);
}
check(WedgeTangentX.Num() == NumWedges);
check(WedgeTangentY.Num() == NumWedges);
check(WedgeTangentZ.Num() == NumWedges);
}
bool PrepareSourceMesh(IMeshBuildData* BuildData)
{
check(Stage == EStage::Uninit);
BeginSlowTask();
TMultiMap<int32, int32>& OverlappingCorners = *new(LODOverlappingCorners)TMultiMap<int32, int32>;
float ComparisonThreshold = THRESH_POINTS_ARE_SAME;//GetComparisonThreshold(LODBuildSettings[LODIndex]);
int32 NumWedges = BuildData->GetNumWedges();
// Find overlapping corners to accelerate adjacency.
Skeletal_FindOverlappingCorners(OverlappingCorners, BuildData, ComparisonThreshold);
// Figure out if we should recompute normals and tangents.
bool bRecomputeNormals = BuildData->BuildOptions.bComputeNormals;
bool bRecomputeTangents = BuildData->BuildOptions.bComputeTangents;
// Dump normals and tangents if we are recomputing them.
if (bRecomputeTangents)
{
TArray<FVector>& TangentX = BuildData->GetTangentArray(0);
TArray<FVector>& TangentY = BuildData->GetTangentArray(1);
TangentX.Empty(NumWedges);
TangentX.AddZeroed(NumWedges);
TangentY.Empty(NumWedges);
TangentY.AddZeroed(NumWedges);
}
if (bRecomputeNormals)
{
TArray<FVector>& TangentZ = BuildData->GetTangentArray(2);
TangentZ.Empty(NumWedges);
TangentZ.AddZeroed(NumWedges);
}
// Compute any missing tangents. MikkTSpace should be use only when the user want to recompute the normals or tangents otherwise should always fallback on builtin
if (BuildData->BuildOptions.bUseMikkTSpace && (BuildData->BuildOptions.bComputeNormals || BuildData->BuildOptions.bComputeTangents))
{
Skeletal_ComputeTangents_MikkTSpace(BuildData, OverlappingCorners);
}
else
{
Skeletal_ComputeTangents(BuildData, OverlappingCorners);
}
// At this point the mesh will have valid tangents.
BuildData->ValidateTangentArraySize();
check(LODOverlappingCorners.Num() == 1);
EndSlowTask();
Stage = EStage::Prepared;
return true;
}
bool GenerateSkeletalRenderMesh(IMeshBuildData* InBuildData)
{
check(Stage == EStage::Prepared);
SkeletalMeshBuildData& BuildData = *(SkeletalMeshBuildData*)InBuildData;
BeginSlowTask();
// Find wedge influences.
TArray<int32> WedgeInfluenceIndices;
TMap<uint32, uint32> VertexIndexToInfluenceIndexMap;
for (uint32 LookIdx = 0; LookIdx < (uint32)BuildData.Influences.Num(); LookIdx++)
{
// Order matters do not allow the map to overwrite an existing value.
if (!VertexIndexToInfluenceIndexMap.Find(BuildData.Influences[LookIdx].VertIndex))
{
VertexIndexToInfluenceIndexMap.Add(BuildData.Influences[LookIdx].VertIndex, LookIdx);
}
}
for (int32 WedgeIndex = 0; WedgeIndex < BuildData.Wedges.Num(); WedgeIndex++)
{
uint32* InfluenceIndex = VertexIndexToInfluenceIndexMap.Find(BuildData.Wedges[WedgeIndex].iVertex);
if (InfluenceIndex)
{
WedgeInfluenceIndices.Add(*InfluenceIndex);
}
else
{
// we have missing influence vert, we weight to root
WedgeInfluenceIndices.Add(0);
// add warning message
if (BuildData.OutWarningMessages)
{
BuildData.OutWarningMessages->Add(FText::Format(FText::FromString("Missing influence on vert {0}. Weighting it to root."), FText::FromString(FString::FromInt(BuildData.Wedges[WedgeIndex].iVertex))));
if (BuildData.OutWarningNames)
{
BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_VertMissingInfluences);
}
}
}
}
check(BuildData.Wedges.Num() == WedgeInfluenceIndices.Num());
TArray<FSkeletalMeshVertIndexAndZ> VertIndexAndZ;
TArray<FSoftSkinBuildVertex> RawVertices;
VertIndexAndZ.Empty(BuildData.Points.Num());
RawVertices.Reserve(BuildData.Points.Num());
for (int32 FaceIndex = 0; FaceIndex < BuildData.Faces.Num(); FaceIndex++)
{
// Only update the status progress bar if we are in the game thread and every thousand faces.
// Updating status is extremely slow
if (FaceIndex % 5000 == 0)
{
UpdateSlowTask(FaceIndex, BuildData.Faces.Num());
}
const FMeshFace& Face = BuildData.Faces[FaceIndex];
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
FSoftSkinBuildVertex Vertex;
const uint32 WedgeIndex = BuildData.GetWedgeIndex(FaceIndex, VertexIndex);
const FMeshWedge& Wedge = BuildData.Wedges[WedgeIndex];
Vertex.Position = BuildData.GetVertexPosition(FaceIndex, VertexIndex);
FVector TangentX, TangentY, TangentZ;
TangentX = BuildData.TangentX[WedgeIndex].GetSafeNormal();
TangentY = BuildData.TangentY[WedgeIndex].GetSafeNormal();
TangentZ = BuildData.TangentZ[WedgeIndex].GetSafeNormal();
/*if (BuildData.BuildOptions.bComputeNormals || BuildData.BuildOptions.bComputeTangents)
{
TangentX = BuildData.TangentX[VertexIndex].GetSafeNormal();
TangentY = BuildData.TangentY[VertexIndex].GetSafeNormal();
if( BuildData.BuildOptions.bComputeNormals )
{
TangentZ = BuildData.TangentZ[VertexIndex].GetSafeNormal();
}
else
{
//TangentZ = Face.TangentZ[VertexIndex];
}
TangentY -= TangentX * (TangentX | TangentY);
TangentY.Normalize();
TangentX -= TangentZ * (TangentZ | TangentX);
TangentY -= TangentZ * (TangentZ | TangentY);
TangentX.Normalize();
TangentY.Normalize();
}
else*/
{
//TangentX = Face.TangentX[VertexIndex];
//TangentY = Face.TangentY[VertexIndex];
//TangentZ = Face.TangentZ[VertexIndex];
// Normalize overridden tangents. Its possible for them to import un-normalized.
TangentX.Normalize();
TangentY.Normalize();
TangentZ.Normalize();
}
Vertex.TangentX = TangentX;
Vertex.TangentY = TangentY;
Vertex.TangentZ = TangentZ;
FMemory::Memcpy(Vertex.UVs, Wedge.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
Vertex.Color = Wedge.Color;
{
// Count the influences.
int32 InfIdx = WedgeInfluenceIndices[Face.iWedge[VertexIndex]];
int32 LookIdx = InfIdx;
uint32 InfluenceCount = 0;
while (BuildData.Influences.IsValidIndex(LookIdx) && (BuildData.Influences[LookIdx].VertIndex == Wedge.iVertex))
{
InfluenceCount++;
LookIdx++;
}
InfluenceCount = FMath::Min<uint32>(InfluenceCount, MAX_TOTAL_INFLUENCES);
// Setup the vertex influences.
Vertex.InfluenceBones[0] = 0;
Vertex.InfluenceWeights[0] = 255;
for (uint32 i = 1; i < MAX_TOTAL_INFLUENCES; i++)
{
Vertex.InfluenceBones[i] = 0;
Vertex.InfluenceWeights[i] = 0;
}
uint32 TotalInfluenceWeight = 0;
for (uint32 i = 0; i < InfluenceCount; i++)
{
FBoneIndexType BoneIndex = (FBoneIndexType)BuildData.Influences[InfIdx + i].BoneIndex;
if (BoneIndex >= BuildData.RefSkeleton.GetRawBoneNum())
continue;
Vertex.InfluenceBones[i] = BoneIndex;
Vertex.InfluenceWeights[i] = (uint8)(BuildData.Influences[InfIdx + i].Weight * 255.0f);
TotalInfluenceWeight += Vertex.InfluenceWeights[i];
}
Vertex.InfluenceWeights[0] += 255 - TotalInfluenceWeight;
}
// Add the vertex as well as its original index in the points array
Vertex.PointWedgeIdx = Wedge.iVertex;
int32 RawIndex = RawVertices.Add(Vertex);
// Add an efficient way to find dupes of this vertex later for fast combining of vertices
FSkeletalMeshVertIndexAndZ IAndZ;
IAndZ.Index = RawIndex;
IAndZ.Z = Vertex.Position.Z;
VertIndexAndZ.Add(IAndZ);
}
}
// Generate chunks and their vertices and indices
SkeletalMeshTools::BuildSkeletalMeshChunks(BuildData.Faces, RawVertices, VertIndexAndZ, BuildData.BuildOptions.bKeepOverlappingVertices, BuildData.Chunks, BuildData.bTooManyVerts);
// Chunk vertices to satisfy the requested limit.
const uint32 MaxGPUSkinBones = FGPUBaseSkinVertexFactory::GetMaxGPUSkinBones();
check(MaxGPUSkinBones <= FGPUBaseSkinVertexFactory::GHardwareMaxGPUSkinBones);
SkeletalMeshTools::ChunkSkinnedVertices(BuildData.Chunks, MaxGPUSkinBones);
EndSlowTask();
Stage = EStage::GenerateRendering;
return true;
}
void BeginSlowTask()
{
if (IsInGameThread())
{
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"), true);
}
}
void UpdateSlowTask(int32 Numerator, int32 Denominator)
{
if (IsInGameThread())
{
GWarn->StatusUpdate(Numerator, Denominator, NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"));
}
}
void EndSlowTask()
{
if (IsInGameThread())
{
GWarn->EndSlowTask();
}
}
private:
enum class EStage
{
Uninit,
Prepared,
GenerateRendering,
};
TIndirectArray<TMultiMap<int32, int32> > LODOverlappingCorners;
EStage Stage;
};
bool FMeshUtilities::BuildSkeletalMesh(FStaticLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, const TArray<FVertInfluence>& Influences, const TArray<FMeshWedge>& Wedges, const TArray<FMeshFace>& Faces, const TArray<FVector>& Points, const TArray<int32>& PointToOriginalMap, const MeshBuildOptions& BuildOptions, TArray<FText> * OutWarningMessages, TArray<FName> * OutWarningNames)
{
#if WITH_EDITORONLY_DATA
// Temporarily supporting both import paths
if (!BuildOptions.bUseMikkTSpace)
{
return BuildSkeletalMesh_Legacy(LODModel, RefSkeleton, Influences, Wedges, Faces, Points, PointToOriginalMap, BuildOptions.bKeepOverlappingVertices, BuildOptions.bComputeNormals, BuildOptions.bComputeTangents, OutWarningMessages, OutWarningNames);
}
SkeletalMeshBuildData BuildData(
LODModel,
RefSkeleton,
Influences,
Wedges,
Faces,
Points,
PointToOriginalMap,
BuildOptions,
OutWarningMessages,
OutWarningNames);
FSkeletalMeshUtilityBuilder Builder;
if (!Builder.PrepareSourceMesh(&BuildData))
{
return false;
}
if (!Builder.GenerateSkeletalRenderMesh(&BuildData))
{
return false;
}
// Build the skeletal model from chunks.
Builder.BeginSlowTask();
BuildSkeletalModelFromChunks(BuildData.LODModel, BuildData.RefSkeleton, BuildData.Chunks, BuildData.PointToOriginalMap);
Builder.EndSlowTask();
// Only show these warnings if in the game thread. When importing morph targets, this function can run in another thread and these warnings dont prevent the mesh from importing
if (IsInGameThread())
{
bool bHasBadSections = false;
for (int32 SectionIndex = 0; SectionIndex < BuildData.LODModel.Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = BuildData.LODModel.Sections[SectionIndex];
bHasBadSections |= (Section.NumTriangles == 0);
// Log info about the section.
UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: Material=%u, %u triangles"),
SectionIndex,
Section.MaterialIndex,
Section.NumTriangles
);
}
if (bHasBadSections)
{
FText BadSectionMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshHasBadSections", "Input mesh has a section with no triangles. This mesh may not render properly."));
if (BuildData.OutWarningMessages)
{
BuildData.OutWarningMessages->Add(BadSectionMessage);
if (BuildData.OutWarningNames)
{
BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_SectionWithNoTriangle);
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, BadSectionMessage);
}
}
if (BuildData.bTooManyVerts)
{
FText TooManyVertsMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshTooManyVertices", "Input mesh has too many vertices. The generated mesh will be corrupt! Consider adding extra materials to split up the source mesh into smaller chunks."));
if (BuildData.OutWarningMessages)
{
BuildData.OutWarningMessages->Add(TooManyVertsMessage);
if (BuildData.OutWarningNames)
{
BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_TooManyVertices);
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, TooManyVertsMessage);
}
}
}
return true;
#else
if (OutWarningMessages)
{
OutWarningMessages->Add(FText::FromString(TEXT("Cannot call FMeshUtilities::BuildSkeletalMesh on a console!")));
}
else
{
UE_LOG(LogSkeletalMesh, Fatal, TEXT("Cannot call FMeshUtilities::BuildSkeletalMesh on a console!"));
}
return false;
#endif
}
//@TODO: The OutMessages has to be a struct that contains FText/FName, or make it Token and add that as error. Needs re-work. Temporary workaround for now.
bool FMeshUtilities::BuildSkeletalMesh_Legacy(FStaticLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, const TArray<FVertInfluence>& Influences, const TArray<FMeshWedge>& Wedges, const TArray<FMeshFace>& Faces, const TArray<FVector>& Points, const TArray<int32>& PointToOriginalMap, bool bKeepOverlappingVertices, bool bComputeNormals, bool bComputeTangents, TArray<FText> * OutWarningMessages, TArray<FName> * OutWarningNames)
{
bool bTooManyVerts = false;
check(PointToOriginalMap.Num() == Points.Num());
// Calculate face tangent vectors.
TArray<FVector> FaceTangentX;
TArray<FVector> FaceTangentY;
FaceTangentX.AddUninitialized(Faces.Num());
FaceTangentY.AddUninitialized(Faces.Num());
if (bComputeNormals || bComputeTangents)
{
for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
{
FVector P1 = Points[Wedges[Faces[FaceIndex].iWedge[0]].iVertex],
P2 = Points[Wedges[Faces[FaceIndex].iWedge[1]].iVertex],
P3 = Points[Wedges[Faces[FaceIndex].iWedge[2]].iVertex];
FVector TriangleNormal = FPlane(P3, P2, P1);
FMatrix ParameterToLocal(
FPlane(P2.X - P1.X, P2.Y - P1.Y, P2.Z - P1.Z, 0),
FPlane(P3.X - P1.X, P3.Y - P1.Y, P3.Z - P1.Z, 0),
FPlane(P1.X, P1.Y, P1.Z, 0),
FPlane(0, 0, 0, 1)
);
float U1 = Wedges[Faces[FaceIndex].iWedge[0]].UVs[0].X,
U2 = Wedges[Faces[FaceIndex].iWedge[1]].UVs[0].X,
U3 = Wedges[Faces[FaceIndex].iWedge[2]].UVs[0].X,
V1 = Wedges[Faces[FaceIndex].iWedge[0]].UVs[0].Y,
V2 = Wedges[Faces[FaceIndex].iWedge[1]].UVs[0].Y,
V3 = Wedges[Faces[FaceIndex].iWedge[2]].UVs[0].Y;
FMatrix ParameterToTexture(
FPlane(U2 - U1, V2 - V1, 0, 0),
FPlane(U3 - U1, V3 - V1, 0, 0),
FPlane(U1, V1, 1, 0),
FPlane(0, 0, 0, 1)
);
FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
FVector TangentX = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal(),
TangentY = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal(),
TangentZ;
TangentX = TangentX - TriangleNormal * (TangentX | TriangleNormal);
TangentY = TangentY - TriangleNormal * (TangentY | TriangleNormal);
FaceTangentX[FaceIndex] = TangentX.GetSafeNormal();
FaceTangentY[FaceIndex] = TangentY.GetSafeNormal();
}
}
TArray<int32> WedgeInfluenceIndices;
// Find wedge influences.
TMap<uint32, uint32> VertexIndexToInfluenceIndexMap;
for (uint32 LookIdx = 0; LookIdx < (uint32)Influences.Num(); LookIdx++)
{
// Order matters do not allow the map to overwrite an existing value.
if (!VertexIndexToInfluenceIndexMap.Find(Influences[LookIdx].VertIndex))
{
VertexIndexToInfluenceIndexMap.Add(Influences[LookIdx].VertIndex, LookIdx);
}
}
for (int32 WedgeIndex = 0; WedgeIndex < Wedges.Num(); WedgeIndex++)
{
uint32* InfluenceIndex = VertexIndexToInfluenceIndexMap.Find(Wedges[WedgeIndex].iVertex);
if (InfluenceIndex)
{
WedgeInfluenceIndices.Add(*InfluenceIndex);
}
else
{
// we have missing influence vert, we weight to root
WedgeInfluenceIndices.Add(0);
// add warning message
if (OutWarningMessages)
{
OutWarningMessages->Add(FText::Format(FText::FromString("Missing influence on vert {0}. Weighting it to root."), FText::FromString(FString::FromInt(Wedges[WedgeIndex].iVertex))));
if (OutWarningNames)
{
OutWarningNames->Add(FFbxErrors::SkeletalMesh_VertMissingInfluences);
}
}
}
}
check(Wedges.Num() == WedgeInfluenceIndices.Num());
// Calculate smooth wedge tangent vectors.
if (IsInGameThread())
{
// Only update status if in the game thread. When importing morph targets, this function can run in another thread
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"), true);
}
// To accelerate generation of adjacency, we'll create a table that maps each vertex index
// to its overlapping vertices, and a table that maps a vertex to the its influenced faces
TMultiMap<int32, int32> Vert2Duplicates;
TMultiMap<int32, int32> Vert2Faces;
TArray<FSkeletalMeshVertIndexAndZ> VertIndexAndZ;
{
// Create a list of vertex Z/index pairs
VertIndexAndZ.Empty(Points.Num());
for (int32 i = 0; i < Points.Num(); i++)
{
FSkeletalMeshVertIndexAndZ iandz;
iandz.Index = i;
iandz.Z = Points[i].Z;
VertIndexAndZ.Add(iandz);
}
// Sorting function for vertex Z/index pairs
struct FCompareFSkeletalMeshVertIndexAndZ
{
FORCEINLINE bool operator()(const FSkeletalMeshVertIndexAndZ& A, const FSkeletalMeshVertIndexAndZ& B) const
{
return A.Z < B.Z;
}
};
// Sort the vertices by z value
VertIndexAndZ.Sort(FCompareFSkeletalMeshVertIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > THRESH_POINTS_ARE_SAME)
{
// our list is sorted, so there can't be any more dupes
break;
}
// check to see if the points are really overlapping
if (PointsEqual(
Points[VertIndexAndZ[i].Index],
Points[VertIndexAndZ[j].Index]))
{
Vert2Duplicates.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
Vert2Duplicates.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
}
}
}
// we are done with this
VertIndexAndZ.Reset();
// now create a map from vert indices to faces
for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
{
const FMeshFace& Face = Faces[FaceIndex];
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
Vert2Faces.AddUnique(Wedges[Face.iWedge[VertexIndex]].iVertex, FaceIndex);
}
}
}
TArray<FSkinnedMeshChunk*> Chunks;
TArray<int32> AdjacentFaces;
TArray<int32> DupVerts;
TArray<int32> DupFaces;
// List of raw calculated vertices that will be merged later
TArray<FSoftSkinBuildVertex> RawVertices;
RawVertices.Reserve(Points.Num());
// Create a list of vertex Z/index pairs
for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
{
// Only update the status progress bar if we are in the gamethread and every thousand faces.
// Updating status is extremely slow
if (FaceIndex % 5000 == 0 && IsInGameThread())
{
// Only update status if in the game thread. When importing morph targets, this function can run in another thread
GWarn->StatusUpdate(FaceIndex, Faces.Num(), NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"));
}
const FMeshFace& Face = Faces[FaceIndex];
FVector VertexTangentX[3],
VertexTangentY[3],
VertexTangentZ[3];
if (bComputeNormals || bComputeTangents)
{
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
VertexTangentX[VertexIndex] = FVector::ZeroVector;
VertexTangentY[VertexIndex] = FVector::ZeroVector;
VertexTangentZ[VertexIndex] = FVector::ZeroVector;
}
FVector TriangleNormal = FPlane(
Points[Wedges[Face.iWedge[2]].iVertex],
Points[Wedges[Face.iWedge[1]].iVertex],
Points[Wedges[Face.iWedge[0]].iVertex]
);
float Determinant = FVector::Triple(FaceTangentX[FaceIndex], FaceTangentY[FaceIndex], TriangleNormal);
// Start building a list of faces adjacent to this triangle
AdjacentFaces.Reset();
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
int32 vert = Wedges[Face.iWedge[VertexIndex]].iVertex;
DupVerts.Reset();
Vert2Duplicates.MultiFind(vert, DupVerts);
DupVerts.Add(vert); // I am a "dupe" of myself
for (int32 k = 0; k < DupVerts.Num(); k++)
{
DupFaces.Reset();
Vert2Faces.MultiFind(DupVerts[k], DupFaces);
for (int32 l = 0; l < DupFaces.Num(); l++)
{
AdjacentFaces.AddUnique(DupFaces[l]);
}
}
}
// Process adjacent faces
for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
{
int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
const FMeshFace& OtherFace = Faces[OtherFaceIndex];
FVector OtherTriangleNormal = FPlane(
Points[Wedges[OtherFace.iWedge[2]].iVertex],
Points[Wedges[OtherFace.iWedge[1]].iVertex],
Points[Wedges[OtherFace.iWedge[0]].iVertex]
);
float OtherFaceDeterminant = FVector::Triple(FaceTangentX[OtherFaceIndex], FaceTangentY[OtherFaceIndex], OtherTriangleNormal);
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
for (int32 OtherVertexIndex = 0; OtherVertexIndex < 3; OtherVertexIndex++)
{
if (PointsEqual(
Points[Wedges[OtherFace.iWedge[OtherVertexIndex]].iVertex],
Points[Wedges[Face.iWedge[VertexIndex]].iVertex]
))
{
if (Determinant * OtherFaceDeterminant > 0.0f && SkeletalMeshTools::SkeletalMesh_UVsEqual(Wedges[OtherFace.iWedge[OtherVertexIndex]], Wedges[Face.iWedge[VertexIndex]]))
{
VertexTangentX[VertexIndex] += FaceTangentX[OtherFaceIndex];
VertexTangentY[VertexIndex] += FaceTangentY[OtherFaceIndex];
}
// Only contribute 'normal' if the vertices are truly one and the same to obey hard "smoothing" edges baked into
// the mesh by vertex duplication
if (Wedges[OtherFace.iWedge[OtherVertexIndex]].iVertex == Wedges[Face.iWedge[VertexIndex]].iVertex)
{
VertexTangentZ[VertexIndex] += OtherTriangleNormal;
}
}
}
}
}
}
for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
{
FSoftSkinBuildVertex Vertex;
Vertex.Position = Points[Wedges[Face.iWedge[VertexIndex]].iVertex];
FVector TangentX, TangentY, TangentZ;
if (bComputeNormals || bComputeTangents)
{
TangentX = VertexTangentX[VertexIndex].GetSafeNormal();
TangentY = VertexTangentY[VertexIndex].GetSafeNormal();
if (bComputeNormals)
{
TangentZ = VertexTangentZ[VertexIndex].GetSafeNormal();
}
else
{
TangentZ = Face.TangentZ[VertexIndex];
}
TangentY -= TangentX * (TangentX | TangentY);
TangentY.Normalize();
TangentX -= TangentZ * (TangentZ | TangentX);
TangentY -= TangentZ * (TangentZ | TangentY);
TangentX.Normalize();
TangentY.Normalize();
}
else
{
TangentX = Face.TangentX[VertexIndex];
TangentY = Face.TangentY[VertexIndex];
TangentZ = Face.TangentZ[VertexIndex];
// Normalize overridden tangents. Its possible for them to import un-normalized.
TangentX.Normalize();
TangentY.Normalize();
TangentZ.Normalize();
}
Vertex.TangentX = TangentX;
Vertex.TangentY = TangentY;
Vertex.TangentZ = TangentZ;
FMemory::Memcpy(Vertex.UVs, Wedges[Face.iWedge[VertexIndex]].UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
Vertex.Color = Wedges[Face.iWedge[VertexIndex]].Color;
{
// Count the influences.
int32 InfIdx = WedgeInfluenceIndices[Face.iWedge[VertexIndex]];
int32 LookIdx = InfIdx;
uint32 InfluenceCount = 0;
while (Influences.IsValidIndex(LookIdx) && (Influences[LookIdx].VertIndex == Wedges[Face.iWedge[VertexIndex]].iVertex))
{
InfluenceCount++;
LookIdx++;
}
InfluenceCount = FMath::Min<uint32>(InfluenceCount, MAX_TOTAL_INFLUENCES);
// Setup the vertex influences.
Vertex.InfluenceBones[0] = 0;
Vertex.InfluenceWeights[0] = 255;
for (uint32 i = 1; i < MAX_TOTAL_INFLUENCES; i++)
{
Vertex.InfluenceBones[i] = 0;
Vertex.InfluenceWeights[i] = 0;
}
uint32 TotalInfluenceWeight = 0;
for (uint32 i = 0; i < InfluenceCount; i++)
{
FBoneIndexType BoneIndex = (FBoneIndexType)Influences[InfIdx + i].BoneIndex;
if (BoneIndex >= RefSkeleton.GetRawBoneNum())
continue;
Vertex.InfluenceBones[i] = BoneIndex;
Vertex.InfluenceWeights[i] = (uint8)(Influences[InfIdx + i].Weight * 255.0f);
TotalInfluenceWeight += Vertex.InfluenceWeights[i];
}
Vertex.InfluenceWeights[0] += 255 - TotalInfluenceWeight;
}
// Add the vertex as well as its original index in the points array
Vertex.PointWedgeIdx = Wedges[Face.iWedge[VertexIndex]].iVertex;
int32 RawIndex = RawVertices.Add(Vertex);
// Add an efficient way to find dupes of this vertex later for fast combining of vertices
FSkeletalMeshVertIndexAndZ IAndZ;
IAndZ.Index = RawIndex;
IAndZ.Z = Vertex.Position.Z;
VertIndexAndZ.Add(IAndZ);
}
}
// Generate chunks and their vertices and indices
SkeletalMeshTools::BuildSkeletalMeshChunks(Faces, RawVertices, VertIndexAndZ, bKeepOverlappingVertices, Chunks, bTooManyVerts);
// Chunk vertices to satisfy the requested limit.
const uint32 MaxGPUSkinBones = FGPUBaseSkinVertexFactory::GetMaxGPUSkinBones();
check(MaxGPUSkinBones <= FGPUBaseSkinVertexFactory::GHardwareMaxGPUSkinBones);
SkeletalMeshTools::ChunkSkinnedVertices(Chunks, MaxGPUSkinBones);
// Build the skeletal model from chunks.
BuildSkeletalModelFromChunks(LODModel, RefSkeleton, Chunks, PointToOriginalMap);
if (IsInGameThread())
{
// Only update status if in the game thread. When importing morph targets, this function can run in another thread
GWarn->EndSlowTask();
}
// Only show these warnings if in the game thread. When importing morph targets, this function can run in another thread and these warnings dont prevent the mesh from importing
if (IsInGameThread())
{
bool bHasBadSections = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
bHasBadSections |= (Section.NumTriangles == 0);
// Log info about the section.
UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: Material=%u, %u triangles"),
SectionIndex,
Section.MaterialIndex,
Section.NumTriangles
);
}
if (bHasBadSections)
{
FText BadSectionMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshHasBadSections", "Input mesh has a section with no triangles. This mesh may not render properly."));
if (OutWarningMessages)
{
OutWarningMessages->Add(BadSectionMessage);
if (OutWarningNames)
{
OutWarningNames->Add(FFbxErrors::SkeletalMesh_SectionWithNoTriangle);
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, BadSectionMessage);
}
}
if (bTooManyVerts)
{
FText TooManyVertsMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshTooManyVertices", "Input mesh has too many vertices. The generated mesh will be corrupt! Consider adding extra materials to split up the source mesh into smaller chunks."));
if (OutWarningMessages)
{
OutWarningMessages->Add(TooManyVertsMessage);
if (OutWarningNames)
{
OutWarningNames->Add(FFbxErrors::SkeletalMesh_TooManyVertices);
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, TooManyVertsMessage);
}
}
}
return true;
}
static bool NonOpaqueMaterialPredicate(UStaticMeshComponent* InMesh)
{
TArray<UMaterialInterface*> OutMaterials;
InMesh->GetUsedMaterials(OutMaterials);
for (auto Material : OutMaterials)
{
if (Material == nullptr || Material->GetBlendMode() != BLEND_Opaque)
{
return true;
}
}
return false;
}
static FIntPoint ConditionalImageResize(const FIntPoint& SrcSize, const FIntPoint& DesiredSize, TArray<FColor>& InOutImage, bool bLinearSpace)
{
const int32 NumDesiredSamples = DesiredSize.X*DesiredSize.Y;
if (InOutImage.Num() && InOutImage.Num() != NumDesiredSamples)
{
check(InOutImage.Num() == SrcSize.X*SrcSize.Y);
TArray<FColor> OutImage;
if (NumDesiredSamples > 0)
{
FImageUtils::ImageResize(SrcSize.X, SrcSize.Y, InOutImage, DesiredSize.X, DesiredSize.Y, OutImage, bLinearSpace);
}
Exchange(InOutImage, OutImage);
return DesiredSize;
}
return SrcSize;
}
static void RetrieveValidStaticMeshComponentsForMerging(AActor* InActor, TArray<UStaticMeshComponent*>& OutComponents)
{
TInlineComponentArray<UStaticMeshComponent*> Components;
InActor->GetComponents<UStaticMeshComponent>(Components);
// TODO: support derived classes from static component
Components.RemoveAll([](UStaticMeshComponent* Val){ return !(Val->GetClass() == UStaticMeshComponent::StaticClass() || Val->IsA(USplineMeshComponent::StaticClass())); });
// TODO: support non-opaque materials
//Components.RemoveAll(&NonOpaqueMaterialPredicate);
OutComponents.Append(Components);
}
static void CheckWrappingUVs(TArray<FRawMeshExt>& SourceMeshes, TArray<bool>& MeshShouldBakeVertexData)
{
const uint32 MeshCount = SourceMeshes.Num();
for (uint32 MeshIndex = 0; MeshIndex < MeshCount; ++MeshIndex)
{
FRawMeshExt& SourceMesh = SourceMeshes[MeshIndex];
const int32 LODIndex = SourceMeshes[MeshIndex].ExportLODIndex;
if (SourceMesh.bShouldExportLOD[LODIndex])
{
FRawMesh* RawMesh = SourceMesh.MeshLODData[LODIndex].RawMesh;
check(RawMesh);
for (uint32 ChannelIndex = 0; ChannelIndex < MAX_MESH_TEXTURE_COORDS; ++ChannelIndex)
{
bool bProcessed = false;
bool bHasCoordinates = (RawMesh->WedgeTexCoords[ChannelIndex].Num() != 0);
if (bHasCoordinates)
{
FVector2D Min(FLT_MAX, FLT_MAX);
FVector2D Max(-FLT_MAX, -FLT_MAX);
for (const FVector2D& Coordinate : RawMesh->WedgeTexCoords[ChannelIndex])
{
if ((FMath::IsNegativeFloat(Coordinate.X) || FMath::IsNegativeFloat(Coordinate.Y)) || (Coordinate.X > (1.0f + KINDA_SMALL_NUMBER) || Coordinate.Y > (1.0f + KINDA_SMALL_NUMBER)))
{
MeshShouldBakeVertexData[MeshIndex] = true;
bProcessed = true;
break;
}
}
}
if (bProcessed)
{
break;
}
}
}
}
}
void FMeshUtilities::CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync, const float ScreenSize)
{
// Error/warning checking for input
if (MeshMerging == NULL)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No automatic mesh merging module available"));
return;
}
// Check that the delegate has a func-ptr bound to it
if (!InProxyCreatedDelegate.IsBound())
{
UE_LOG(LogMeshUtilities, Log, TEXT("Invalid (unbound) delegate for returning generated proxy mesh"));
return;
}
// No actors given as input
if (InActors.Num() == 0)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No actors specified to generate a proxy mesh for"));
return;
}
// Base asset name for a new assets
// In case outer is null ProxyBasePackageName has to be long package name
if (InOuter == nullptr && FPackageName::IsShortPackageName(InProxyBasePackageName))
{
UE_LOG(LogMeshUtilities, Warning, TEXT("Invalid long package name: '%s'."), *InProxyBasePackageName);
return;
}
FScopedSlowTask SlowTask(100.f, (LOCTEXT("CreateProxyMesh_CreateMesh", "Creating Mesh Proxy")));
SlowTask.MakeDialog();
// Retrieve static mesh components valid for merging from the given set of actors
TArray<UStaticMeshComponent*> ComponentsToMerge;
{
// Collect components to merge
for (AActor* Actor : InActors)
{
RetrieveValidStaticMeshComponentsForMerging(Actor, ComponentsToMerge);
}
}
// Check if there are actually any static mesh components to merge
if (ComponentsToMerge.Num() == 0)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No valid static mesh components found in given set of Actors"));
return;
}
typedef FIntPoint FMeshIdAndLOD;
TArray<FRawMeshExt> SourceMeshes;
TArray<FSectionInfo> UniqueSections;
TMap<FMeshIdAndLOD, TArray<int32>> GlobalMaterialMap;
static const int32 ProxyMeshTargetLODLevel = 0;
FBoxSphereBounds EstimatedBounds(ForceInitToZero);
for (const UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
{
EstimatedBounds = EstimatedBounds + StaticMeshComponent->Bounds;
}
static const float FOVRad = 90.0f * (float)PI / 360.0f;
static const FMatrix ProjectionMatrix = FPerspectiveMatrix(FOVRad, 1920, 1080, 0.01f);
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
float EstimatedDistance = Utilities->CalculateDrawDistanceFromScreenSize(EstimatedBounds.SphereRadius, ScreenSize, ProjectionMatrix);
SlowTask.EnterProgressFrame(5.0f, LOCTEXT("CreateProxyMesh_CollectingMeshes", "Collecting Input Static Meshes"));
// Retrieve mesh / material data
for (const UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
{
TArray<int32> StaticMeshGlobalMaterialMap;
FRawMesh* RawMesh = new FRawMesh();
FMemory::Memzero(RawMesh, sizeof(FRawMesh));
const int32 ProxyMeshSourceLODLevel = InMeshProxySettings.bCalculateCorrectLODModel ? Utilities->GetLODLevelForScreenSize(StaticMeshComponent, Utilities->CalculateScreenSizeFromDrawDistance(StaticMeshComponent->Bounds.SphereRadius, ProjectionMatrix, EstimatedDistance)) : 0;
// Proxy meshes should always propagate vertex colours for material baking
static const bool bPropagateVertexColours = true;
const bool bValidRawMesh = ConstructRawMesh(StaticMeshComponent, ProxyMeshSourceLODLevel, bPropagateVertexColours, *RawMesh, UniqueSections, StaticMeshGlobalMaterialMap);
if ( bValidRawMesh )
{
// Add constructed raw mesh to source mesh array
const int32 SourceMeshIndex = SourceMeshes.AddZeroed();
SourceMeshes[SourceMeshIndex].MeshLODData[ProxyMeshTargetLODLevel].RawMesh = RawMesh;
SourceMeshes[SourceMeshIndex].bShouldExportLOD[ProxyMeshTargetLODLevel] = true;
SourceMeshes[SourceMeshIndex].ExportLODIndex = ProxyMeshTargetLODLevel;
// Make sure we do now the bounds of our UVs
//CalculateTextureCoordinateBoundsForRawMesh(*SourceMeshes[SourceMeshIndex].MeshLODData[ProxyMeshTargetLODLevel].RawMesh, SourceMeshes[SourceMeshIndex].MeshLODData[ProxyMeshTargetLODLevel].TexCoordBounds);
// Append retrieved materials for this static mesh component to the global material map
GlobalMaterialMap.Add(FMeshIdAndLOD(SourceMeshIndex, ProxyMeshTargetLODLevel), StaticMeshGlobalMaterialMap);
}
}
if (SourceMeshes.Num() == 0)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No valid (or completely culled) raw meshes constructed from static mesh components"));
return;
}
SlowTask.EnterProgressFrame(InMeshProxySettings.bUseLandscapeCulling ? 40.0f : 45.0f, LOCTEXT("CreateProxyMesh_RemapAndFlatten", "Remapping and Flattening Materials"));
TArray<bool> MeshShouldBakeVertexData;
MeshShouldBakeVertexData.AddZeroed(SourceMeshes.Num());
CheckWrappingUVs(SourceMeshes, MeshShouldBakeVertexData);
TMap<FMeshIdAndLOD, TArray<int32> > NewGlobalMaterialMap;
TArray<FSectionInfo> NewUniqueSections;
FMaterialUtilities::RemapUniqueMaterialIndices(
UniqueSections,
SourceMeshes,
GlobalMaterialMap,
InMeshProxySettings.MaterialSettings,
true, // Always need vertex data for baking materials
true, // Always want to merge materials
MeshShouldBakeVertexData,
NewGlobalMaterialMap,
NewUniqueSections);
// Use shared material data.
Exchange(GlobalMaterialMap, NewGlobalMaterialMap);
Exchange(UniqueSections, NewUniqueSections);
// Flatten Materials
TArray<FFlattenMaterial> FlattenedMaterials;
TArray<UMaterialInterface*> Materials;
for (const FSectionInfo& Section : UniqueSections)
{
Materials.Push(Section.Material);
}
FlattenMaterialsWithMeshData(Materials, SourceMeshes, GlobalMaterialMap, MeshShouldBakeVertexData, InMeshProxySettings.MaterialSettings, FlattenedMaterials);
for (FRawMeshExt& MeshData : SourceMeshes)
{
if (MeshData.MeshLODData[MeshData.ExportLODIndex].NewUVs.Num() == 0)
{
MeshData.MeshLODData[MeshData.ExportLODIndex].TexCoordBounds.Empty();
}
}
for (FFlattenMaterial& InMaterial : FlattenedMaterials)
{
FMaterialUtilities::OptimizeFlattenMaterial(InMaterial);
}
//For each raw mesh, re-map the material indices from Local to Global material indices space
for (int32 RawMeshIndex = 0; RawMeshIndex < SourceMeshes.Num(); ++RawMeshIndex)
{
const TArray<int32>& GlobalMaterialIndices = *GlobalMaterialMap.Find(FMeshIdAndLOD(RawMeshIndex, ProxyMeshTargetLODLevel));
TArray<int32>& MaterialIndices = SourceMeshes[RawMeshIndex].MeshLODData[ProxyMeshTargetLODLevel].RawMesh->FaceMaterialIndices;
int32 MaterialIndicesCount = MaterialIndices.Num();
for (int32 TriangleIndex = 0; TriangleIndex < MaterialIndicesCount; ++TriangleIndex)
{
int32 LocalMaterialIndex = MaterialIndices[TriangleIndex];
int32 GlobalMaterialIndex = GlobalMaterialIndices[LocalMaterialIndex];
//Assign the new material index to the raw mesh
MaterialIndices[TriangleIndex] = GlobalMaterialIndex;
}
}
// Build proxy mesh
// Landscape culling
TArray<FRawMesh*> CullingRawMeshes;
if (InMeshProxySettings.bUseLandscapeCulling)
{
SlowTask.EnterProgressFrame(5.0f, LOCTEXT("CreateProxyMesh_LandscapeCulling", "Applying Landscape Culling"));
// Extract landscape proxies and cull volumes from the world
TArray<ALandscapeProxy*> LandscapeActors;
TArray<AMeshMergeCullingVolume*> CullVolumes;
UWorld* InWorld = InActors[0]->GetWorld();
uint32 MaxLandscapeExportLOD = 0;
if (InWorld->IsValidLowLevel())
{
for (FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator)
{
for (AActor* Actor : (*Iterator)->Actors)
{
if (Actor)
{
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(Actor);
if (LandscapeProxy && LandscapeProxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
// Retrieve highest landscape LOD level possible
MaxLandscapeExportLOD = FMath::Max(MaxLandscapeExportLOD, FMath::CeilLogTwo(LandscapeProxy->SubsectionSizeQuads + 1) - 1);
LandscapeActors.Add(LandscapeProxy);
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(EstimatedBounds.Origin, EstimatedBounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
}
}
// Setting determines the precision at which we should export the landscape for culling (highest, half or lowest)
const uint32 LandscapeExportLOD = ((float)MaxLandscapeExportLOD * (0.5f * (float)InMeshProxySettings.LandscapeCullingPrecision));
for (ALandscapeProxy* Landscape : LandscapeActors)
{
// Export the landscape to raw mesh format
FRawMesh* LandscapeRawMesh = new FRawMesh();
FBoxSphereBounds LandscapeBounds = EstimatedBounds;
Landscape->ExportToRawMesh(LandscapeExportLOD, *LandscapeRawMesh, LandscapeBounds);
if (LandscapeRawMesh->VertexPositions.Num())
{
CullingRawMeshes.Add(LandscapeRawMesh);
}
}
// Also add volume mesh data as culling meshes
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
// Export the landscape to raw mesh format
FRawMesh* VolumeMesh = new FRawMesh();
TArray<FStaticMaterial> VolumeMaterials;
GetBrushMesh(Volume, Volume->Brush, *VolumeMesh, VolumeMaterials);
// Offset vertices to correct world position;
FVector VolumeLocation = Volume->GetActorLocation();
for (FVector& Position : VolumeMesh->VertexPositions)
{
Position += VolumeLocation;
}
CullingRawMeshes.Add(VolumeMesh);
}
}
// Allocate merge complete data
FMergeCompleteData* Data = new FMergeCompleteData();
Data->InOuter = InOuter;
Data->InProxySettings = InMeshProxySettings;
Data->ProxyBasePackageName = InProxyBasePackageName;
Data->CallbackDelegate = InProxyCreatedDelegate;
// Add this proxy job to map
Processor->AddProxyJob(InGuid, Data);
// We are only using LOD level 0 (ProxyMeshTargetLODLevel)
TArray<FMeshMergeData> MergeData;
for (FRawMeshExt& SourceMesh : SourceMeshes)
{
MergeData.Add(SourceMesh.MeshLODData[ProxyMeshTargetLODLevel]);
}
// Populate landscape clipping geometry
for (FRawMesh* RawMesh : CullingRawMeshes)
{
FMeshMergeData ClipData;
ClipData.bIsClippingMesh = true;
ClipData.RawMesh = RawMesh;
MergeData.Add(ClipData);
}
SlowTask.EnterProgressFrame(50.0f, LOCTEXT("CreateProxyMesh_GenerateProxy", "Generating Proxy Mesh"));
// Choose Simplygon Swarm (if available) or local proxy lod method
if (DistributedMeshMerging != nullptr && GetDefault<UEditorPerProjectUserSettings>()->bUseSimplygonSwarm && bAllowAsync)
{
DistributedMeshMerging->ProxyLOD(MergeData, Data->InProxySettings, FlattenedMaterials, InGuid);
}
else
{
MeshMerging->ProxyLOD(MergeData, Data->InProxySettings, FlattenedMaterials, InGuid);
Processor->Tick(0); // make sure caller gets merging results
}
for (FMeshMergeData& DataToRelease : MergeData)
{
DataToRelease.ReleaseData();
}
}
void FMeshUtilities::CreateProxyMesh(const TArray<AActor*>& Actors, const struct FMeshProxySettings& InProxySettings, UPackage* InOuter, const FString& ProxyBasePackageName, TArray<UObject*>& OutAssetsToSync, FVector& OutProxyLocation)
{
CreateProxyMesh(Actors, InProxySettings, InOuter, ProxyBasePackageName, OutAssetsToSync);
}
void FMeshUtilities::CreateProxyMesh(const TArray<AActor*>& Actors, const struct FMeshProxySettings& InProxySettings, UPackage* InOuter, const FString& ProxyBasePackageName, TArray<UObject*>& OutAssetsToSync, const float ScreenAreaSize)
{
FCreateProxyDelegate Delegate;
FGuid JobGuid = FGuid::NewGuid();
Delegate.BindLambda(
[&](const FGuid Guid, TArray<UObject*>& InAssetsToSync)
{
if (JobGuid == Guid)
{
OutAssetsToSync.Append(InAssetsToSync);
}
}
);
CreateProxyMesh(Actors, InProxySettings, InOuter, ProxyBasePackageName, JobGuid, Delegate, false, ScreenAreaSize);
}
void FMeshUtilities::FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const
{
FScopedSlowTask SlowTask(InMaterials.Num(), (LOCTEXT("FlattenMaterialsWithMeshData", "Flattening Materials With Mesh Data")));
SlowTask.MakeDialog();
// Prepare container for cached shaders.
TMap<UMaterialInterface*, FExportMaterialProxyCache> CachedShaders;
CachedShaders.Empty(InMaterials.Num());
bool bDitheredLODTransition = false;
for (int32 MaterialIndex = 0; MaterialIndex < InMaterials.Num(); MaterialIndex++)
{
UMaterialInterface* CurrentMaterial = InMaterials[MaterialIndex];
SlowTask.EnterProgressFrame(1.0f, FText::FromString(FString::Printf(TEXT("Flattening out %s"), *CurrentMaterial->GetName())));
// Store if any material uses dithered transitions
bDitheredLODTransition |= CurrentMaterial->IsDitheredLODTransition();
// Check if we already have cached compiled shader for this material.
FExportMaterialProxyCache* CachedShader = CachedShaders.Find(CurrentMaterial);
if (CachedShader == nullptr)
{
CachedShader = &CachedShaders.Add(CurrentMaterial);
}
FFlattenMaterial FlattenMaterial = FMaterialUtilities::CreateFlattenMaterialWithSettings(InMaterialProxySettings);
/* Find a mesh which uses the current material. Materials using vertex data are added for each individual mesh using it,
which is why baking down the materials like this works. :) */
int32 UsedMeshIndex = 0;
int32 LocalMaterialIndex = 0;
int32 LocalTextureBoundIndex = 0;
FMeshMergeData* MergeData = nullptr;
for (int32 MeshIndex = 0; MeshIndex < InSourceMeshes.Num() && MergeData == nullptr; MeshIndex++)
{
const int32 LODIndex = InSourceMeshes[MeshIndex].ExportLODIndex;
if (InSourceMeshes[MeshIndex].MeshLODData[LODIndex].RawMesh->VertexPositions.Num())
{
const TArray<int32>& GlobalMaterialIndices = *InMaterialIndexMap.Find(FMeshIdAndLOD(MeshIndex, LODIndex));
for (LocalMaterialIndex = 0; LocalMaterialIndex < GlobalMaterialIndices.Num(); LocalMaterialIndex++)
{
// Only need to set merge data if we need to bake out using vertex data for this specific mesh
if (InMeshShouldBakeVertexData[MeshIndex] && GlobalMaterialIndices[LocalMaterialIndex] == MaterialIndex)
{
UsedMeshIndex = MeshIndex;
MergeData = &InSourceMeshes[MeshIndex].MeshLODData[LODIndex];
LocalTextureBoundIndex = LocalMaterialIndex;
break;
}
}
}
else
{
break;
}
}
// If there is specific vertex data available and used in the material we should generate non-overlapping UVs
if (MergeData && InMeshShouldBakeVertexData[UsedMeshIndex])
{
// Generate new non-overlapping texture coordinates for mesh if needed
if (MergeData->TexCoordBounds.Num() == 0)
{
// Calculate the max bounds for this raw mesh
CalculateTextureCoordinateBoundsForRawMesh(*MergeData->RawMesh, MergeData->TexCoordBounds);
}
if (MergeData->NewUVs.Num() == 0)
{
// Generate unique UVs
GenerateUniqueUVsForStaticMesh(*MergeData->RawMesh, InMaterialProxySettings.TextureSize.GetMax(), MergeData->NewUVs);
}
FBox2D TextureBoundsForMesh(EForceInit::ForceInitToZero);
for (const FVector2D& UV : MergeData->NewUVs)
{
TextureBoundsForMesh += UV;
}
// Export the material using mesh data to support vertex based material properties
FMaterialUtilities::ExportMaterial(
CurrentMaterial,
MergeData->RawMesh,
LocalMaterialIndex,
TextureBoundsForMesh,
MergeData->NewUVs,
FlattenMaterial,
CachedShader);
}
else
{
// Export the material without vertex data
FMaterialUtilities::ExportMaterial(
CurrentMaterial,
FlattenMaterial,
CachedShader);
}
// Fill flatten material samples alpha values with 255 (for saving out textures correctly for Simplygon Swarm)
FlattenMaterial.FillAlphaValues(255);
// Add flattened material to outgoing array
OutFlattenedMaterials.Add(FlattenMaterial);
// Check if this material will be used later. If not - release shader.
bool bMaterialStillUsed = false;
for (int32 Index = MaterialIndex + 1; Index < InMaterials.Num(); Index++)
{
if (InMaterials[Index] == CurrentMaterial)
{
bMaterialStillUsed = true;
break;
}
}
if (!bMaterialStillUsed)
{
CachedShader->Release();
}
}
if (OutFlattenedMaterials.Num() > 1)
{
// Dither transition fix-up
for (FFlattenMaterial& FlatMaterial : OutFlattenedMaterials)
{
FlatMaterial.bDitheredLODTransition = bDitheredLODTransition;
}
// Start with determining maximum emissive scale
float MaxEmissiveScale = 0.0f;
for (FFlattenMaterial& FlatMaterial : OutFlattenedMaterials)
{
if (FlatMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive))
{
if (FlatMaterial.EmissiveScale > MaxEmissiveScale)
{
MaxEmissiveScale = FlatMaterial.EmissiveScale;
}
}
}
if (MaxEmissiveScale > 0.001f)
{
// Rescale all materials.
for (FFlattenMaterial& FlatMaterial : OutFlattenedMaterials)
{
const float Scale = FlatMaterial.EmissiveScale / MaxEmissiveScale;
if (FMath::Abs(Scale - 1.0f) < 0.01f)
{
// Difference is not noticeable for this material, or this material has maximal emissive level.
continue;
}
// Rescale emissive data.
TArray<FColor>& EmissiveSamples = FlatMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive);
for (int32 PixelIndex = 0; PixelIndex < EmissiveSamples.Num(); PixelIndex++)
{
FColor& C = EmissiveSamples[PixelIndex];
C.R = FMath::RoundToInt(C.R * Scale);
C.G = FMath::RoundToInt(C.G * Scale);
C.B = FMath::RoundToInt(C.B * Scale);
}
// Update emissive scale to maximum
FlatMaterial.EmissiveScale = MaxEmissiveScale;
}
}
}
}
// Exports static mesh LOD render data to a RawMesh
void FMeshUtilities::ExportStaticMeshLOD(const FStaticMeshLODResources& StaticMeshLOD, FRawMesh& OutRawMesh) const
{
const int32 NumWedges = StaticMeshLOD.IndexBuffer.GetNumIndices();
const int32 NumVertexPositions = StaticMeshLOD.PositionVertexBuffer.GetNumVertices();
const int32 NumFaces = NumWedges / 3;
// Indices
StaticMeshLOD.IndexBuffer.GetCopy(OutRawMesh.WedgeIndices);
// Vertex positions
if (NumVertexPositions > 0)
{
OutRawMesh.VertexPositions.Empty(NumVertexPositions);
for (int32 PosIdx = 0; PosIdx < NumVertexPositions; ++PosIdx)
{
FVector Pos = StaticMeshLOD.PositionVertexBuffer.VertexPosition(PosIdx);
OutRawMesh.VertexPositions.Add(Pos);
}
}
// Vertex data
if (StaticMeshLOD.VertexBuffer.GetNumVertices() > 0)
{
OutRawMesh.WedgeTangentX.Empty(NumWedges);
OutRawMesh.WedgeTangentY.Empty(NumWedges);
OutRawMesh.WedgeTangentZ.Empty(NumWedges);
const int32 NumTexCoords = StaticMeshLOD.VertexBuffer.GetNumTexCoords();
for (int32 TexCoodIdx = 0; TexCoodIdx < NumTexCoords; ++TexCoodIdx)
{
OutRawMesh.WedgeTexCoords[TexCoodIdx].Empty(NumWedges);
}
for (int32 WedgeIndex : OutRawMesh.WedgeIndices)
{
FVector WedgeTangentX = StaticMeshLOD.VertexBuffer.VertexTangentX(WedgeIndex);
FVector WedgeTangentY = StaticMeshLOD.VertexBuffer.VertexTangentY(WedgeIndex);
FVector WedgeTangentZ = StaticMeshLOD.VertexBuffer.VertexTangentZ(WedgeIndex);
OutRawMesh.WedgeTangentX.Add(WedgeTangentX);
OutRawMesh.WedgeTangentY.Add(WedgeTangentY);
OutRawMesh.WedgeTangentZ.Add(WedgeTangentZ);
for (int32 TexCoodIdx = 0; TexCoodIdx < NumTexCoords; ++TexCoodIdx)
{
FVector2D WedgeTexCoord = StaticMeshLOD.VertexBuffer.GetVertexUV(WedgeIndex, TexCoodIdx);
OutRawMesh.WedgeTexCoords[TexCoodIdx].Add(WedgeTexCoord);
}
}
}
// Vertex colors
if (StaticMeshLOD.ColorVertexBuffer.GetNumVertices() > 0)
{
OutRawMesh.WedgeColors.Empty(NumWedges);
for (int32 WedgeIndex : OutRawMesh.WedgeIndices)
{
FColor VertexColor = StaticMeshLOD.ColorVertexBuffer.VertexColor(WedgeIndex);
OutRawMesh.WedgeColors.Add(VertexColor);
}
}
// Materials
{
OutRawMesh.FaceMaterialIndices.Empty(NumFaces);
OutRawMesh.FaceMaterialIndices.SetNumZeroed(NumFaces);
for (const FStaticMeshSection& Section : StaticMeshLOD.Sections)
{
uint32 FirstTriangle = Section.FirstIndex / 3;
for (uint32 TriangleIndex = 0; TriangleIndex < Section.NumTriangles; ++TriangleIndex)
{
OutRawMesh.FaceMaterialIndices[FirstTriangle + TriangleIndex] = Section.MaterialIndex;
}
}
}
// Smoothing masks
{
OutRawMesh.FaceSmoothingMasks.Empty(NumFaces);
OutRawMesh.FaceSmoothingMasks.SetNumUninitialized(NumFaces);
for (auto& SmoothingMask : OutRawMesh.FaceSmoothingMasks)
{
SmoothingMask = 1;
}
}
}
const bool IsLandscapeHit(const FVector& RayOrigin, const FVector& RayEndPoint, const UWorld* World, const TArray<ALandscapeProxy*>& LandscapeProxies, FVector& OutHitLocation)
{
static FName TraceTag = FName(TEXT("LandscapeTrace"));
TArray<FHitResult> Results;
// Each landscape component has 2 collision shapes, 1 of them is specific to landscape editor
// Trace only ECC_Visibility channel, so we do hit only Editor specific shape
World->LineTraceMultiByObjectType(Results, RayOrigin, RayEndPoint, FCollisionObjectQueryParams(ECollisionChannel::ECC_Visibility), FCollisionQueryParams(TraceTag, true));
bool bHitLandscape = false;
for (const FHitResult& HitResult : Results)
{
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Cast<ULandscapeHeightfieldCollisionComponent>(HitResult.Component.Get());
if (CollisionComponent)
{
ALandscapeProxy* HitLandscape = CollisionComponent->GetLandscapeProxy();
if (HitLandscape && LandscapeProxies.Contains(HitLandscape))
{
// Could write a correct clipping algorithm, that clips the triangle to hit location
OutHitLocation = HitLandscape->LandscapeActorToWorld().InverseTransformPosition(HitResult.Location);
// Above landscape so visible
bHitLandscape = true;
}
}
}
return bHitLandscape;
}
void CullTrianglesFromVolumesAndUnderLandscapes(const UStaticMeshComponent* InMeshComponent, FRawMesh &OutRawMesh)
{
UWorld* World = InMeshComponent->GetWorld();
TArray<ALandscapeProxy*> Landscapes;
TArray<AMeshMergeCullingVolume*> CullVolumes;
FBox ComponentBox(InMeshComponent->Bounds.Origin - InMeshComponent->Bounds.BoxExtent, InMeshComponent->Bounds.Origin + InMeshComponent->Bounds.BoxExtent);
for (ULevel* Level : World->GetLevels())
{
for (AActor* Actor : Level->Actors)
{
ALandscape* Proxy = Cast<ALandscape>(Actor);
if (Proxy && Proxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
// Ignore Z axis for 2d bounds check
if (LandscapeBox.IntersectXY(ComponentBox))
{
Landscapes.Add(Proxy->GetLandscapeActor());
}
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(InMeshComponent->Bounds.Origin, InMeshComponent->Bounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
TArray<bool> VertexVisible;
VertexVisible.AddZeroed(OutRawMesh.VertexPositions.Num());
int32 Index = 0;
for (const FVector& Position : OutRawMesh.VertexPositions)
{
// Start with setting visibility to true on all vertices
VertexVisible[Index] = true;
// Check if this vertex is culled due to being underneath a landscape
if (Landscapes.Num() > 0)
{
bool bVertexWithinLandscapeBounds = false;
for (ALandscapeProxy* Proxy : Landscapes)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
bVertexWithinLandscapeBounds |= LandscapeBox.IsInsideXY(Position);
}
if (bVertexWithinLandscapeBounds)
{
const FVector Start = Position;
FVector End = Position - (WORLD_MAX * FVector::UpVector);
FVector OutHit;
const bool IsAboveLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
End = Position + (WORLD_MAX * FVector::UpVector);
const bool IsUnderneathLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
// Vertex is visible when above landscape (with actual landscape underneath) or if there is no landscape beneath or above the vertex (falls outside of landscape bounds)
VertexVisible[Index] = (IsAboveLandscape && !IsUnderneathLandscape);// || (!IsAboveLandscape && !IsUnderneathLandscape);
}
}
// Volume culling
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
const bool bVertexIsInsideVolume = Volume->EncompassesPoint(Position, 0.0f, nullptr);
if (bVertexIsInsideVolume)
{
// Inside a culling volume so invisible
VertexVisible[Index] = false;
}
}
Index++;
}
// We now know which vertices are below the landscape
TArray<bool> TriangleVisible;
int32 NumTriangles = OutRawMesh.WedgeIndices.Num() / 3;
TriangleVisible.AddZeroed(NumTriangles);
bool bCreateNewMesh = false;
// Determine which triangles of the mesh are visible
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
{
bool AboveLandscape = false;
for (int32 WedgeIndex = 0; WedgeIndex < 3; ++WedgeIndex)
{
AboveLandscape |= VertexVisible[OutRawMesh.WedgeIndices[(TriangleIndex * 3) + WedgeIndex]];
}
TriangleVisible[TriangleIndex] = AboveLandscape;
bCreateNewMesh |= !AboveLandscape;
}
// Check whether or not we have to create a new mesh
if (bCreateNewMesh)
{
FRawMesh NewRawMesh;
TMap<int32, int32> VertexRemapping;
// Fill new mesh with data only from visible triangles
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
if (!TriangleVisible[TriangleIndex])
continue;
for (int32 WedgeIndex = 0; WedgeIndex < 3; ++WedgeIndex)
{
int32 OldIndex = OutRawMesh.WedgeIndices[(TriangleIndex * 3) + WedgeIndex];
int32 NewIndex;
int32* RemappedIndex = VertexRemapping.Find(Index);
if (RemappedIndex)
{
NewIndex = *RemappedIndex;
}
else
{
NewIndex = NewRawMesh.VertexPositions.Add(OutRawMesh.VertexPositions[OldIndex]);
VertexRemapping.Add(OldIndex, NewIndex);
}
NewRawMesh.WedgeIndices.Add(NewIndex);
if (OutRawMesh.WedgeColors.Num()) NewRawMesh.WedgeColors.Add(OutRawMesh.WedgeColors[(TriangleIndex * 3) + WedgeIndex]);
if (OutRawMesh.WedgeTangentX.Num()) NewRawMesh.WedgeTangentX.Add(OutRawMesh.WedgeTangentX[(TriangleIndex * 3) + WedgeIndex]);
if (OutRawMesh.WedgeTangentY.Num()) NewRawMesh.WedgeTangentY.Add(OutRawMesh.WedgeTangentY[(TriangleIndex * 3) + WedgeIndex]);
if (OutRawMesh.WedgeTangentZ.Num()) NewRawMesh.WedgeTangentZ.Add(OutRawMesh.WedgeTangentZ[(TriangleIndex * 3) + WedgeIndex]);
for (int32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS; ++UVIndex)
{
if (OutRawMesh.WedgeTexCoords[UVIndex].Num())
{
NewRawMesh.WedgeTexCoords[UVIndex].Add(OutRawMesh.WedgeTexCoords[UVIndex][(TriangleIndex * 3) + WedgeIndex]);
}
}
}
NewRawMesh.FaceMaterialIndices.Add(OutRawMesh.FaceMaterialIndices[TriangleIndex]);
NewRawMesh.FaceSmoothingMasks.Add(OutRawMesh.FaceSmoothingMasks[TriangleIndex]);
}
OutRawMesh = NewRawMesh;
}
}
void PropagateSplineDeformationToRawMesh(const USplineMeshComponent* InSplineMeshComponent, FRawMesh &OutRawMesh)
{
// Apply spline deformation for each vertex's tangents
for (int32 iVert = 0; iVert < OutRawMesh.WedgeIndices.Num(); ++iVert)
{
uint32 Index = OutRawMesh.WedgeIndices[iVert];
float& AxisValue = USplineMeshComponent::GetAxisValue(OutRawMesh.VertexPositions[Index], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
// Transform tangents first
if (OutRawMesh.WedgeTangentX.Num())
{
OutRawMesh.WedgeTangentX[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentX[iVert]);
}
if (OutRawMesh.WedgeTangentY.Num())
{
OutRawMesh.WedgeTangentY[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentY[iVert]);
}
if (OutRawMesh.WedgeTangentZ.Num())
{
OutRawMesh.WedgeTangentZ[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentZ[iVert]);
}
}
// Apply spline deformation for each vertex position
for (int32 iVert = 0; iVert < OutRawMesh.VertexPositions.Num(); ++iVert)
{
float& AxisValue = USplineMeshComponent::GetAxisValue(OutRawMesh.VertexPositions[iVert], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
AxisValue = 0.0f;
OutRawMesh.VertexPositions[iVert] = SliceTransform.TransformPosition(OutRawMesh.VertexPositions[iVert]);
}
}
void TransformRawMeshVertexData(const FTransform& InTransform, FRawMesh &OutRawMesh )
{
for (FVector& Vertex : OutRawMesh.VertexPositions)
{
Vertex = InTransform.TransformPosition(Vertex);
}
for (FVector& TangentX : OutRawMesh.WedgeTangentX)
{
TangentX = InTransform.TransformVectorNoScale(TangentX);
}
for (FVector& TangentY : OutRawMesh.WedgeTangentY)
{
TangentY = InTransform.TransformVectorNoScale(TangentY);
}
for (FVector& TangentZ : OutRawMesh.WedgeTangentZ)
{
TangentZ = InTransform.TransformVectorNoScale(TangentZ);
}
const bool bIsMirrored = InTransform.GetDeterminant() < 0.f;
if (bIsMirrored)
{
// Flip faces
for (int32 FaceIdx = 0; FaceIdx < OutRawMesh.WedgeIndices.Num() / 3; FaceIdx++)
{
int32 I0 = FaceIdx * 3 + 0;
int32 I2 = FaceIdx * 3 + 2;
Swap(OutRawMesh.WedgeIndices[I0], OutRawMesh.WedgeIndices[I2]);
// seems like vertex colors and UVs are not indexed, so swap values instead
if (OutRawMesh.WedgeColors.Num())
{
Swap(OutRawMesh.WedgeColors[I0], OutRawMesh.WedgeColors[I2]);
}
for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i)
{
if (OutRawMesh.WedgeTexCoords[i].Num())
{
Swap(OutRawMesh.WedgeTexCoords[i][I0], OutRawMesh.WedgeTexCoords[i][I2]);
}
}
}
}
}
void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh &OutRawMesh )
{
const int32 NumWedges = OutRawMesh.WedgeIndices.Num();
// Dump normals and tangents if we are recomputing them.
if (bRecomputeTangents)
{
OutRawMesh.WedgeTangentX.Empty(NumWedges);
OutRawMesh.WedgeTangentX.AddZeroed(NumWedges);
OutRawMesh.WedgeTangentY.Empty(NumWedges);
OutRawMesh.WedgeTangentY.AddZeroed(NumWedges);
}
if (bRecomputeNormals)
{
OutRawMesh.WedgeTangentZ.Empty(NumWedges);
OutRawMesh.WedgeTangentZ.AddZeroed(NumWedges);
}
// Compute any missing tangents.
if (bRecomputeNormals || bRecomputeTangents)
{
float ComparisonThreshold = GetComparisonThreshold(InBuildSettings);
TMultiMap<int32, int32> OverlappingCorners;
FindOverlappingCorners(OverlappingCorners, OutRawMesh, ComparisonThreshold);
// Static meshes always blend normals of overlapping corners.
uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
if (InBuildSettings.bRemoveDegenerates)
{
// If removing degenerate triangles, ignore them when computing tangents.
TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
}
if (InBuildSettings.bUseMikkTSpace)
{
ComputeTangents_MikkTSpace(OutRawMesh, OverlappingCorners, TangentOptions);
}
else
{
ComputeTangents(OutRawMesh, OverlappingCorners, TangentOptions);
}
}
// At this point the mesh will have valid tangents.
check(OutRawMesh.WedgeTangentX.Num() == NumWedges);
check(OutRawMesh.WedgeTangentY.Num() == NumWedges);
check(OutRawMesh.WedgeTangentZ.Num() == NumWedges);
}
bool FMeshUtilities::ConstructRawMesh(
const UStaticMeshComponent* InMeshComponent,
int32 InLODIndex,
const bool bPropagateVertexColours,
FRawMesh& OutRawMesh,
TArray<FSectionInfo>& OutUniqueSections,
TArray<int32>& OutGlobalMaterialIndices) const
{
// Retrieve source static mesh
const UStaticMesh* SourceStaticMesh = InMeshComponent->GetStaticMesh();
if (SourceStaticMesh == NULL)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("No static mesh actor found in component %s."), *InMeshComponent->GetName());
return false;
}
if (!SourceStaticMesh->SourceModels.IsValidIndex(InLODIndex))
{
UE_LOG(LogMeshUtilities, Log, TEXT("No mesh data found for LOD%d %s."), InLODIndex, *SourceStaticMesh->GetName());
return false;
}
if (!SourceStaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex))
{
UE_LOG(LogMeshUtilities, Warning, TEXT("No mesh render data found for LOD%d %s."), InLODIndex, *SourceStaticMesh->GetName());
return false;
}
const FStaticMeshSourceModel& SourceStaticMeshModel = SourceStaticMesh->SourceModels[InLODIndex];
// Imported meshes will have a filled RawMeshBulkData set
const bool bImportedMesh = !SourceStaticMeshModel.RawMeshBulkData->IsEmpty();
// Check whether or not this mesh has been reduced in-engine
const bool bReducedMesh = (SourceStaticMeshModel.ReductionSettings.PercentTriangles < 1.0f);
// rying to retrieve rawmesh from SourceStaticMeshModel was giving issues, which causes a mismatch
const bool bRenderDataMismatch = (InLODIndex > 0);
// Determine whether we load the raw mesh data from (original) import data or from the generated render data resources
if (bImportedMesh && !InMeshComponent->IsA<USplineMeshComponent>() && !bReducedMesh && !bRenderDataMismatch)
{
SourceStaticMeshModel.RawMeshBulkData->LoadRawMesh(OutRawMesh);
}
else
{
ExportStaticMeshLOD(SourceStaticMesh->RenderData->LODResources[InLODIndex], OutRawMesh);
}
// Make sure the raw mesh is not irreparably malformed.
if (!OutRawMesh.IsValidOrFixable())
{
UE_LOG(LogMeshUtilities, Error, TEXT("Raw mesh (%s) is corrupt for LOD%d."), *SourceStaticMesh->GetName(), InLODIndex);
return false;
}
// Handle spline mesh deformation
if (InMeshComponent->IsA<USplineMeshComponent>())
{
const USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(InMeshComponent);
// Deform raw mesh data according to the Spline Mesh Component's data
PropagateSplineDeformationToRawMesh(SplineMeshComponent, OutRawMesh);
}
// Use build settings from base mesh for LOD entries that was generated inside Editor.
const FMeshBuildSettings& BuildSettings = bImportedMesh ? SourceStaticMeshModel.BuildSettings : SourceStaticMesh->SourceModels[0].BuildSettings;
// Transform raw mesh to world space
FTransform ComponentToWorldTransform = InMeshComponent->GetComponentTransform();
// Take into account build scale settings only for meshes imported from raw data
// meshes reconstructed from render data already have build scale applied
if (bImportedMesh)
{
ComponentToWorldTransform.SetScale3D(ComponentToWorldTransform.GetScale3D()*BuildSettings.BuildScale3D);
}
// If specified propagate painted vertex colors into our raw mesh
if (bPropagateVertexColours)
{
PropagatePaintedColorsToRawMesh(InMeshComponent, InLODIndex, OutRawMesh);
}
// Transform raw mesh vertex data by the Static Mesh Component's component to world transformation
TransformRawMeshVertexData(ComponentToWorldTransform, OutRawMesh);
// Culling triangles could lead to an entirely empty RawMesh (all vertices culled)
if (!OutRawMesh.IsValid())
{
return false;
}
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
const bool bIsMirrored = ComponentToWorldTransform.GetDeterminant() < 0.f;
bool bRecomputeNormals = (bImportedMesh && BuildSettings.bRecomputeNormals) || OutRawMesh.WedgeTangentZ.Num() == 0 || bIsMirrored;
bool bRecomputeTangents = (bImportedMesh && BuildSettings.bRecomputeTangents) || OutRawMesh.WedgeTangentX.Num() == 0 || OutRawMesh.WedgeTangentY.Num() == 0 || bIsMirrored;
if (bRecomputeNormals || bRecomputeTangents)
{
RecomputeTangentsAndNormalsForRawMesh(bRecomputeTangents, bRecomputeNormals, BuildSettings, OutRawMesh);
}
// Retrieving materials
UMaterialInterface* DefaultMaterial = Cast<UMaterialInterface>(UMaterial::GetDefaultMaterial(MD_Surface));
//Need to store the unique material indices in order to re-map the material indices in each rawmesh
TArray<int32> RemapMaterialArrayIndex;
RemapMaterialArrayIndex.AddZeroed(SourceStaticMesh->StaticMaterials.Num());
for (int32 ArrayIndex = 0; ArrayIndex < RemapMaterialArrayIndex.Num(); ++ArrayIndex)
{
RemapMaterialArrayIndex[ArrayIndex] = ArrayIndex;
}
for (const FStaticMeshSection& Section : SourceStaticMesh->RenderData->LODResources[InLODIndex].Sections)
{
// Add material and store the material ID
UMaterialInterface* MaterialToAdd = InMeshComponent->GetMaterial(Section.MaterialIndex);
FName MaterialSlotNameToAdd = SourceStaticMesh->StaticMaterials.IsValidIndex(Section.MaterialIndex) ? SourceStaticMesh->StaticMaterials[Section.MaterialIndex].MaterialSlotName : NAME_None;
if (MaterialToAdd)
{
//Need to check if the resource exists
FMaterialResource* Resource = MaterialToAdd->GetMaterialResource(GMaxRHIFeatureLevel);
if (!Resource)
{
MaterialToAdd = DefaultMaterial;
}
}
else
{
MaterialToAdd = DefaultMaterial;
}
FSectionInfo SectionInfo;
SectionInfo.Material = MaterialToAdd;
SectionInfo.MaterialSlotName = MaterialSlotNameToAdd;
SectionInfo.bCollisionEnabled = Section.bEnableCollision;
SectionInfo.bShadowCastingEnabled = Section.bCastShadow;
const int32 MaterialIdx = OutUniqueSections.Add(SectionInfo);
const int32 MaterialMapIdx = OutGlobalMaterialIndices.Add(MaterialIdx);
RemapMaterialArrayIndex[Section.MaterialIndex] = MaterialMapIdx;
}
// Update face material indices, only if we are merging old imported static mesh asset, since the new build do not allow to shuffle section at import.
if (SourceStaticMesh->ImportVersion < RemoveStaticMeshSkinxxWorkflow && OutRawMesh.FaceMaterialIndices.Num())
{
for (int32& MaterialIndex : OutRawMesh.FaceMaterialIndices)
{
int32 RemapMaterialIndex = RemapMaterialArrayIndex[MaterialIndex];
MaterialIndex = RemapMaterialIndex;
}
}
return true;
}
void FMeshUtilities::ExtractMeshDataForGeometryCache(FRawMesh& RawMesh, const FMeshBuildSettings& BuildSettings, TArray<FStaticMeshBuildVertex>& OutVertices, TArray<TArray<uint32> >& OutPerSectionIndices, int32 ImportVersion)
{
int32 NumWedges = RawMesh.WedgeIndices.Num();
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
bool bRecomputeNormals = (BuildSettings.bRecomputeNormals) || RawMesh.WedgeTangentZ.Num() == 0;
bool bRecomputeTangents = (BuildSettings.bRecomputeTangents) || RawMesh.WedgeTangentX.Num() == 0 || RawMesh.WedgeTangentY.Num() == 0;
// Dump normals and tangents if we are recomputing them.
if (bRecomputeTangents)
{
RawMesh.WedgeTangentX.Empty(NumWedges);
RawMesh.WedgeTangentX.AddZeroed(NumWedges);
RawMesh.WedgeTangentY.Empty(NumWedges);
RawMesh.WedgeTangentY.AddZeroed(NumWedges);
}
if (bRecomputeNormals)
{
RawMesh.WedgeTangentZ.Empty(NumWedges);
RawMesh.WedgeTangentZ.AddZeroed(NumWedges);
}
// Compute any missing tangents.
TMultiMap<int32, int32> OverlappingCorners;
if (bRecomputeNormals || bRecomputeTangents)
{
float ComparisonThreshold = GetComparisonThreshold(BuildSettings);
FindOverlappingCorners(OverlappingCorners, RawMesh, ComparisonThreshold);
// Static meshes always blend normals of overlapping corners.
uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
if (BuildSettings.bRemoveDegenerates)
{
// If removing degenerate triangles, ignore them when computing tangents.
TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
}
if (BuildSettings.bUseMikkTSpace)
{
ComputeTangents_MikkTSpace(RawMesh, OverlappingCorners, TangentOptions);
}
else
{
ComputeTangents(RawMesh, OverlappingCorners, TangentOptions);
}
}
// At this point the mesh will have valid tangents.
check(RawMesh.WedgeTangentX.Num() == NumWedges);
check(RawMesh.WedgeTangentY.Num() == NumWedges);
check(RawMesh.WedgeTangentZ.Num() == NumWedges);
TArray<int32> OutWedgeMap;
int32 MaxMaterialIndex = 1;
for (int32 FaceIndex = 0; FaceIndex < RawMesh.FaceMaterialIndices.Num(); FaceIndex++)
{
MaxMaterialIndex = FMath::Max<int32>(RawMesh.FaceMaterialIndices[FaceIndex], MaxMaterialIndex);
}
TMap<uint32, uint32> MaterialToSectionMapping;
for (int32 i = 0; i <= MaxMaterialIndex; ++i)
{
OutPerSectionIndices.Push(TArray<uint32>());
MaterialToSectionMapping.Add(i, i);
}
BuildStaticMeshVertexAndIndexBuffers(OutVertices, OutPerSectionIndices, OutWedgeMap, RawMesh, OverlappingCorners, MaterialToSectionMapping, KINDA_SMALL_NUMBER, BuildSettings.BuildScale3D, ImportVersion);
if (RawMesh.WedgeIndices.Num() < 100000 * 3)
{
CacheOptimizeVertexAndIndexBuffer(OutVertices, OutPerSectionIndices, OutWedgeMap);
check(OutWedgeMap.Num() == RawMesh.WedgeIndices.Num());
}
}
/*------------------------------------------------------------------------------
Mesh merging
------------------------------------------------------------------------------*/
bool FMeshUtilities::PropagatePaintedColorsToRawMesh(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, FRawMesh& RawMesh) const
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh->SourceModels.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData[LODIndex].OverrideVertexColors != nullptr)
{
FColorVertexBuffer& ColorVertexBuffer = *StaticMeshComponent->LODData[LODIndex].OverrideVertexColors;
FStaticMeshSourceModel& SrcModel = StaticMesh->SourceModels[LODIndex];
FStaticMeshRenderData& RenderData = *StaticMesh->RenderData;
FStaticMeshLODResources& RenderModel = RenderData.LODResources[LODIndex];
if (ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices())
{
int32 NumWedges = RawMesh.WedgeIndices.Num();
const bool bUseWedgeMap = RenderData.WedgeMap.Num() > 0 && RenderData.WedgeMap.Num() == NumWedges && !StaticMeshComponent->IsA<USplineMeshComponent>();
// If we have a wedge map
if (bUseWedgeMap)
{
if (RenderData.WedgeMap.Num() == NumWedges)
{
int32 NumExistingColors = RawMesh.WedgeColors.Num();
if (NumExistingColors < NumWedges)
{
RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors);
}
for (int32 i = 0; i < NumWedges; ++i)
{
FColor WedgeColor = FColor::White;
int32 Index = RenderData.WedgeMap[i];
if (Index != INDEX_NONE)
{
WedgeColor = ColorVertexBuffer.VertexColor(Index);
}
RawMesh.WedgeColors[i] = WedgeColor;
}
return true;
}
}
// No wedge map (this can happen when we poly reduce the LOD for example)
// Use index buffer directly
else
{
UE_LOG(LogMeshUtilities, Warning, TEXT("{%s} Wedge map size %d is wrong or empty. Expected %d. Falling back on using index buffer for propagating vertex painting"), *StaticMesh->GetName(), RenderData.WedgeMap.Num(), RawMesh.WedgeIndices.Num());
RawMesh.WedgeColors.SetNumUninitialized(NumWedges);
if (RawMesh.VertexPositions.Num() == ColorVertexBuffer.GetNumVertices())
{
for (int32 i = 0; i < NumWedges; ++i)
{
FColor WedgeColor = FColor::White;
uint32 VertIndex = RawMesh.WedgeIndices[i];
if (VertIndex < ColorVertexBuffer.GetNumVertices())
{
WedgeColor = ColorVertexBuffer.VertexColor(VertIndex);
}
RawMesh.WedgeColors[i] = WedgeColor;
}
return true;
}
}
}
}
return false;
}
static void TransformPhysicsGeometry(const FTransform& InTransform, FKAggregateGeom& AggGeom)
{
FTransform NoScaleInTransform = InTransform;
NoScaleInTransform.SetScale3D(FVector(1, 1, 1));
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKConvexElem& Elem : AggGeom.ConvexElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*InTransform);
}
// seems like all primitives except Convex need separate scaling pass
const FVector Scale3D = InTransform.GetScale3D();
if (!Scale3D.Equals(FVector(1.f)))
{
const float MinPrimSize = KINDA_SMALL_NUMBER;
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
Elem.ScaleElem(Scale3D, MinPrimSize);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
Elem.ScaleElem(Scale3D, MinPrimSize);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
Elem.ScaleElem(Scale3D, MinPrimSize);
}
}
}
static void ExtractPhysicsGeometry(UStaticMeshComponent* InMeshComponent, FKAggregateGeom& OutAggGeom)
{
UStaticMesh* SrcMesh = InMeshComponent->GetStaticMesh();
if (SrcMesh == nullptr)
{
return;
}
if (!SrcMesh->BodySetup)
{
return;
}
OutAggGeom = SrcMesh->BodySetup->AggGeom;
// Convert boxes to convex, so they can be sheared
for (int32 BoxIdx = 0; BoxIdx < OutAggGeom.BoxElems.Num(); BoxIdx++)
{
FKConvexElem* NewConvexColl = new(OutAggGeom.ConvexElems) FKConvexElem();
NewConvexColl->ConvexFromBoxElem(OutAggGeom.BoxElems[BoxIdx]);
}
OutAggGeom.BoxElems.Empty();
// we are not owner of this stuff
OutAggGeom.RenderInfo = nullptr;
for (FKConvexElem& Elem : OutAggGeom.ConvexElems)
{
Elem.SetConvexMesh(nullptr);
Elem.SetMirroredConvexMesh(nullptr);
}
// Transform geometry to world space
FTransform CtoM = InMeshComponent->GetComponentTransform();
TransformPhysicsGeometry(CtoM, OutAggGeom);
}
void FMeshUtilities::CalculateTextureCoordinateBoundsForRawMesh(const FRawMesh& InRawMesh, TArray<FBox2D>& OutBounds) const
{
const int32 NumWedges = InRawMesh.WedgeIndices.Num();
const int32 NumTris = NumWedges / 3;
OutBounds.Empty();
int32 WedgeIndex = 0;
for (int32 TriIndex = 0; TriIndex < NumTris; TriIndex++)
{
int MaterialIndex = InRawMesh.FaceMaterialIndices[TriIndex];
if (OutBounds.Num() <= MaterialIndex)
OutBounds.SetNumZeroed(MaterialIndex + 1);
{
int32 CachedWedgeIndex = WedgeIndex;
for (int32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS; ++UVIndex)
{
WedgeIndex = CachedWedgeIndex;
if (InRawMesh.WedgeTexCoords[UVIndex].Num())
{
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++, WedgeIndex++)
{
OutBounds[MaterialIndex] += InRawMesh.WedgeTexCoords[UVIndex][WedgeIndex];
}
}
}
}
}
}
void FMeshUtilities::CalculateTextureCoordinateBoundsForSkeletalMesh(const FStaticLODModel& LODModel, TArray<FBox2D>& OutBounds) const
{
TArray<FSoftSkinVertex> Vertices;
FMultiSizeIndexContainerData IndexData;
LODModel.GetVertices(Vertices);
LODModel.MultiSizeIndexContainer.GetIndexBufferData(IndexData);
const uint32 SectionCount = (uint32)LODModel.NumNonClothingSections();
check(OutBounds.Num() != 0);
for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
{
const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
const uint32 FirstIndex = Section.BaseIndex;
const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;
const int32 MaterialIndex = Section.MaterialIndex;
if (OutBounds.Num() <= MaterialIndex)
{
OutBounds.SetNumZeroed(MaterialIndex + 1);
}
for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
{
uint32 VertexIndex = IndexData.Indices[Index];
FSoftSkinVertex& Vertex = Vertices[VertexIndex];
FVector2D TexCoord = Vertex.UVs[0];
OutBounds[MaterialIndex] += TexCoord;
}
}
}
static void CopyTextureRect(const FColor* Src, const FIntPoint& SrcSize, FColor* Dst, const FIntPoint& DstSize, const FIntPoint& DstPos)
{
int32 RowLength = SrcSize.X*sizeof(FColor);
FColor* RowDst = Dst + DstSize.X*DstPos.Y;
const FColor* RowSrc = Src;
for (int32 RowIdx = 0; RowIdx < SrcSize.Y; ++RowIdx)
{
FMemory::Memcpy(RowDst + DstPos.X, RowSrc, RowLength);
RowDst += DstSize.X;
RowSrc += SrcSize.X;
}
}
static void SetTextureRect(const FColor& ColorValue, const FIntPoint& SrcSize, FColor* Dst, const FIntPoint& DstSize, const FIntPoint& DstPos)
{
FColor* RowDst = Dst + DstSize.X*DstPos.Y;
for (int32 RowIdx = 0; RowIdx < SrcSize.Y; ++RowIdx)
{
for (int32 ColIdx = 0; ColIdx < SrcSize.X; ++ColIdx)
{
RowDst[DstPos.X + ColIdx] = ColorValue;
}
RowDst += DstSize.X;
}
}
struct FRawMeshUVTransform
{
FVector2D Offset;
FVector2D Scale;
bool IsValid() const
{
return (Scale != FVector2D::ZeroVector);
}
};
static FVector2D GetValidUV(const FVector2D& UV)
{
FVector2D NewUV = UV;
// first make sure they're positive
if (UV.X < 0.0f)
{
NewUV.X = UV.X + FMath::CeilToInt(FMath::Abs(UV.X));
}
if (UV.Y < 0.0f)
{
NewUV.Y = UV.Y + FMath::CeilToInt(FMath::Abs(UV.Y));
}
// now make sure they're within [0, 1]
if (UV.X > 1.0f)
{
NewUV.X = FMath::Fmod(NewUV.X, 1.0f);
}
if (UV.Y > 1.0f)
{
NewUV.Y = FMath::Fmod(NewUV.Y, 1.0f);
}
return NewUV;
}
static void MergeFlattenedMaterials(TArray<struct FFlattenMaterial>& InMaterialList, FFlattenMaterial& OutMergedMaterial, TArray<FRawMeshUVTransform>& OutUVTransforms)
{
OutUVTransforms.Reserve(InMaterialList.Num());
// Fill output UV transforms with invalid values
for (auto Material : InMaterialList)
{
// Invalid UV transform
FRawMeshUVTransform UVTransform;
UVTransform.Offset = FVector2D::ZeroVector;
UVTransform.Scale = FVector2D::ZeroVector;
OutUVTransforms.Add(UVTransform);
}
int32 AtlasGridSize = FMath::CeilToInt(FMath::Sqrt(InMaterialList.Num()));
FIntPoint AtlasTextureSize = OutMergedMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse);
FIntPoint ExportTextureSize = AtlasTextureSize / AtlasGridSize;
int32 AtlasNumSamples = AtlasTextureSize.X*AtlasTextureSize.Y;
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
{
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
if (OutMergedMaterial.ShouldGenerateDataForProperty(Property))
{
check(OutMergedMaterial.GetPropertySize(Property) == AtlasTextureSize);
TArray<FColor>& Samples = OutMergedMaterial.GetPropertySamples(Property);
Samples.SetNumZeroed(AtlasNumSamples);
}
}
int32 AtlasRowIdx = 0;
int32 AtlasColIdx = 0;
FIntPoint AtlasTargetPos = FIntPoint(0, 0);
bool bSamplesWritten[(uint32)EFlattenMaterialProperties::NumFlattenMaterialProperties];
FMemory::Memset(bSamplesWritten, 0);
// Flatten all materials and merge them into one material using texture atlases
for (int32 MatIdx = 0; MatIdx < InMaterialList.Num(); ++MatIdx)
{
FFlattenMaterial& FlatMaterial = InMaterialList[MatIdx];
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
{
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
if (OutMergedMaterial.ShouldGenerateDataForProperty(Property) && FlatMaterial.DoesPropertyContainData(Property))
{
TArray<FColor>& SourceSamples = FlatMaterial.GetPropertySamples(Property);
TArray<FColor>& TargetSamples = OutMergedMaterial.GetPropertySamples(Property);
if (FlatMaterial.IsPropertyConstant(Property))
{
SetTextureRect(SourceSamples[0], ExportTextureSize, TargetSamples.GetData(), AtlasTextureSize, AtlasTargetPos);
}
else
{
FIntPoint PropertySize = FlatMaterial.GetPropertySize(Property);
PropertySize = ConditionalImageResize(PropertySize, ExportTextureSize, SourceSamples, false);
CopyTextureRect(SourceSamples.GetData(), ExportTextureSize, TargetSamples.GetData(), AtlasTextureSize, AtlasTargetPos);
FlatMaterial.SetPropertySize(Property, PropertySize);
}
bSamplesWritten[PropertyIndex] |= true;
}
}
check(OutUVTransforms.IsValidIndex(MatIdx));
OutUVTransforms[MatIdx].Offset = FVector2D(
(float)AtlasTargetPos.X / AtlasTextureSize.X,
(float)AtlasTargetPos.Y / AtlasTextureSize.Y);
OutUVTransforms[MatIdx].Scale = FVector2D(
(float)ExportTextureSize.X / AtlasTextureSize.X,
(float)ExportTextureSize.Y / AtlasTextureSize.Y);
AtlasColIdx++;
if (AtlasColIdx >= AtlasGridSize)
{
AtlasColIdx = 0;
AtlasRowIdx++;
}
AtlasTargetPos = FIntPoint(AtlasColIdx*ExportTextureSize.X, AtlasRowIdx*ExportTextureSize.Y);
}
// Check if some properties weren't populated with data (which means we can empty them out)
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
{
if (!bSamplesWritten[PropertyIndex])
{
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
OutMergedMaterial.GetPropertySamples(Property).Empty();
OutMergedMaterial.SetPropertySize(Property, FIntPoint(0, 0));
}
}
}
static void FlattenBinnedMaterials(TArray<struct FFlattenMaterial>& InMaterialList, const TArray<FBox2D>& InMaterialBoxes, FFlattenMaterial& OutMergedMaterial, TArray<FRawMeshUVTransform>& OutUVTransforms)
{
OutUVTransforms.Reserve(InMaterialList.Num());
// We support merging only for opaque materials
// Fill output UV transforms with invalid values
for (auto Material : InMaterialList)
{
// Invalid UV transform
FRawMeshUVTransform UVTransform;
UVTransform.Offset = FVector2D::ZeroVector;
UVTransform.Scale = FVector2D::ZeroVector;
OutUVTransforms.Add(UVTransform);
}
// Merge all material properties
for (int32 Index = 0; Index < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++Index)
{
const EFlattenMaterialProperties Property = (EFlattenMaterialProperties)Index;
const FIntPoint& OutTextureSize = OutMergedMaterial.GetPropertySize(Property);
if (OutTextureSize != FIntPoint::ZeroValue)
{
TArray<FColor>& OutSamples = OutMergedMaterial.GetPropertySamples(Property);
OutSamples.Reserve(OutTextureSize.X * OutTextureSize.Y);
OutSamples.SetNumZeroed(OutTextureSize.X * OutTextureSize.Y);
bool bMaterialsWritten = false;
for (int32 MaterialIndex = 0; MaterialIndex < InMaterialList.Num(); ++MaterialIndex)
{
// Determine output size and offset
FFlattenMaterial& FlatMaterial = InMaterialList[MaterialIndex];
if (FlatMaterial.DoesPropertyContainData(Property))
{
FBox2D MaterialBox = InMaterialBoxes[MaterialIndex];
const FIntPoint& InputSize = FlatMaterial.GetPropertySize(Property);
TArray<FColor>& InputSamples = FlatMaterial.GetPropertySamples(Property);
// Resize material to match output (area) size
FIntPoint OutputSize = FIntPoint(OutTextureSize.X * MaterialBox.GetSize().X, OutTextureSize.Y * MaterialBox.GetSize().Y);
ConditionalImageResize(InputSize, OutputSize, InputSamples, false);
// Copy material data to the merged 'atlas' texture
FIntPoint OutputPosition = FIntPoint(OutTextureSize.X * MaterialBox.Min.X, OutTextureSize.Y * MaterialBox.Min.Y);
CopyTextureRect(InputSamples.GetData(), OutputSize, OutSamples.GetData(), OutTextureSize, OutputPosition);
// Set the UV tranforms only once
if (Index == 0)
{
FRawMeshUVTransform& UVTransform = OutUVTransforms[MaterialIndex];
UVTransform.Offset = MaterialBox.Min;
UVTransform.Scale = MaterialBox.GetSize();
}
bMaterialsWritten = true;
}
}
if (!bMaterialsWritten)
{
OutSamples.Empty();
OutMergedMaterial.SetPropertySize(Property, FIntPoint(0, 0));
}
}
}
}
void FMeshUtilities::MergeActors(
const TArray<AActor*>& SourceActors,
const FMeshMergingSettings& InSettings,
UPackage* InOuter,
const FString& InBasePackageName,
int32 UseLOD, // does not build all LODs but only use this LOD to create base mesh
TArray<UObject*>& OutAssetsToSync,
FVector& OutMergedActorLocation,
bool bSilent) const
{
MergeActors(SourceActors, InSettings, InOuter, InBasePackageName, OutAssetsToSync, OutMergedActorLocation, bSilent);
}
void FMeshUtilities::MergeActors(
const TArray<AActor*>& SourceActors,
const FMeshMergingSettings& InSettings,
UPackage* InOuter,
const FString& InBasePackageName,
TArray<UObject*>& OutAssetsToSync,
FVector& OutMergedActorLocation,
bool bSilent) const
{
checkf(SourceActors.Num(), TEXT("No actors supplied for merging"));
TArray<UStaticMeshComponent*> ComponentsToMerge;
ComponentsToMerge.Reserve(SourceActors.Num());
// Collect static mesh components
for (AActor* Actor : SourceActors)
{
TInlineComponentArray<UStaticMeshComponent*> Components;
Actor->GetComponents<UStaticMeshComponent>(Components);
// Filter out bad components
for (UStaticMeshComponent* MeshComponent : Components)
{
if (MeshComponent->GetStaticMesh() != nullptr &&
MeshComponent->GetStaticMesh()->SourceModels.Num() > 0)
{
ComponentsToMerge.Add(MeshComponent);
}
}
}
checkf(SourceActors.Num(), TEXT("No valid components found in actors supplied for merging"));
UWorld* World = SourceActors[0]->GetWorld();
checkf(World != nullptr, TEXT("Invalid world retrieved from Actor"));
const float ScreenSize = TNumericLimits<float>::Max();
MergeStaticMeshComponents(ComponentsToMerge, World, InSettings, InOuter, InBasePackageName, OutAssetsToSync, OutMergedActorLocation, ScreenSize, bSilent);
}
void FMeshUtilities::MergeStaticMeshComponents(const TArray<UStaticMeshComponent*>& ComponentsToMerge, UWorld* World, const FMeshMergingSettings& InSettings, UPackage* InOuter, const FString& InBasePackageName, TArray<UObject*>& OutAssetsToSync, FVector& OutMergedActorLocation, const float ScreenSize, bool bSilent /*= false*/) const
{
FScopedSlowTask SlowTask(100.f, (LOCTEXT("MergeStaticMeshComponents_BuildingMesh", "Merging Static Mesh Components")));
SlowTask.MakeDialog();
TArray<FSectionInfo> UniqueSections;
TMap<FMeshIdAndLOD, TArray<int32>> MaterialMap;
TArray<FRawMeshExt> SourceMeshes;
bool bWithVertexColors[MAX_STATIC_MESH_LODS] = {};
bool bOcuppiedUVChannels[MAX_STATIC_MESH_LODS][MAX_MESH_TEXTURE_COORDS] = {};
UBodySetup* BodySetupSource = nullptr;
checkf(ComponentsToMerge.Num(), TEXT("No valid components supplied for merging"));
SourceMeshes.AddZeroed(ComponentsToMerge.Num());
// Use first mesh for naming and pivot
FString MergedAssetPackageName;
FVector MergedAssetPivot;
int32 NumMaxLOD = 0;
for (int32 MeshId = 0; MeshId < ComponentsToMerge.Num(); ++MeshId)
{
UStaticMeshComponent* MeshComponent = ComponentsToMerge[MeshId];
// Determine the maximum number of LOD levels found in the source meshes
NumMaxLOD = FMath::Max(NumMaxLOD, MeshComponent->GetStaticMesh()->SourceModels.Num());
// Save the pivot and asset package name of the first mesh, will later be used for creating merged mesh asset
if (MeshId == 0)
{
// Mesh component pivot point
MergedAssetPivot = InSettings.bPivotPointAtZero ? FVector::ZeroVector : MeshComponent->GetComponentTransform().GetLocation();
// Source mesh asset package name
MergedAssetPackageName = MeshComponent->GetStaticMesh()->GetOutermost()->GetName();
}
}
// Cap the number of LOD levels to the max
NumMaxLOD = FMath::Min(NumMaxLOD, MAX_STATIC_MESH_LODS);
int32 BaseLODIndex = 0;
// Are we going to export a single LOD or not
if (InSettings.LODSelectionType == EMeshLODSelectionType::SpecificLOD && InSettings.SpecificLOD >= 0)
{
// Will export only one specified LOD as LOD0 for the merged mesh
BaseLODIndex = FMath::Max(0, FMath::Min(InSettings.SpecificLOD, MAX_STATIC_MESH_LODS));
}
const bool bMergeAllAvailableLODs = InSettings.LODSelectionType == EMeshLODSelectionType::AllLODs;
SlowTask.EnterProgressFrame(10.0f, LOCTEXT("MergeStaticMeshComponents_RetrievingStaticMeshes", "Collecting Source Static Meshes"));
for (int32 MeshId = 0; MeshId < ComponentsToMerge.Num(); ++MeshId)
{
UStaticMeshComponent* StaticMeshComponent = ComponentsToMerge[MeshId];
// LOD index will be overridden if the user has chosen to pick it according to the viewing distance
int32 CalculatedLODIndex = -1;
if (InSettings.LODSelectionType == EMeshLODSelectionType::CalculateLOD && FMath::IsWithinInclusive(ScreenSize, 0.0f, 1.0f))
{
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
CalculatedLODIndex = Utilities->GetLODLevelForScreenSize(StaticMeshComponent, ScreenSize);
}
SourceMeshes[MeshId].SourceStaticMesh = StaticMeshComponent->GetStaticMesh();
// Retrieve the lowest available LOD level from the mesh
int32 StartLODIndex = InSettings.LODSelectionType == EMeshLODSelectionType::CalculateLOD ? CalculatedLODIndex : FMath::Min(BaseLODIndex, StaticMeshComponent->GetStaticMesh()->SourceModels.Num() - 1);
int32 EndLODIndex = bMergeAllAvailableLODs ? FMath::Min(StaticMeshComponent->GetStaticMesh()->SourceModels.Num(), MAX_STATIC_MESH_LODS) : StartLODIndex + 1;
SourceMeshes[MeshId].MaxLODExport = EndLODIndex - 1;
// Set export LOD index if we are exporting one specifically
SourceMeshes[MeshId].ExportLODIndex = !bMergeAllAvailableLODs ? StartLODIndex : -1;
for (int32 LODIndex = StartLODIndex; LODIndex < EndLODIndex; ++LODIndex)
{
// Store source static mesh and set LOD export flag
SourceMeshes[MeshId].SourceStaticMesh = StaticMeshComponent->GetStaticMesh();
SourceMeshes[MeshId].bShouldExportLOD[LODIndex] = false;
TArray<int32> MeshMaterialMap;
// Retrieve and construct raw mesh from source meshes
SourceMeshes[MeshId].MeshLODData[LODIndex].RawMesh = new FRawMesh();
FRawMesh* RawMeshLOD = SourceMeshes[MeshId].MeshLODData[LODIndex].RawMesh;
if ( ConstructRawMesh(StaticMeshComponent, LODIndex, InSettings.bBakeVertexDataToMesh || InSettings.bUseVertexDataForBakingMaterial, *RawMeshLOD, UniqueSections, MeshMaterialMap))
{
// Only flag the lod to be eligible for exporting if we found valid data
SourceMeshes[MeshId].bShouldExportLOD[LODIndex] = true;
// Check if vertex colours should be propagated
if (InSettings.bBakeVertexDataToMesh)
{
// Whether at least one of the meshes has vertex colors
bWithVertexColors[LODIndex] |= (RawMeshLOD->WedgeColors.Num() != 0);
}
// Which UV channels has data at least in one mesh
for (int32 ChannelIdx = 0; ChannelIdx < MAX_MESH_TEXTURE_COORDS; ++ChannelIdx)
{
bOcuppiedUVChannels[LODIndex][ChannelIdx] |= (RawMeshLOD->WedgeTexCoords[ChannelIdx].Num() != 0) && StaticMeshComponent->GetStaticMesh()->LightMapCoordinateIndex != ChannelIdx;
}
if ( InSettings.bUseLandscapeCulling )
{
// Landscape / volume culling
CullTrianglesFromVolumesAndUnderLandscapes(StaticMeshComponent, *RawMeshLOD);
if (!RawMeshLOD->IsValid())
{
RawMeshLOD = nullptr;
SourceMeshes[MeshId].bShouldExportLOD[LODIndex] = false;
}
}
if (SourceMeshes[MeshId].bShouldExportLOD[LODIndex])
{
MaterialMap.Add(FMeshIdAndLOD(MeshId, LODIndex), MeshMaterialMap);
//CalculateTextureCoordinateBoundsForRawMesh(*SourceMeshes[MeshId].MeshLODData[LODIndex].RawMesh, SourceMeshes[MeshId].MeshLODData[LODIndex].TexCoordBounds);
}
}
}
}
// Failed to retrieve static meshes/materials cannot merge anything
if (MaterialMap.Num() == 0)
{
return;
}
if (InSettings.bMergePhysicsData)
{
for (int32 MeshId = 0; MeshId < ComponentsToMerge.Num(); ++MeshId)
{
UStaticMeshComponent* MeshComponent = ComponentsToMerge[MeshId];
ExtractPhysicsGeometry(MeshComponent, SourceMeshes[MeshId].AggGeom);
// We will use first valid BodySetup as a source of physics settings
if (BodySetupSource == nullptr)
{
BodySetupSource = MeshComponent->GetStaticMesh()->BodySetup;
}
}
}
const bool bShouldBakeOutMaterials = InSettings.bMergeMaterials && !bMergeAllAvailableLODs;
SlowTask.EnterProgressFrame(bShouldBakeOutMaterials ? 10.0f : 40.0f, LOCTEXT("MergeStaticMeshComponents_RemapMaterials", "Remapping Duplicate Materials"));
// Remap material indices regardless of baking out materials or not (could give a draw call decrease)
TArray<bool> MeshShouldBakeVertexData;
MeshShouldBakeVertexData.AddZeroed(SourceMeshes.Num());
if (bShouldBakeOutMaterials && InSettings.bUseVertexDataForBakingMaterial)
{
// If we have UVs outside of the UV boundaries we should use unique UVs to render out the materials
CheckWrappingUVs(SourceMeshes, MeshShouldBakeVertexData);
}
TMap<FMeshIdAndLOD, TArray<int32> > NewMaterialMap;
TArray<FSectionInfo> NewSections;
FMaterialUtilities::RemapUniqueMaterialIndices(
UniqueSections,
SourceMeshes,
MaterialMap,
InSettings.MaterialSettings,
InSettings.bUseVertexDataForBakingMaterial,
InSettings.bMergeMaterials,
MeshShouldBakeVertexData,
NewMaterialMap,
NewSections);
// Use shared material data.
Exchange(MaterialMap, NewMaterialMap);
Exchange(UniqueSections, NewSections);
if (bShouldBakeOutMaterials)
{
// Should merge flattened materials into one texture
SlowTask.EnterProgressFrame(30.0f, LOCTEXT("MergeStaticMeshComponents_BakingDownMaterials", "Rendering out Materials"));
// Flatten Materials
TArray<FFlattenMaterial> FlattenedMaterials;
TArray<UMaterialInterface*> Materials;
for (const FSectionInfo& Section : UniqueSections)
{
Materials.Push(Section.Material);
}
FlattenMaterialsWithMeshData(Materials, SourceMeshes, MaterialMap, MeshShouldBakeVertexData, InSettings.MaterialSettings, FlattenedMaterials);
// Try to optimize materials where possible
for (FFlattenMaterial& InMaterial : FlattenedMaterials)
{
FMaterialUtilities::OptimizeFlattenMaterial(InMaterial);
}
FIntPoint AtlasTextureSize = InSettings.MaterialSettings.TextureSize;
FFlattenMaterial MergedFlatMaterial;
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, AtlasTextureSize);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, InSettings.MaterialSettings.bNormalMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, InSettings.MaterialSettings.bMetallicMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, InSettings.MaterialSettings.bRoughnessMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, InSettings.MaterialSettings.bSpecularMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, InSettings.MaterialSettings.bEmissiveMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, InSettings.MaterialSettings.bOpacityMap ? AtlasTextureSize : FIntPoint::ZeroValue);
MergedFlatMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, InSettings.MaterialSettings.bOpacityMaskMap ? AtlasTextureSize : FIntPoint::ZeroValue);
TArray<FRawMeshUVTransform> UVTransforms;
if (InSettings.bUseTextureBinning)
{
TArray<float> MaterialImportance;
FMaterialUtilities::DetermineMaterialImportance(Materials, MaterialImportance);
TArray<FBox2D> MaterialBoxes;
FMaterialUtilities::GeneratedBinnedTextureSquares(FVector2D(1.0f, 1.0f), MaterialImportance, MaterialBoxes);
FlattenBinnedMaterials(FlattenedMaterials, MaterialBoxes, MergedFlatMaterial, UVTransforms);
}
else
{
MergeFlattenedMaterials(FlattenedMaterials, MergedFlatMaterial, UVTransforms);
}
FMaterialUtilities::OptimizeFlattenMaterial(MergedFlatMaterial);
// Adjust UVs and remap material indices
for (int32 MeshIndex = 0; MeshIndex < SourceMeshes.Num(); ++MeshIndex)
{
const int32 LODIndex = SourceMeshes[MeshIndex].ExportLODIndex;
FRawMesh& RawMesh = *SourceMeshes[MeshIndex].MeshLODData[LODIndex].RawMesh;
if (RawMesh.VertexPositions.Num())
{
const TArray<int32> MaterialIndices = MaterialMap[FMeshIdAndLOD(MeshIndex, LODIndex)];
// If we end up in the situation where we have two of the same meshes which require baking vertex data (thus unique UVs), the first one to be found in the array will be used to bake out the material and generate new uvs for it. The other one however will not have the new UVs and thus the baked out material does not match up with its uvs which makes the mesh be UVed incorrectly with the new baked material.
if (!SourceMeshes[MeshIndex].MeshLODData[LODIndex].NewUVs.Num() && MeshShouldBakeVertexData[MeshIndex])
{
// Calculate the max bounds for this raw mesh
CalculateTextureCoordinateBoundsForRawMesh(*SourceMeshes[MeshIndex].MeshLODData[LODIndex].RawMesh, SourceMeshes[MeshIndex].MeshLODData[LODIndex].TexCoordBounds);
// Generate unique UVs
GenerateUniqueUVsForStaticMesh(*SourceMeshes[MeshIndex].MeshLODData[LODIndex].RawMesh, InSettings.MaterialSettings.TextureSize.GetMax(), SourceMeshes[MeshIndex].MeshLODData[LODIndex].NewUVs);
}
for (int32 UVChannelIdx = 0; UVChannelIdx < MAX_MESH_TEXTURE_COORDS; ++UVChannelIdx)
{
// Determine if we should use original or non-overlapping generated UVs
TArray<FVector2D>& UVs = SourceMeshes[MeshIndex].MeshLODData[LODIndex].NewUVs.Num() ? SourceMeshes[MeshIndex].MeshLODData[LODIndex].NewUVs : RawMesh.WedgeTexCoords[UVChannelIdx];
if (RawMesh.WedgeTexCoords[UVChannelIdx].Num() > 0)
{
int32 UVIdx = 0;
for (int32 FaceMaterialIndex : RawMesh.FaceMaterialIndices)
{
const FRawMeshUVTransform& UVTransform = UVTransforms[MaterialIndices[FaceMaterialIndex]];
if (UVTransform.IsValid())
{
FVector2D UV0 = GetValidUV(UVs[UVIdx + 0]);
FVector2D UV1 = GetValidUV(UVs[UVIdx + 1]);
FVector2D UV2 = GetValidUV(UVs[UVIdx + 2]);
RawMesh.WedgeTexCoords[UVChannelIdx][UVIdx + 0] = UV0 * UVTransform.Scale + UVTransform.Offset;
RawMesh.WedgeTexCoords[UVChannelIdx][UVIdx + 1] = UV1 * UVTransform.Scale + UVTransform.Offset;
RawMesh.WedgeTexCoords[UVChannelIdx][UVIdx + 2] = UV2 * UVTransform.Scale + UVTransform.Offset;
}
UVIdx += 3;
}
}
}
// Reset material indexes
for (int32& FaceMaterialIndex : RawMesh.FaceMaterialIndices)
{
FaceMaterialIndex = 0;
}
}
else
{
break;
}
}
// Create merged material asset
FString MaterialAssetName;
FString MaterialPackageName;
if (InBasePackageName.IsEmpty())
{
MaterialAssetName = TEXT("M_MERGED_") + FPackageName::GetShortName(MergedAssetPackageName);
MaterialPackageName = FPackageName::GetLongPackagePath(MergedAssetPackageName) + TEXT("/") + MaterialAssetName;
}
else
{
MaterialAssetName = TEXT("M_") + FPackageName::GetShortName(InBasePackageName);
MaterialPackageName = FPackageName::GetLongPackagePath(InBasePackageName) + TEXT("/") + MaterialAssetName;
}
UPackage* MaterialPackage = InOuter;
if (MaterialPackage == nullptr)
{
MaterialPackage = CreatePackage(nullptr, *MaterialPackageName);
check(MaterialPackage);
MaterialPackage->FullyLoad();
MaterialPackage->Modify();
}
UMaterialInstanceConstant* MergedMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(MaterialPackage, InSettings.MaterialSettings, MergedFlatMaterial, MaterialAssetName, MaterialPackageName, OutAssetsToSync);
// Set material static lighting usage flag if project has static lighting enabled
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
if (bAllowStaticLighting)
{
MergedMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
}
// Only end up with one material so clear array first
UniqueSections.Empty();
FSectionInfo NewSection;
NewSection.Material = MergedMaterial;
NewSection.bShadowCastingEnabled = true;
NewSection.bCollisionEnabled = false;
UniqueSections.Add(NewSection);
}
FRawMeshExt MergedMesh;
FMemory::Memset(&MergedMesh, 0, sizeof(MergedMesh));
// Flatten out the occupied UV channel flags, we need this to ensure the same amount of uv sets written out for each mesh
bool bFlattenedOcuppiedUVChannels[MAX_MESH_TEXTURE_COORDS];
FMemory::Memset(bFlattenedOcuppiedUVChannels, 0, sizeof(bool) * MAX_MESH_TEXTURE_COORDS);
bFlattenedOcuppiedUVChannels[0] = true; // Should always have one valid texture coordinate channel
for (int CoordinateIndex = 0; CoordinateIndex < MAX_MESH_TEXTURE_COORDS; ++CoordinateIndex)
{
for (int32 LODIndex = 0; LODIndex < MAX_STATIC_MESH_LODS; ++LODIndex)
{
bFlattenedOcuppiedUVChannels[CoordinateIndex] |= bOcuppiedUVChannels[LODIndex][CoordinateIndex];
}
}
SlowTask.EnterProgressFrame(40.0f, LOCTEXT("MergeStaticMeshComponents_MergingMeshesTogether", "Generating Output Static Mesh"));
FMeshSectionInfoMap SectionInfoMap;
int32 MaxExportLODs = bMergeAllAvailableLODs ? NumMaxLOD : 1;
// Merge meshes into single mesh
for (int32 SourceMeshIdx = 0; SourceMeshIdx < SourceMeshes.Num(); ++SourceMeshIdx)
{
for (int32 TargetLODIndex = 0; TargetLODIndex < MaxExportLODs; ++TargetLODIndex)
{
int32 SourceLODIndex = SourceMeshes[SourceMeshIdx].bShouldExportLOD[TargetLODIndex] ? TargetLODIndex : (SourceMeshes[SourceMeshIdx].MaxLODExport);
if (!bMergeAllAvailableLODs)
{
SourceLODIndex = SourceMeshes[SourceMeshIdx].ExportLODIndex;
}
// Allocate raw meshes where needed
if (MergedMesh.MeshLODData[TargetLODIndex].RawMesh == nullptr)
{
MergedMesh.MeshLODData[TargetLODIndex].RawMesh = new FRawMesh();
}
// Merge vertex data from source mesh list into single mesh
const FRawMesh& SourceRawMesh = *SourceMeshes[SourceMeshIdx].MeshLODData[SourceLODIndex].RawMesh;
if (SourceRawMesh.VertexPositions.Num() == 0)
{
continue;
}
const TArray<int32> MaterialIndices = MaterialMap[FMeshIdAndLOD(SourceMeshIdx, SourceLODIndex)];
check(MaterialIndices.Num() > 0);
FRawMesh& TargetRawMesh = *MergedMesh.MeshLODData[TargetLODIndex].RawMesh;
TargetRawMesh.FaceSmoothingMasks.Append(SourceRawMesh.FaceSmoothingMasks);
if (InSettings.bMergeMaterials && !bMergeAllAvailableLODs)
{
TargetRawMesh.FaceMaterialIndices.AddZeroed(SourceRawMesh.FaceMaterialIndices.Num());
}
else
{
for (const int32 Index : SourceRawMesh.FaceMaterialIndices)
{
TargetRawMesh.FaceMaterialIndices.Add(MaterialIndices[Index]);
}
}
int32 IndicesOffset = TargetRawMesh.VertexPositions.Num();
for (int32 Index : SourceRawMesh.WedgeIndices)
{
TargetRawMesh.WedgeIndices.Add(Index + IndicesOffset);
}
for (FVector VertexPos : SourceRawMesh.VertexPositions)
{
TargetRawMesh.VertexPositions.Add(VertexPos - MergedAssetPivot);
}
TargetRawMesh.WedgeTangentX.Append(SourceRawMesh.WedgeTangentX);
TargetRawMesh.WedgeTangentY.Append(SourceRawMesh.WedgeTangentY);
TargetRawMesh.WedgeTangentZ.Append(SourceRawMesh.WedgeTangentZ);
// Deal with vertex colors
// Some meshes may have it, in this case merged mesh will be forced to have vertex colors as well
if (InSettings.bBakeVertexDataToMesh)
{
if (bWithVertexColors[SourceLODIndex] && SourceRawMesh.WedgeColors.Num())
{
TargetRawMesh.WedgeColors.Append(SourceRawMesh.WedgeColors);
}
else
{
// In case this source mesh does not have vertex colors, fill target with 0xFF
int32 ColorsOffset = TargetRawMesh.WedgeColors.Num();
int32 ColorsNum = SourceRawMesh.WedgeIndices.Num();
TargetRawMesh.WedgeColors.AddUninitialized(ColorsNum);
FMemory::Memset(&TargetRawMesh.WedgeColors[ColorsOffset], 0xFF, ColorsNum*TargetRawMesh.WedgeColors.GetTypeSize());
}
}
// Merge all other UV channels
for (int32 ChannelIdx = 0; ChannelIdx < MAX_MESH_TEXTURE_COORDS; ++ChannelIdx)
{
// Whether this channel has data
if (bFlattenedOcuppiedUVChannels[ChannelIdx])
{
const TArray<FVector2D>& SourceChannel = SourceRawMesh.WedgeTexCoords[ChannelIdx];
TArray<FVector2D>& TargetChannel = TargetRawMesh.WedgeTexCoords[ChannelIdx];
// Whether source mesh has data in this channel
if (SourceChannel.Num())
{
TargetChannel.Append(SourceChannel);
}
else
{
// Fill with zero coordinates if source mesh has no data for this channel
const int32 TexCoordNum = SourceRawMesh.WedgeIndices.Num();
for (int32 CoordIdx = 0; CoordIdx < TexCoordNum; ++CoordIdx)
{
TargetChannel.Add(FVector2D::ZeroVector);
}
}
}
}
}
}
// Populate mesh section map
for (int32 TargetLODIndex = 0; TargetLODIndex < MaxExportLODs; ++TargetLODIndex)
{
TArray<uint32> UniqueMaterialIndices;
FRawMesh& TargetRawMesh = *MergedMesh.MeshLODData[TargetLODIndex].RawMesh;
for (uint32 MaterialIndex : TargetRawMesh.FaceMaterialIndices)
{
UniqueMaterialIndices.AddUnique(MaterialIndex);
}
for (int32 Index = 0; Index < UniqueMaterialIndices.Num(); ++Index)
{
const uint32 MaterialIndex = UniqueMaterialIndices[Index];
FSectionInfo StoredSectionInfo = UniqueSections[MaterialIndex];
FMeshSectionInfo SectionInfo;
SectionInfo.bCastShadow = StoredSectionInfo.bShadowCastingEnabled;
SectionInfo.bEnableCollision = StoredSectionInfo.bCollisionEnabled;
SectionInfo.MaterialIndex = MaterialIndex;
SectionInfoMap.Set(TargetLODIndex, Index, SectionInfo);
}
}
// Transform physics primitives to merged mesh pivot
if (InSettings.bMergePhysicsData && !MergedAssetPivot.IsZero())
{
FTransform PivotTM(-MergedAssetPivot);
for (auto& SourceMesh : SourceMeshes)
{
TransformPhysicsGeometry(PivotTM, SourceMesh.AggGeom);
}
}
// Compute target lightmap channel for each LOD, by looking at the first empty UV channel
int32 LightMapUVChannel = InSettings.bGenerateLightMapUV ? -1 : 0;
if (InSettings.bGenerateLightMapUV)
{
for (int32 ChannelIdx = 0; ChannelIdx < MAX_MESH_TEXTURE_COORDS; ++ChannelIdx)
{
bool bOccupied = false;
if (bFlattenedOcuppiedUVChannels[ChannelIdx])
{
continue;
}
else
{
LightMapUVChannel = ChannelIdx;
break;
}
}
if (LightMapUVChannel == -1)
{
// Output warning message
UE_LOG(LogMeshUtilities, Log, TEXT("Failed to find available lightmap uv channel"));
LightMapUVChannel = 0;
}
}
//
//Create merged mesh asset
//
SlowTask.EnterProgressFrame(10.0f, LOCTEXT("MergeStaticMeshComponents_CreatingAsset", "Creating Output Assets"));
{
FString AssetName;
FString PackageName;
if (InBasePackageName.IsEmpty())
{
AssetName = TEXT("SM_MERGED_") + FPackageName::GetShortName(MergedAssetPackageName);
PackageName = FPackageName::GetLongPackagePath(MergedAssetPackageName) + TEXT("/") + AssetName;
}
else
{
AssetName = FPackageName::GetShortName(InBasePackageName);
PackageName = InBasePackageName;
}
UPackage* Package = InOuter;
if (Package == nullptr)
{
Package = CreatePackage(NULL, *PackageName);
check(Package);
Package->FullyLoad();
Package->Modify();
}
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, *AssetName, RF_Public | RF_Standalone);
StaticMesh->InitResources();
FString OutputPath = StaticMesh->GetPathName();
// make sure it has a new lighting guid
StaticMesh->LightingGuid = FGuid::NewGuid();
if (InSettings.bGenerateLightMapUV)
{
StaticMesh->LightMapResolution = InSettings.TargetLightMapResolution;
StaticMesh->LightMapCoordinateIndex = LightMapUVChannel;
}
for (int32 LODIndex = 0; LODIndex < NumMaxLOD; ++LODIndex)
{
if (MergedMesh.MeshLODData[LODIndex].RawMesh != nullptr)
{
FRawMesh& MergedMeshLOD = *MergedMesh.MeshLODData[LODIndex].RawMesh;
if (MergedMeshLOD.VertexPositions.Num() > 0)
{
FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
/*Don't allow the engine to recalculate normals*/
SrcModel->BuildSettings.bRecomputeNormals = false;
SrcModel->BuildSettings.bRecomputeTangents = false;
SrcModel->BuildSettings.bRemoveDegenerates = false;
SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false;
SrcModel->BuildSettings.bUseFullPrecisionUVs = false;
SrcModel->BuildSettings.bGenerateLightmapUVs = InSettings.bGenerateLightMapUV;
SrcModel->BuildSettings.MinLightmapResolution = InSettings.TargetLightMapResolution;
SrcModel->BuildSettings.SrcLightmapIndex = 0;
SrcModel->BuildSettings.DstLightmapIndex = LightMapUVChannel;
SrcModel->RawMeshBulkData->SaveRawMesh(MergedMeshLOD);
}
}
}
// Assign materials
for (const FSectionInfo& Section : UniqueSections)
{
UMaterialInterface* Material = Section.Material;
if (Material && !Material->IsAsset())
{
Material = nullptr; // do not save non-asset materials
}
StaticMesh->StaticMaterials.Add(FStaticMaterial(Material, Section.MaterialSlotName));
}
if (InSettings.bMergePhysicsData)
{
StaticMesh->CreateBodySetup();
if (BodySetupSource)
{
StaticMesh->BodySetup->CopyBodyPropertiesFrom(BodySetupSource);
}
StaticMesh->BodySetup->AggGeom = FKAggregateGeom();
// Copy collision from the source meshes
for (const FRawMeshExt& SourceMesh : SourceMeshes)
{
StaticMesh->BodySetup->AddCollisionFrom(SourceMesh.AggGeom);
}
// Bake rotation into verts of convex hulls, so they scale correctly after rotation
for (FKConvexElem& ConvexElem : StaticMesh->BodySetup->AggGeom.ConvexElems)
{
ConvexElem.BakeTransformToVerts();
}
}
StaticMesh->SectionInfoMap.CopyFrom(SectionInfoMap);
//Set the Imported version before calling the build
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
StaticMesh->Build(bSilent);
StaticMesh->PostEditChange();
OutAssetsToSync.Add(StaticMesh);
OutMergedActorLocation = MergedAssetPivot;
}
for (FRawMeshExt& SourceMesh : SourceMeshes)
{
for (FMeshMergeData& Mergedata : SourceMesh.MeshLODData)
{
Mergedata.ReleaseData();
}
}
for (FMeshMergeData& Mergedata : MergedMesh.MeshLODData)
{
Mergedata.ReleaseData();
}
}
void FMeshUtilities::MergeStaticMeshComponents(const TArray<UStaticMeshComponent*>& ComponentsToMerge, UWorld* World, const FMeshMergingSettings& InSettings, UPackage* InOuter, const FString& InBasePackageName, int32 UseLOD, /* does not build all LODs but only use this LOD to create base mesh */ TArray<UObject*>& OutAssetsToSync, FVector& OutMergedActorLocation, const float ScreenAreaSize, bool bSilent /*= false*/) const
{
MergeStaticMeshComponents(ComponentsToMerge, World, InSettings, InOuter, InBasePackageName, OutAssetsToSync, OutMergedActorLocation, ScreenAreaSize, bSilent);
}
bool FMeshUtilities::RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const
{
IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshBoneReductionModule>("MeshBoneReduction");
IMeshBoneReduction * MeshBoneReductionInterface = MeshBoneReductionModule.GetMeshBoneReductionInterface();
return MeshBoneReductionInterface->ReduceBoneCounts(SkeletalMesh, LODIndex, BoneNamesToRemove);
}
/*------------------------------------------------------------------------------
Mesh reduction .
------------------------------------------------------------------------------*/
IMeshReduction* FMeshUtilities::GetStaticMeshReductionInterface()
{
return StaticMeshReduction;
}
IMeshReduction* FMeshUtilities::GetSkeletalMeshReductionInterface()
{
return SkeletalMeshReduction;
}
/*------------------------------------------------------------------------------
Mesh merging.
------------------------------------------------------------------------------*/
IMeshMerging* FMeshUtilities::GetMeshMergingInterface()
{
return MeshMerging;
}
class FMeshSimplifcationSettingsCustomization : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance()
{
return MakeShareable( new FMeshSimplifcationSettingsCustomization );
}
virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override
{
MeshReductionModuleProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMeshSimplificationSettings, MeshReductionModuleName));
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("General"));
IDetailPropertyRow& PropertyRow = Category.AddProperty(MeshReductionModuleProperty);
FDetailWidgetRow& WidgetRow = PropertyRow.CustomWidget();
WidgetRow.NameContent()
[
MeshReductionModuleProperty->CreatePropertyNameWidget()
];
WidgetRow.ValueContent()
.MaxDesiredWidth(0)
[
SNew(SComboButton)
.OnGetMenuContent(this, &FMeshSimplifcationSettingsCustomization::GenerateMeshSimplifierMenu)
.ContentPadding(FMargin(2.0f, 2.0f))
.ButtonContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FMeshSimplifcationSettingsCustomization::GetCurrentMeshSimplifierName)
]
];
}
private:
FText GetCurrentMeshSimplifierName() const
{
if(MeshReductionModuleProperty->IsValidHandle())
{
FText Name;
MeshReductionModuleProperty->GetValueAsDisplayText(Name);
return Name;
}
else
{
return LOCTEXT("AutomaticMeshReductionPlugin", "Automatic");
}
}
TSharedRef<SWidget> GenerateMeshSimplifierMenu() const
{
FMenuBuilder MenuBuilder(true, nullptr);
TArray<FName> ModuleNames;
FModuleManager::Get().FindModules(TEXT("*MeshReduction"), ModuleNames);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("AvailableReductionPluginsMenuSection", "Available Plugins"));
if(ModuleNames.Num() > 0)
{
for(FName ModuleName : ModuleNames)
{
FUIAction UIAction;
UIAction.ExecuteAction.BindSP(this, &FMeshSimplifcationSettingsCustomization::OnMeshSimplificationModuleChosen, ModuleName);
UIAction.GetActionCheckState.BindSP(this, &FMeshSimplifcationSettingsCustomization::IsMeshSimplificationModuleChosen, ModuleName);
MenuBuilder.AddMenuEntry( FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton );
}
MenuBuilder.AddMenuSeparator();
}
FUIAction OpenMarketplaceAction;
OpenMarketplaceAction.ExecuteAction.BindSP(this, &FMeshSimplifcationSettingsCustomization::OnFindReductionPluginsClicked);
FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu");
MenuBuilder.AddMenuEntry( LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction);
return MenuBuilder.MakeWidget();
}
void OnMeshSimplificationModuleChosen(FName ModuleName)
{
if(MeshReductionModuleProperty->IsValidHandle())
{
MeshReductionModuleProperty->SetValue(ModuleName);
}
}
ECheckBoxState IsMeshSimplificationModuleChosen(FName ModuleName)
{
if(MeshReductionModuleProperty->IsValidHandle())
{
FName CurrentModuleName;
MeshReductionModuleProperty->GetValue(CurrentModuleName);
return CurrentModuleName == ModuleName ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
return ECheckBoxState::Unchecked;
}
void OnFindReductionPluginsClicked()
{
FString URL;
FUnrealEdMisc::Get().GetURL(TEXT("MeshSimplificationPluginsURL"), URL);
FUnrealEdMisc::Get().OpenMarketplace(URL);
}
private:
TSharedPtr<IPropertyHandle> MeshReductionModuleProperty;
};
/*------------------------------------------------------------------------------
Module initialization / teardown.
------------------------------------------------------------------------------*/
void FMeshUtilities::StartupModule()
{
check(StaticMeshReduction == NULL);
check(SkeletalMeshReduction == NULL);
check(MeshMerging == NULL);
Processor = new FProxyGenerationProcessor();
FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.RegisterCustomClassLayout("MeshSimplificationSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FMeshSimplifcationSettingsCustomization::MakeInstance));
// This module could be launched very early by static meshes loading before the settings class that stores this value has had a chance to load. Have to read from the config file early in the startup process
FString MeshReductionModuleName;
GConfig->GetString(TEXT("/Script/Engine.MeshSimplificationSettings"), TEXT("r.MeshReductionModule"), MeshReductionModuleName, GEngineIni);
CVarMeshReductionModule->Set(*MeshReductionModuleName);
// Initially load the mesh reduction module that was previously saved in the settings
UpdateMeshReductionModule();
{
TArray<FName> SwarmModuleNames;
FModuleManager::Get().FindModules(TEXT("*SimplygonSwarm"), SwarmModuleNames);
// Look for MeshReduction interface
for (int32 Index = 0; Index < SwarmModuleNames.Num(); Index++)
{
IMeshReductionModule& MeshReductionModule = FModuleManager::LoadModuleChecked<IMeshReductionModule>(SwarmModuleNames[Index]);
// Look for distributed mesh merging interface
if (DistributedMeshMerging == NULL)
{
DistributedMeshMerging = MeshReductionModule.GetMeshMergingInterface();
if (DistributedMeshMerging)
{
UE_LOG(LogMeshUtilities, Log, TEXT("Using %s for distributed automatic mesh merging"), *SwarmModuleNames[Index].ToString());
}
}
}
if (!StaticMeshReduction)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No automatic static mesh reduction module available"));
}
if (!SkeletalMeshReduction)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No automatic skeletal mesh reduction module available"));
}
if (!MeshMerging)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No automatic mesh merging module available"));
}
else
{
MeshMerging->CompleteDelegate.BindRaw(Processor, &FProxyGenerationProcessor::ProxyGenerationComplete);
MeshMerging->FailedDelegate.BindRaw(Processor, &FProxyGenerationProcessor::ProxyGenerationFailed);
}
if (!DistributedMeshMerging)
{
UE_LOG(LogMeshUtilities, Log, TEXT("No distributed automatic mesh merging module available"));
}
else
{
DistributedMeshMerging->CompleteDelegate.BindRaw(Processor, &FProxyGenerationProcessor::ProxyGenerationComplete);
DistributedMeshMerging->FailedDelegate.BindRaw(Processor, &FProxyGenerationProcessor::ProxyGenerationFailed);
}
}
bDisableTriangleOrderOptimization = (CVarTriangleOrderOptimization.GetValueOnGameThread() == 2);
bUsingNvTriStrip = !bDisableTriangleOrderOptimization && (CVarTriangleOrderOptimization.GetValueOnGameThread() == 0);
// Construct and cache the version string for the mesh utilities module.
VersionString = FString::Printf(
TEXT("%s%s%s"),
MESH_UTILITIES_VER,
StaticMeshReduction ? *StaticMeshReduction->GetVersionString() : TEXT(""),
bUsingNvTriStrip ? TEXT("_NvTriStrip") : TEXT("")
);
bUsingSimplygon = VersionString.Contains(TEXT("Simplygon"));
// hook up level editor extension for skeletal mesh conversion
ModuleLoadedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddLambda([this](FName InModuleName, EModuleChangeReason InChangeReason)
{
if (InChangeReason == EModuleChangeReason::ModuleLoaded)
{
if (InModuleName == "LevelEditor")
{
AddLevelViewportMenuExtender();
}
else if (InModuleName == "AnimationBlueprintEditor")
{
AddAnimationBlueprintEditorToolbarExtender();
}
else if (InModuleName == "AnimationEditor")
{
AddAnimationEditorToolbarExtender();
}
else if (InModuleName == "SkeletalMeshEditor")
{
AddSkeletalMeshEditorToolbarExtender();
}
else if (InModuleName == "SkeletonEditor")
{
AddSkeletonEditorToolbarExtender();
}
}
});
}
void FMeshUtilities::ShutdownModule()
{
static const FName PropertyEditorModuleName("PropertyEditor");
if(FModuleManager::Get().IsModuleLoaded(PropertyEditorModuleName))
{
FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>(PropertyEditorModuleName);
PropertyEditorModule.UnregisterCustomClassLayout("MeshSimplificationSettings");
}
RemoveLevelViewportMenuExtender();
RemoveAnimationBlueprintEditorToolbarExtender();
RemoveAnimationEditorToolbarExtender();
RemoveSkeletalMeshEditorToolbarExtender();
RemoveSkeletonEditorToolbarExtender();
FModuleManager::Get().OnModulesChanged().Remove(ModuleLoadedDelegateHandle);
StaticMeshReduction = NULL;
SkeletalMeshReduction = NULL;
MeshMerging = NULL;
VersionString.Empty();
}
bool FMeshUtilities::GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const
{
// Create a copy of original mesh
FRawMesh TempMesh = RawMesh;
// Find overlapping corners for UV generator. Allow some threshold - this should not produce any error in a case if resulting
// mesh will not merge these vertices.
TMultiMap<int32, int32> OverlappingCorners;
FindOverlappingCorners(OverlappingCorners, RawMesh, THRESH_POINTS_ARE_SAME);
// Generate new UVs
FLayoutUV Packer(&TempMesh, 0, 1, FMath::Clamp(TextureResolution / 4, 32, 512));
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
// Save generated UVs
OutTexCoords = TempMesh.WedgeTexCoords[1];
}
return bPackSuccess;
}
bool FMeshUtilities::GenerateUniqueUVsForSkeletalMesh(const FStaticLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const
{
// Get easy to use SkeletalMesh data
TArray<FSoftSkinVertex> Vertices;
FMultiSizeIndexContainerData IndexData;
LODModel.GetVertices(Vertices);
LODModel.MultiSizeIndexContainer.GetIndexBufferData(IndexData);
int32 NumCorners = IndexData.Indices.Num();
// Generate FRawMesh from FStaticLODModel
FRawMesh TempMesh;
TempMesh.WedgeIndices.AddUninitialized(NumCorners);
TempMesh.WedgeTexCoords[0].AddUninitialized(NumCorners);
TempMesh.VertexPositions.AddUninitialized(NumCorners);
// Prepare vertex to wedge map
// PrevCorner[i] points to previous corner which shares the same wedge
TArray<int32> LastWedgeCorner;
LastWedgeCorner.AddUninitialized(Vertices.Num());
TArray<int32> PrevCorner;
PrevCorner.AddUninitialized(NumCorners);
for (int32 Index = 0; Index < Vertices.Num(); Index++)
{
LastWedgeCorner[Index] = -1;
}
for (int32 Index = 0; Index < NumCorners; Index++)
{
// Copy static vertex data
int32 VertexIndex = IndexData.Indices[Index];
FSoftSkinVertex& Vertex = Vertices[VertexIndex];
TempMesh.WedgeIndices[Index] = Index; // rudimental data, not really used by FLayoutUV - but array size matters
TempMesh.WedgeTexCoords[0][Index] = Vertex.UVs[0];
TempMesh.VertexPositions[Index] = Vertex.Position;
// Link all corners belonging to a single wedge into list
int32 PrevCornerIndex = LastWedgeCorner[VertexIndex];
LastWedgeCorner[VertexIndex] = Index;
PrevCorner[Index] = PrevCornerIndex;
}
// return GenerateUniqueUVsForStaticMesh(TempMesh, TextureResolution, OutTexCoords);
// Build overlapping corners map
TMultiMap<int32, int32> OverlappingCorners;
for (int32 Index = 0; Index < NumCorners; Index++)
{
int VertexIndex = IndexData.Indices[Index];
for (int32 CornerIndex = LastWedgeCorner[VertexIndex]; CornerIndex >= 0; CornerIndex = PrevCorner[CornerIndex])
{
if (CornerIndex != Index)
{
OverlappingCorners.Add(Index, CornerIndex);
}
}
}
// Generate new UVs
FLayoutUV Packer(&TempMesh, 0, 1, FMath::Clamp(TextureResolution / 4, 32, 512));
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
// Save generated UVs
OutTexCoords = TempMesh.WedgeTexCoords[1];
}
return bPackSuccess;
}
void FMeshUtilities::CalculateTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutTangentX, TArray<FVector>& OutTangentY, TArray<FVector>& OutNormals) const
{
const float ComparisonThreshold = (InTangentOptions & ETangentOptions::IgnoreDegenerateTriangles ) ? THRESH_POINTS_ARE_SAME : 0.0f;
TMultiMap<int32, int32> OverlappingCorners;
FindOverlappingCorners(OverlappingCorners, InVertices, InIndices, ComparisonThreshold);
ComputeTangents(InVertices, InIndices, InUVs, InSmoothingGroupIndices, OverlappingCorners, OutTangentX, OutTangentY, OutNormals, InTangentOptions);
}
void FMeshUtilities::AddAnimationBlueprintEditorToolbarExtender()
{
IAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::Get().LoadModuleChecked<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
auto& ToolbarExtenders = AnimationBlueprintEditorModule.GetAllAnimationBlueprintEditorToolbarExtenders();
ToolbarExtenders.Add(IAnimationBlueprintEditorModule::FAnimationBlueprintEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetAnimationBlueprintEditorToolbarExtender));
AnimationBlueprintEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
}
void FMeshUtilities::RemoveAnimationBlueprintEditorToolbarExtender()
{
IAnimationBlueprintEditorModule* AnimationBlueprintEditorModule = FModuleManager::Get().GetModulePtr<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
if (AnimationBlueprintEditorModule)
{
typedef IAnimationBlueprintEditorModule::FAnimationBlueprintEditorToolbarExtender DelegateType;
AnimationBlueprintEditorModule->GetAllAnimationBlueprintEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == AnimationBlueprintEditorExtenderHandle; });
}
}
TSharedRef<FExtender> FMeshUtilities::GetAnimationBlueprintEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationBlueprintEditor> InAnimationBlueprintEditor)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
UMeshComponent* MeshComponent = Cast<UMeshComponent>(InAnimationBlueprintEditor->GetPersonaToolkit()->GetPreviewMeshComponent());
Extender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddConvertComponentToStaticMeshToToolbar, MeshComponent)
);
return Extender;
}
void FMeshUtilities::AddAnimationEditorToolbarExtender()
{
IAnimationEditorModule& AnimationEditorModule = FModuleManager::Get().LoadModuleChecked<IAnimationEditorModule>("AnimationEditor");
auto& ToolbarExtenders = AnimationEditorModule.GetAllAnimationEditorToolbarExtenders();
ToolbarExtenders.Add(IAnimationEditorModule::FAnimationEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetAnimationEditorToolbarExtender));
AnimationEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
}
void FMeshUtilities::RemoveAnimationEditorToolbarExtender()
{
IAnimationEditorModule* AnimationEditorModule = FModuleManager::Get().GetModulePtr<IAnimationEditorModule>("AnimationEditor");
if (AnimationEditorModule)
{
typedef IAnimationEditorModule::FAnimationEditorToolbarExtender DelegateType;
AnimationEditorModule->GetAllAnimationEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == AnimationEditorExtenderHandle; });
}
}
TSharedRef<FExtender> FMeshUtilities::GetAnimationEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationEditor> InAnimationEditor)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
UMeshComponent* MeshComponent = Cast<UMeshComponent>(InAnimationEditor->GetPersonaToolkit()->GetPreviewMeshComponent());
Extender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddConvertComponentToStaticMeshToToolbar, MeshComponent)
);
return Extender;
}
void FMeshUtilities::AddSkeletalMeshEditorToolbarExtender()
{
ISkeletalMeshEditorModule& SkeletalMeshEditorModule = FModuleManager::Get().LoadModuleChecked<ISkeletalMeshEditorModule>("SkeletalMeshEditor");
auto& ToolbarExtenders = SkeletalMeshEditorModule.GetAllSkeletalMeshEditorToolbarExtenders();
ToolbarExtenders.Add(ISkeletalMeshEditorModule::FSkeletalMeshEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetSkeletalMeshEditorToolbarExtender));
SkeletalMeshEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
}
void FMeshUtilities::RemoveSkeletalMeshEditorToolbarExtender()
{
ISkeletalMeshEditorModule* SkeletalMeshEditorModule = FModuleManager::Get().GetModulePtr<ISkeletalMeshEditorModule>("SkeletalMeshEditor");
if (SkeletalMeshEditorModule)
{
typedef ISkeletalMeshEditorModule::FSkeletalMeshEditorToolbarExtender DelegateType;
SkeletalMeshEditorModule->GetAllSkeletalMeshEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == SkeletalMeshEditorExtenderHandle; });
}
}
TSharedRef<FExtender> FMeshUtilities::GetSkeletalMeshEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletalMeshEditor> InSkeletalMeshEditor)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
UMeshComponent* MeshComponent = Cast<UMeshComponent>(InSkeletalMeshEditor->GetPersonaToolkit()->GetPreviewMeshComponent());
Extender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddConvertComponentToStaticMeshToToolbar, MeshComponent)
);
return Extender;
}
void FMeshUtilities::AddSkeletonEditorToolbarExtender()
{
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::Get().LoadModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
auto& ToolbarExtenders = SkeletonEditorModule.GetAllSkeletonEditorToolbarExtenders();
ToolbarExtenders.Add(ISkeletonEditorModule::FSkeletonEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetSkeletonEditorToolbarExtender));
SkeletonEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
}
void FMeshUtilities::RemoveSkeletonEditorToolbarExtender()
{
ISkeletonEditorModule* SkeletonEditorModule = FModuleManager::Get().GetModulePtr<ISkeletonEditorModule>("SkeletonEditor");
if (SkeletonEditorModule)
{
typedef ISkeletonEditorModule::FSkeletonEditorToolbarExtender DelegateType;
SkeletonEditorModule->GetAllSkeletonEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == SkeletonEditorExtenderHandle; });
}
}
TSharedRef<FExtender> FMeshUtilities::GetSkeletonEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletonEditor> InSkeletonEditor)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
UMeshComponent* MeshComponent = Cast<UMeshComponent>(InSkeletonEditor->GetPersonaToolkit()->GetPreviewMeshComponent());
Extender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddConvertComponentToStaticMeshToToolbar, MeshComponent)
);
return Extender;
}
void FMeshUtilities::HandleAddConvertComponentToStaticMeshToToolbar(FToolBarBuilder& ParentToolbarBuilder, UMeshComponent* InMeshComponent)
{
ParentToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateLambda([this, InMeshComponent]()
{
ConvertMeshesToStaticMesh(TArray<UMeshComponent*>({ InMeshComponent }), InMeshComponent->GetComponentToWorld());
})),
NAME_None,
LOCTEXT("MakeStaticMesh", "Make Static Mesh"),
LOCTEXT("MakeStaticMeshTooltip", "Make a new static mesh out of the preview's current pose."),
FSlateIcon("EditorStyle", "Persona.ConvertToStaticMesh")
);
}
void FMeshUtilities::AddLevelViewportMenuExtender()
{
FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked<FLevelEditorModule>("LevelEditor");
auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FMeshUtilities::GetLevelViewportContextMenuExtender));
LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle();
}
void FMeshUtilities::RemoveLevelViewportMenuExtender()
{
if (LevelViewportExtenderHandle.IsValid())
{
FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr<FLevelEditorModule>("LevelEditor");
if (LevelEditorModule)
{
typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType;
LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; });
}
}
}
/** Util for getting all MeshComponents from a supplied set of Actors */
void GetSkinnedAndStaticMeshComponentsFromActors(const TArray<AActor*> InActors, TArray<UMeshComponent*>& OutMeshComponents)
{
for (AActor* Actor : InActors)
{
// add all components from this actor
TInlineComponentArray<UMeshComponent*> ActorComponents(Actor);
for (UMeshComponent* ActorComponent : ActorComponents)
{
if (ActorComponent->IsA(USkinnedMeshComponent::StaticClass()) || ActorComponent->IsA(UStaticMeshComponent::StaticClass()))
{
OutMeshComponents.AddUnique(ActorComponent);
}
}
// add all attached actors
TArray<AActor*> AttachedActors;
Actor->GetAttachedActors(AttachedActors);
for (AActor* AttachedActor : AttachedActors)
{
TInlineComponentArray<UMeshComponent*> AttachedActorComponents(AttachedActor);
for (UMeshComponent* AttachedActorComponent : AttachedActorComponents)
{
if (AttachedActorComponent->IsA(USkinnedMeshComponent::StaticClass()) || AttachedActorComponent->IsA(UStaticMeshComponent::StaticClass()))
{
OutMeshComponents.AddUnique(AttachedActorComponent);
}
}
}
}
}
TSharedRef<FExtender> FMeshUtilities::GetLevelViewportContextMenuExtender(const TSharedRef<FUICommandList> CommandList, const TArray<AActor*> InActors)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
if (InActors.Num() > 0)
{
TArray<UMeshComponent*> Components;
GetSkinnedAndStaticMeshComponentsFromActors(InActors, Components);
if (Components.Num() > 0)
{
FText ActorName = InActors.Num() == 1 ? FText::Format(LOCTEXT("ActorNameSingular", "\"{0}\""), FText::FromString(InActors[0]->GetActorLabel())) : LOCTEXT("ActorNamePlural", "Actors");
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
TSharedRef<FUICommandList> LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions();
Extender->AddMenuExtension("ActorControl", EExtensionHook::After, LevelEditorCommandBindings, FMenuExtensionDelegate::CreateLambda(
[this, ActorName, InActors](FMenuBuilder& MenuBuilder) {
MenuBuilder.AddMenuEntry(
FText::Format(LOCTEXT("ConvertSelectedActorsToStaticMeshText", "Convert {0} To Static Mesh"), ActorName),
LOCTEXT("ConvertSelectedActorsToStaticMeshTooltip", "Convert the selected actor's meshes to a new Static Mesh asset. Supports static and skeletal meshes."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(this, &FMeshUtilities::ConvertActorMeshesToStaticMesh, InActors))
);
})
);
}
}
return Extender;
}
void FMeshUtilities::ConvertActorMeshesToStaticMesh(const TArray<AActor*> InActors)
{
TArray<UMeshComponent*> MeshComponents;
GetSkinnedAndStaticMeshComponentsFromActors(InActors, MeshComponents);
auto GetActorRootTransform = [](AActor* InActor)
{
FTransform RootTransform(FTransform::Identity);
if (ACharacter* Character = Cast<ACharacter>(InActor))
{
RootTransform = Character->GetTransform();
RootTransform.SetLocation(RootTransform.GetLocation() - FVector(0.0f, 0.0f, Character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight()));
}
else
{
// otherwise just use the actor's origin
RootTransform = InActor->GetTransform();
}
return RootTransform;
};
// now pick a root transform
FTransform RootTransform(FTransform::Identity);
if (InActors.Num() == 1)
{
RootTransform = GetActorRootTransform(InActors[0]);
}
else
{
// multiple actors use the average of their origins, with Z being the min of all origins. Rotation is identity for simplicity
FVector Location(FVector::ZeroVector);
float MinZ = FLT_MAX;
for (AActor* Actor : InActors)
{
FTransform ActorTransform(GetActorRootTransform(Actor));
Location += ActorTransform.GetLocation();
MinZ = FMath::Min(ActorTransform.GetLocation().Z, MinZ);
}
Location /= (float)InActors.Num();
Location.Z = MinZ;
RootTransform.SetLocation(Location);
}
ConvertMeshesToStaticMesh(MeshComponents, RootTransform);
}
#undef LOCTEXT_NAMESPACE