From 76003579ddf6605a0fac7c81b2eceb9e614c22be Mon Sep 17 00:00:00 2001 From: lina halper Date: Fri, 11 Jan 2019 22:36:30 -0500 Subject: [PATCH] Copying //UE4/Dev-Anim to Main (//UE4/Main) [FYI] Laurent.Delayen, Thomas.Sarkanen #rb: none #lockdown thomas.sarkanen #ROBOMERGE-OWNER: ryan.gerleve #ROBOMERGE-AUTHOR: lina.halper #ROBOMERGE-SOURCE: CL 4715449 in //UE4/Main/... #ROBOMERGE-BOT: ENGINE (Main -> Dev-Networking) [CL 4715461 by lina halper in Dev-Networking branch] --- Engine/Build/Commit.gitdeps.xml | 16 + Engine/Config/BaseEngine.ini | 4 + .../AnimationSharing/AnimationSharing.uplugin | 35 + .../AnimationSharing.Build.cs | 31 + .../Private/AdditiveAnimationInstance.cpp | 76 + .../Private/AnimationSharingInstances.cpp | 47 + .../Private/AnimationSharingManager.cpp | 2108 +++++++++++ .../Private/AnimationSharingModule.cpp | 50 + .../Private/AnimationSharingSetup.cpp | 30 + .../Private/TransitionBlendInstance.cpp | 72 + .../Public/AdditiveAnimationInstance.h | 30 + .../Public/AnimationSharingInstances.h | 80 + .../Public/AnimationSharingManager.h | 555 +++ .../Public/AnimationSharingModule.h | 36 + .../Public/AnimationSharingSetup.h | 27 + .../Public/AnimationSharingTypes.h | 181 + .../Public/TransitionBlendInstance.h | 28 + .../AnimationSharingEd.Build.cs | 32 + .../Private/AnimationSharingEdModule.cpp | 43 + .../Private/AnimationSharingSetupFactory.cpp | 22 + .../SetupDetailsViewCustomizations.cpp | 379 ++ .../Public/AnimationSharingEdModule.h | 22 + .../Public/AnimationSharingSetupFactory.h | 25 + .../AssetTypeActions_AnimationSharingSetup.h | 18 + .../Public/SetupDetailsViewCustomizations.h | 74 + .../Private/AlembicImportOptions.cpp | 24 +- .../Private/AlembicImportOptions.h | 1 + .../Private/Editor/ControlRigEditor.cpp | 3 + .../AnimationBudgetBlueprintLibrary.cpp | 20 + .../Private/AnimationBudgetBlueprintLibrary.h | 23 + .../Developer/AssetTools/AssetTools.Build.cs | 2 + .../AssetTools/Private/AssetTools.cpp | 2 + ...peActions_AnimCurveCompressionSettings.cpp | 110 + ...TypeActions_AnimCurveCompressionSettings.h | 27 + .../AssetTypeActions_AnimSequence.cpp | 30 +- .../AssetTypeActions_AnimSequence.h | 3 + .../MeshBuilder/MeshBuilder.Build.cs | 14 - .../MeshUtilities/MeshUtilities.Build.cs | 14 - .../Private/SimplygonMeshReduction.cpp | 3179 ----------------- .../Public/SimplygonTypes.h | 535 --- .../SimplygonMeshReduction.Build.cs | 46 - .../Private/SimplygonRESTClient.cpp | 1341 ------- .../SimplygonSwarm/Private/SimplygonSwarm.cpp | 1759 --------- .../Public/SimplygonRESTClient.h | 367 -- .../Public/SimplygonSwarmCommon.h | 26 - .../Public/SimplygonSwarmHelpers.h | 140 - .../SimplygonSwarm/SimplygonSwarm.Build.cs | 72 - .../Private/AdvancedPreviewScene.cpp | 42 +- .../Public/AssetViewerSettings.h | 5 + .../Private/AnimationModifier.cpp | 6 + .../Private/AnimationModifierHelpers.h | 63 + .../Private/AnimationModifiersModule.cpp | 30 + .../Private/AnimationModifiersModule.h | 4 + ...SAnimationModifierContentBrowserWindow.cpp | 267 ++ .../SAnimationModifierContentBrowserWindow.h | 67 + .../Private/SAnimationModifiersTab.cpp | 62 +- .../Private/SAnimationModifiersTab.h | 5 +- .../Private/SModifierListview.cpp | 29 +- .../Public/IAnimationModifiersModule.h | 6 + .../Private/SkeletalControlNodeDetails.cpp | 17 +- .../Private/SkeletalControlNodeDetails.h | 2 +- .../EditorStyle/Private/SlateEditorStyle.cpp | 4 + .../Editor/Kismet/Private/BlueprintEditor.cpp | 12 + .../Private/MergeProxyUtils/Utils.cpp | 20 +- .../Private/AnimViewportShowCommands.cpp | 2 - .../Private/AnimViewportShowCommands.h | 3 - .../Private/AnimationEditorViewportClient.cpp | 6 + .../Persona/Private/SAnimCurvePanel.cpp | 17 +- .../Persona/Private/SAnimViewportToolBar.cpp | 2 +- .../Private/SAnimationEditorViewport.cpp | 40 +- .../Private/SAnimationEditorViewport.h | 7 - .../Private/SkeletalMeshEditor.cpp | 74 + .../Private/SkeletalMeshEditor.h | 2 + .../Private/SkeletalMeshEditorCommands.cpp | 2 + .../Private/SkeletalMeshEditorCommands.h | 3 + .../SkeletalMeshEditor.Build.cs | 4 +- .../Private/EditableSkeleton.cpp | 4 +- .../AnimCurveCompressionSettingsFactory.h | 17 + .../Commandlets/ContentCommandlets.cpp | 41 +- .../Editor/UnrealEd/Private/EditorEngine.cpp | 1 + .../AnimCurveCompressionSettingsFactory.cpp | 20 + .../Configuration/TargetRules.cs | 45 +- .../Configuration/UEBuildPlatform.cs | 2 +- .../Platform/Android/UEBuildAndroid.cs | 4 +- .../Platform/HTML5/UEBuildHTML5.cs | 4 +- .../Platform/IOS/UEBuildIOS.cs | 4 +- .../Platform/Linux/UEBuildLinux.cs | 7 +- .../Platform/Mac/UEBuildMac.cs | 4 +- .../Platform/Windows/UEBuildWindows.cs | 5 +- .../UnrealBuildTool/System/RulesAssembly.cs | 4 +- .../UnrealMultiUserServer.Target.cs | 2 - .../Animation/AnimCurveCompressionCodec.h | 75 + ...urveCompressionCodec_CompressedRichCurve.h | 42 + ...imCurveCompressionCodec_UniformlySampled.h | 38 + .../Animation/AnimCurveCompressionSettings.h | 45 + .../Engine/Classes/Animation/AnimMontage.h | 22 +- .../Engine/Classes/Animation/AnimSequence.h | 29 +- .../Classes/Animation/AnimSequenceBase.h | 4 +- .../Runtime/Engine/Classes/Curves/RichCurve.h | 119 + .../Animation/AnimCompressionDerivedData.cpp | 18 +- .../Animation/AnimCompressionDerivedData.h | 2 +- .../Animation/AnimCurveCompressionCodec.cpp | 42 + ...veCompressionCodec_CompressedRichCurve.cpp | 137 + ...CurveCompressionCodec_UniformlySampled.cpp | 293 ++ .../AnimCurveCompressionSettings.cpp | 74 + .../Private/Animation/AnimCurveTypes.cpp | 7 +- .../Animation/AnimNode_SubInstance.cpp | 13 +- .../Engine/Private/Animation/AnimSequence.cpp | 212 +- .../Private/Animation/AnimSequenceBase.cpp | 13 + .../Private/Animation/AnimationUtils.cpp | 77 + .../Components/SkinnedMeshComponent.cpp | 1 + .../Engine/Private/Curves/RichCurve.cpp | 821 ++++- .../Runtime/Engine/Private/SkeletalMesh.cpp | 1 + .../Private/SkeletalMeshComponentPhysics.cpp | 3 +- .../Engine/Private/SkeletalRenderGPUSkin.cpp | 4 +- .../Runtime/Engine/Private/UnrealEngine.cpp | 2 +- .../Engine/Public/Animation/AnimTypes.h | 6 + .../Runtime/Engine/Public/AnimationUtils.h | 12 + Engine/Source/ThirdParty/SPL/SPL.Build.cs | 46 - Engine/Source/ThirdParty/SPL/SPL.tps | 16 - Engine/Source/ThirdParty/SSF/SSF.Build.cs | 45 - Engine/Source/ThirdParty/SSF/SSF.tps | 7 - .../ThirdParty/Simplygon/Simplygon.Build.cs | 55 - .../Source/ThirdParty/Simplygon/Simplygon.tps | 7 - 124 files changed, 7006 insertions(+), 8008 deletions(-) create mode 100644 Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h create mode 100644 Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h create mode 100644 Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp create mode 100644 Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h create mode 100644 Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp create mode 100644 Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h delete mode 100644 Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp delete mode 100644 Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h delete mode 100644 Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs delete mode 100644 Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp delete mode 100644 Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp delete mode 100644 Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h delete mode 100644 Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h delete mode 100644 Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h delete mode 100644 Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs create mode 100644 Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h create mode 100644 Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp create mode 100644 Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h create mode 100644 Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h create mode 100644 Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp create mode 100644 Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp delete mode 100644 Engine/Source/ThirdParty/SPL/SPL.Build.cs delete mode 100644 Engine/Source/ThirdParty/SPL/SPL.tps delete mode 100644 Engine/Source/ThirdParty/SSF/SSF.Build.cs delete mode 100644 Engine/Source/ThirdParty/SSF/SSF.tps delete mode 100644 Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs delete mode 100644 Engine/Source/ThirdParty/Simplygon/Simplygon.tps diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index c82949531668..dab137cf068b 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -5091,6 +5091,7 @@ + @@ -5867,6 +5868,8 @@ + + @@ -27501,6 +27504,10 @@ + + + + @@ -41910,6 +41917,7 @@ + @@ -44283,6 +44291,7 @@ + @@ -46804,6 +46813,7 @@ + @@ -48555,6 +48565,7 @@ + @@ -55236,6 +55247,7 @@ + @@ -55787,6 +55799,7 @@ + @@ -64331,6 +64344,7 @@ + @@ -66342,6 +66356,7 @@ + @@ -66362,6 +66377,7 @@ + diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini index 7ca17d27720e..e1f99a0158f4 100644 --- a/Engine/Config/BaseEngine.ini +++ b/Engine/Config/BaseEngine.ini @@ -2156,6 +2156,10 @@ HMDWornMovementThreshold = 50.0 [/Script/Engine.AnimationSettings] bStripAnimationDataOnDedicatedServer=False + +[Animation.DefaultObjectSettings] +CurveCompressionSettings="/Engine/Animation/DefaultAnimCurveCompressionSettings" + [/Script/Engine.MeshSimplificationSettings] r.MeshReductionModule="QuadricMeshReduction" diff --git a/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin b/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin new file mode 100644 index 000000000000..6071a35ea124 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin @@ -0,0 +1,35 @@ +{ + "FileVersion": 1, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Animation Sharing", + "Description": "Plugin to create Shared Animation systems using the Master-Child pose functionality", + "Category": "Programming", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "AnimationSharing", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "AnimationSharingEd", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "SignificanceManager", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs new file mode 100644 index 000000000000..2ea79518ea8c --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs @@ -0,0 +1,31 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class AnimationSharing : ModuleRules +{ + public AnimationSharing(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "EngineSettings", + "SignificanceManager" + } + ); + + if (Target.Type == TargetType.Editor) + { + PrivateDependencyModuleNames.Add("TargetPlatform"); + } + + PrivateIncludePaths.AddRange( + new string[] { + "AnimationSharing/Private" + }); + } +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp new file mode 100644 index 000000000000..6c24680622a4 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AdditiveAnimationInstance.h" +#include "AnimationSharingInstances.h" +#include "AnimationSharingManager.h" + +FAdditiveAnimationInstance::FAdditiveAnimationInstance() : SkeletalMeshComponent(nullptr), AdditiveInstance(nullptr), AdditiveAnimationSequence(nullptr), BaseComponent(nullptr), bLoopingState(false) +{ +} + +void FAdditiveAnimationInstance::Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBPClass) +{ + if (InSkeletalMeshComponent && InAnimationBPClass) + { + SkeletalMeshComponent = InSkeletalMeshComponent; + SkeletalMeshComponent->SetAnimInstanceClass(InAnimationBPClass); + SkeletalMeshComponent->SetComponentTickEnabled(false); + SkeletalMeshComponent->SetForcedLOD(1); + SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; + AdditiveInstance = Cast(SkeletalMeshComponent->GetAnimInstance()); + } +} + +void FAdditiveAnimationInstance::Setup(USkeletalMeshComponent* InBaseComponent, UAnimSequence* InAnimSequence) +{ + UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 1); + SkeletalMeshComponent->SetComponentTickEnabled(true); + if (AdditiveInstance) + { + AdditiveInstance->BaseComponent = BaseComponent = InBaseComponent; + AdditiveInstance->AdditiveAnimation = AdditiveAnimationSequence = InAnimSequence; + AdditiveInstance->Alpha = 1.0f; + AdditiveInstance->bStateBool = bLoopingState = true; + + SkeletalMeshComponent->AddTickPrerequisiteComponent(BaseComponent); + } +} + +void FAdditiveAnimationInstance::UpdateBaseComponent(USkeletalMeshComponent* InBaseComponent) +{ + if (AdditiveInstance) + { + SkeletalMeshComponent->RemoveTickPrerequisiteComponent(BaseComponent); + AdditiveInstance->BaseComponent = BaseComponent = InBaseComponent; + SkeletalMeshComponent->AddTickPrerequisiteComponent(BaseComponent); + } +} + +void FAdditiveAnimationInstance::Stop() +{ + if (AdditiveInstance) + { + UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 0); + SkeletalMeshComponent->SetComponentTickEnabled(false); + SkeletalMeshComponent->RemoveTickPrerequisiteComponent(BaseComponent); + } +} + +void FAdditiveAnimationInstance::Start() +{ + if (AdditiveInstance) + { + AdditiveInstance->bStateBool = bLoopingState = false; + } +} + +USkeletalMeshComponent* FAdditiveAnimationInstance::GetBaseComponent() const +{ + return BaseComponent; +} + +USkeletalMeshComponent* FAdditiveAnimationInstance::GetComponent() const +{ + return SkeletalMeshComponent; +} + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp new file mode 100644 index 000000000000..40e8821ca297 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp @@ -0,0 +1,47 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingInstances.h" +#include "AnimationSharingManager.h" +#include "Algo/Transform.h" +#include "Stats/Stats.h" + +UAnimSharingStateInstance::UAnimSharingStateInstance(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer), AnimationToPlay(nullptr), PermutationTimeOffset(0.f), PlayRate(1.f), StateIndex(INDEX_NONE), ComponentIndex(INDEX_NONE), Instance(nullptr) +{ + +} + +void UAnimSharingStateInstance::GetInstancedActors(TArray& Actors) +{ + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetInstancedActors); + if (Instance && Instance->PerStateData.IsValidIndex(StateIndex)) + { + FPerStateData& StateData = Instance->PerStateData[StateIndex]; + if (StateData.Components.IsValidIndex(ComponentIndex)) + { + USkeletalMeshComponent* Component = StateData.Components[ComponentIndex]; + const TArray>& SlaveComponents = Component->GetSlavePoseComponents(); + + Algo::TransformIf(SlaveComponents, Actors, + [&Actors](const TWeakObjectPtr& WeakPtr) + { + // Needs to be valid and unique + return WeakPtr.IsValid() && !Actors.Contains(WeakPtr->GetOwner()); + }, + [](const TWeakObjectPtr& WeakPtr) + { + return WeakPtr->GetOwner(); + }); + } + } +} + +UAnimSharingTransitionInstance::UAnimSharingTransitionInstance(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer), FromComponent(nullptr), ToComponent(nullptr), BlendTime(.5f), bBlendBool(false) +{ +} + +UAnimSharingAdditiveInstance::UAnimSharingAdditiveInstance(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp new file mode 100644 index 000000000000..e0e4dab9fd06 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp @@ -0,0 +1,2108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingManager.h" +#include "AnimationSharingModule.h" +#include "Stats/Stats.h" +#include "Components/SkinnedMeshComponent.h" +#include "TransitionBlendInstance.h" +#include "Animation/AnimationAsset.h" +#include "Animation/AnimSequence.h" +#include "SignificanceManager.h" +#include "AnimationSharingSetup.h" +#include "AdditiveAnimationInstance.h" +#include "AnimationSharingInstances.h" + +#include "Misc/CoreMisc.h" +#include "DrawDebugHelpers.h" +#include "Math/NumericLimits.h" +#include "Logging/LogMacros.h" +#include "Engine/Engine.h" +#include "ProfilingDebugging/CsvProfiler.h" +#include "Misc/DefaultValueHelper.h" + +#if WITH_EDITOR +#include "PlatformInfo.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#endif // WITH_EDITOR + +DEFINE_LOG_CATEGORY(LogAnimationSharing); + +DECLARE_CYCLE_STAT(TEXT("Tick"), STAT_AnimationSharing_Tick, STATGROUP_AnimationSharing); +DECLARE_CYCLE_STAT(TEXT("UpdateBlends"), STAT_AnimationSharing_UpdateBlends, STATGROUP_AnimationSharing); +DECLARE_CYCLE_STAT(TEXT("UpdateOnDemands"), STAT_AnimationSharing_UpdateOnDemands, STATGROUP_AnimationSharing); +DECLARE_CYCLE_STAT(TEXT("UpdateAdditives"), STAT_AnimationSharing_UpdateAdditives, STATGROUP_AnimationSharing); +DECLARE_CYCLE_STAT(TEXT("TickActorStates"), STAT_AnimationSharing_TickActorStates, STATGROUP_AnimationSharing); +DECLARE_CYCLE_STAT(TEXT("KickoffInstances"), STAT_AnimationSharing_KickoffInstances, STATGROUP_AnimationSharing); + +DECLARE_DWORD_COUNTER_STAT(TEXT("NumBlends"), STAT_AnimationSharing_NumBlends, STATGROUP_AnimationSharing); +DECLARE_DWORD_COUNTER_STAT(TEXT("NumOnDemands"), STAT_AnimationSharing_NumOnDemands, STATGROUP_AnimationSharing); +DECLARE_DWORD_COUNTER_STAT(TEXT("NumActors"), STAT_AnimationSharing_NumActors, STATGROUP_AnimationSharing); +DECLARE_DWORD_COUNTER_STAT(TEXT("NumComponent"), STAT_AnimationSharing_NumComponent, STATGROUP_AnimationSharing); + +static int32 GAnimationSharingDebugging = 0; +static FAutoConsoleVariableRef CVarAnimSharing_DebugStates( + TEXT("a.Sharing.DebugStates"), + GAnimationSharingDebugging, + TEXT("Values: 0/1/2/3\n") + TEXT("Controls whether and which animation sharing debug features are enabled.\n") + TEXT("0: Turned off.\n") + TEXT("1: Turns on active master-components and blend with material coloring, and printing state information for each actor above their capsule.\n") + TEXT("2: Turns printing state information about currently active animation states, blend etc. Also enables line drawing from slave-components to currently assigned master components."), + ECVF_Cheat); + +static int32 GAnimationSharingEnabled = 1; +static FAutoConsoleCommandWithWorldAndArgs CVarAnimSharing_Enabled( + TEXT("a.Sharing.Enabled"), + TEXT("Arguments: 0/1\n") + TEXT("Controls whether the animation sharing is enabled."), + FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World) + { + if (Args.Num() != 0) + { + const bool bShouldBeEnabled = Args[0].ToBool(); + if (!bShouldBeEnabled && GAnimationSharingEnabled && World) + { + /** Need to unregister actors here*/ + UAnimationSharingManager* Manager = FAnimSharingModule::Get(World); + if (Manager) + { + Manager->ClearActorData(); + } + } + + GAnimationSharingEnabled = bShouldBeEnabled; + UE_LOG(LogAnimationSharing, Log, TEXT("Animation Sharing System - %s"), GAnimationSharingEnabled ? TEXT("Enabled") : TEXT("Disabled")); + } + }), + ECVF_Cheat); + +#if !UE_BUILD_SHIPPING +static int32 GMasterComponentsVisible = 0; +static FAutoConsoleCommandWithWorldAndArgs CVarAnimSharing_ToggleVisibility( + TEXT("a.Sharing.ToggleVisibility"), + TEXT("Toggles the visibility of the Master Pose Components."), + FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World) + { + const bool bShouldBeVisible = !GMasterComponentsVisible; + + /** Need to unregister actors here*/ + UAnimationSharingManager* Manager = FAnimSharingModule::Get(World); + if (Manager) + { + Manager->SetMasterComponentsVisibility(bShouldBeVisible); + } + + GMasterComponentsVisible = bShouldBeVisible; + }), + ECVF_Cheat); + +#else +static const int32 GMasterComponentsVisible = 0; +#endif + +#if WITH_EDITOR +static TAutoConsoleVariable CVarAnimSharing_PreviewScalabilityPlatform( + TEXT("a.Sharing.ScalabilityPlatform"), + "", + TEXT("Controls which platform should be used when retrieving per platform scalability settings.\n") + TEXT("Empty: Current platform.\n") + TEXT("Name of Platform\n") + TEXT("Name of Platform Group\n"), + ECVF_Cheat); +#endif + +#define LOG_STATES 0 +#define CSV_STATS 0 +#define DETAIL_STATS 0 + +#if DEBUG_MATERIALS +TArray UAnimationSharingManager::DebugMaterials; +#endif + +void UAnimationSharingManager::BeginDestroy() +{ + Super::BeginDestroy(); + PerSkeletonData.Empty(); + + // Unregister tick function + TickFunction.UnRegisterTickFunction(); + TickFunction.Manager = nullptr; +} + +UWorld* UAnimationSharingManager::GetWorld() const +{ + return Cast(GetOuter()); +} + +UAnimationSharingManager* UAnimationSharingManager::GetAnimationSharingManager(UObject* WorldContextObject) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + return GetManagerForWorld(World); + } + + return nullptr; +} + +UAnimationSharingManager* UAnimationSharingManager::GetManagerForWorld(UWorld* InWorld) +{ + return FAnimSharingModule::Get(InWorld); +} + +void FTickAnimationSharingFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) +{ + if (ensure(Manager)) + { + Manager->Tick(DeltaTime); + } +} + +FString FTickAnimationSharingFunction::DiagnosticMessage() +{ + return TEXT("FTickAnimationSharingFunction"); +} + +FName FTickAnimationSharingFunction::DiagnosticContext(bool bDetailed) +{ + return FName(TEXT("TickAnimationSharing")); +} + + +FTickAnimationSharingFunction& UAnimationSharingManager::GetTickFunction() +{ + return TickFunction; +} + +void UAnimationSharingManager::Initialise(const UAnimationSharingSetup* InSetup) +{ + if (InSetup) + { + TickFunction.Manager = this; + TickFunction.RegisterTickFunction(GetWorld()->PersistentLevel); + + ScalabilitySettings = InSetup->ScalabilitySettings; + +#if WITH_EDITOR + // Update local copy defaults with current platform value + const FName PlatformName = UAnimationSharingManager::GetPlatformName(); + ScalabilitySettings.UseBlendTransitions = ScalabilitySettings.UseBlendTransitions.GetValueForPlatformIdentifiers(PlatformName, PlatformName); + ScalabilitySettings.BlendSignificanceValue = ScalabilitySettings.BlendSignificanceValue.GetValueForPlatformIdentifiers(PlatformName, PlatformName); + ScalabilitySettings.MaximumNumberConcurrentBlends = ScalabilitySettings.MaximumNumberConcurrentBlends.GetValueForPlatformIdentifiers(PlatformName, PlatformName); + ScalabilitySettings.TickSignificanceValue = ScalabilitySettings.TickSignificanceValue.GetValueForPlatformIdentifiers(PlatformName, PlatformName); +#endif + + // Debug materials +#if DEBUG_MATERIALS + DebugMaterials.Empty(); + { + UMaterialInterface* RedMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingRed.AnimSharingRed")); + DebugMaterials.Add(RedMaterial); + UMaterialInterface* GreenMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingGreen.AnimSharingGreen")); + DebugMaterials.Add(GreenMaterial); + UMaterialInterface* BlueMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingBlue.AnimSharingBlue")); + DebugMaterials.Add(BlueMaterial); + } +#endif + UWorld* World = GetWorld(); + + for (const FPerSkeletonAnimationSharingSetup& SkeletonSetup : InSetup->SkeletonSetups) + { + SetupPerSkeletonData(SkeletonSetup); + } + } +} + +const FAnimationSharingScalability& UAnimationSharingManager::GetScalabilitySettings() const +{ + return ScalabilitySettings; +} + +void UAnimationSharingManager::SetupPerSkeletonData(const FPerSkeletonAnimationSharingSetup& SkeletonSetup) +{ + const USkeleton* Skeleton = SkeletonSetup.Skeleton.LoadSynchronous(); + UEnum* StateEnum = SkeletonSetup.StateProcessorClass->GetDefaultObject()->GetAnimationStateEnum(); + if (Skeleton && StateEnum) + { + UAnimSharingInstance* Data = NewObject(this); + PerSkeletonData.Add(Data); + Skeletons.Add(Skeleton); + Data->Setup(this, SkeletonSetup, &ScalabilitySettings, Skeletons.Num() - 1); + } +} + +uint32 UAnimationSharingManager::CreateActorHandle(uint8 SkeletonIndex, uint32 ActorIndex) const +{ + ensureMsgf(ActorIndex <= 0xFFFFFF, TEXT("Invalid Actor Handle due to overflow")); + return (SkeletonIndex << 24) | ActorIndex; +} + +uint8 UAnimationSharingManager::GetSkeletonIndexFromHandle(uint32 InHandle) const +{ + return (InHandle & 0xFF000000) >> 24; +} + +uint32 UAnimationSharingManager::GetActorIndexFromHandle(uint32 InHandle) const +{ + return (InHandle & 0x00FFFFFF); +} + +void UAnimationSharingManager::Tick(float DeltaTime) +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_Tick); + + const float WorldTime = GetWorld()->GetTimeSeconds(); + + /** Keeping track of currently running instances / animations for debugging purposes */ + int32 TotalNumBlends = 0; + int32 TotalNumOnDemands = 0; + int32 TotalNumComponents = 0; + int32 TotalNumActors = 0; + + int32 TotalNumRunningStates = 0; + int32 TotalNumRunningComponents = 0; + + /** Iterator over all Skeleton setups */ + for (int32 Index = 0; Index < PerSkeletonData.Num(); ++Index) + { + UAnimSharingInstance* Instance = PerSkeletonData[Index]; + Instance->WorldTime = WorldTime; + + /** Tick both Blend and On-Demand instances first, as they could be finishing */ + Instance->TickBlendInstances(); + Instance->TickOnDemandInstances(); + Instance->TickAdditiveInstances(); + + /** Tick actor states */ + Instance->TickActorStates(); + + /** Setup and start any blending transitions created while ticking the actor states */ + Instance->KickoffInstances(); + +#if !UE_BUILD_SHIPPING + if (GAnimationSharingDebugging >= 1) + { + Instance->TickDebugInformation(); + } +#endif + /** Tick the animation states to determine which components should be turned on/off */ + Instance->TickAnimationStates(); + +#if DETAIL_STATS + /** Stat counters */ + TotalNumOnDemands += Instance->OnDemandInstances.Num(); + TotalNumBlends += Instance->BlendInstances.Num(); + TotalNumActors += Instance->PerActorData.Num(); + TotalNumComponents += Instance->PerComponentData.Num(); + + for (FPerStateData& StateData : Instance->PerStateData) + { + if (StateData.InUseComponentFrameBits.Contains(true)) + { + ++TotalNumRunningStates; + } + + for (int32 ComponentIndex = 0; ComponentIndex < StateData.PreviousInUseComponentFrameBits.Num(); ++ComponentIndex) + { + if (StateData.PreviousInUseComponentFrameBits[ComponentIndex] == true) + { + ++TotalNumRunningComponents; + } + } + } +#endif // DETAIL_STATS + } + +#if DETAIL_STATS + SET_DWORD_STAT(STAT_AnimationSharing_NumOnDemands, TotalNumOnDemands); + SET_DWORD_STAT(STAT_AnimationSharing_NumBlends, TotalNumBlends); + + SET_DWORD_STAT(STAT_AnimationSharing_NumActors, TotalNumActors); + SET_DWORD_STAT(STAT_AnimationSharing_NumComponent, TotalNumComponents); + + SET_DWORD_STAT(STAT_AnimationSharing_NumBlends, TotalNumBlends); +#endif // DETAIL_STATS + +#if CSV_STATS + CSV_CUSTOM_STAT_GLOBAL(NumOnDemands, TotalNumOnDemands, ECsvCustomStatOp::Set); + CSV_CUSTOM_STAT_GLOBAL(NumBlends, TotalNumBlends, ECsvCustomStatOp::Set); + CSV_CUSTOM_STAT_GLOBAL(NumRunningStates, TotalNumRunningStates, ECsvCustomStatOp::Set); + CSV_CUSTOM_STAT_GLOBAL(NumRunningComponents, TotalNumRunningComponents, ECsvCustomStatOp::Set); +#endif + +} + +void UAnimationSharingManager::RegisterActor(AActor* InActor, FUpdateActorHandle CallbackDelegate) +{ + if (!UAnimationSharingManager::AnimationSharingEnabled()) + { + return; + } + + if (InActor) + { + TArray> OwnedComponents; + InActor->GetComponents(OwnedComponents); + checkf(OwnedComponents.Num(), TEXT("No SkeletalMeshComponents found in actor!")); + + const USkeleton* UsedSkeleton = [&]() + { + USkeleton* CurrentSkeleton = nullptr; + for (USkeletalMeshComponent* SkeletalMeshComponent : OwnedComponents) + { + const USkeletalMesh* Mesh = SkeletalMeshComponent->SkeletalMesh; + USkeleton* Skeleton = Mesh->Skeleton; + + if (CurrentSkeleton == nullptr) + { + CurrentSkeleton = Skeleton; + } + else if (CurrentSkeleton != Skeleton) + { + if (!CurrentSkeleton->IsCompatibleMesh(Mesh)) + { + checkf(false, TEXT("Multiple different skeletons within same actor")); + } + } + } + + return CurrentSkeleton; + }(); + + RegisterActorWithSkeleton(InActor, UsedSkeleton, CallbackDelegate); + } +} + +void UAnimationSharingManager::RegisterActorWithSkeleton(AActor* InActor, const USkeleton* SharingSkeleton, FUpdateActorHandle CallbackDelegate) +{ + if (!UAnimationSharingManager::AnimationSharingEnabled()) + { + return; + } + + const AnimationSharingDataHandle Handle = [&]() -> uint32 + { + uint32 ArrayIndex = Skeletons.IndexOfByPredicate([&](const USkeleton* Skeleton) + { + return (Skeleton == SharingSkeleton) || (Skeleton->IsCompatible(SharingSkeleton)); + }); + ensureMsgf(ArrayIndex != INDEX_NONE, TEXT("Invalid skeleton for which there is no sharing setup available!")); + return ArrayIndex; + }(); + + if (Handle != INDEX_NONE) + { + TArray> OwnedComponents; + InActor->GetComponents(OwnedComponents); + checkf(OwnedComponents.Num(), TEXT("No SkeletalMeshComponents found in actor!")); + + UAnimSharingInstance* Data = PerSkeletonData[Handle]; + + // Register the actor + const int32 ActorIndex = Data->RegisteredActors.Add(InActor); + + FPerActorData& ActorData = Data->PerActorData.AddZeroed_GetRef(); + ActorData.BlendInstanceIndex = ActorData.OnDemandInstanceIndex = ActorData.AdditiveInstanceIndex = INDEX_NONE; + ActorData.SignificanceValue = Data->SignificanceManager->GetSignificance(InActor); + ActorData.UpdateActorHandleDelegate = CallbackDelegate; + + bool bShouldProcess = true; + ActorData.CurrentState = ActorData.PreviousState = Data->DetermineStateForActor(ActorIndex, bShouldProcess); + + for (USkeletalMeshComponent* Component : OwnedComponents) + { + FPerComponentData& ComponentData = Data->PerComponentData.AddZeroed_GetRef(); + ComponentData.ActorIndex = ActorIndex; + ComponentData.Component = Component; + + Component->PrimaryComponentTick.bCanEverTick = false; + Component->SetComponentTickEnabled(false); + Component->bIgnoreMasterPoseComponentLOD = true; + + ActorData.ComponentIndices.Add(Data->PerComponentData.Num() - 1); + + const int32 ComponentIndex = Data->PerComponentData.Num() - 1; + Data->SetupSlaveComponent(ActorData.CurrentState, ActorIndex); + } + + if (Data->PerStateData[ActorData.CurrentState].bIsOnDemand && ActorData.OnDemandInstanceIndex != INDEX_NONE) + { + // We will have setup an on-demand instance so we need to kick it off here before we next tick + Data->OnDemandInstances[ActorData.OnDemandInstanceIndex].bActive = true; + Data->OnDemandInstances[ActorData.OnDemandInstanceIndex].StartTime = Data->WorldTime; + } + + const int32 ActorHandle = CreateActorHandle(Handle, ActorIndex); + ActorData.UpdateActorHandleDelegate.ExecuteIfBound(ActorHandle); + } +} + +void UAnimationSharingManager::RegisterActorWithSkeletonBP(AActor* InActor, const USkeleton* SharingSkeleton) +{ + RegisterActorWithSkeleton(InActor, SharingSkeleton, FUpdateActorHandle::CreateLambda([](int32 A) {})); +} + +void UAnimationSharingManager::UnregisterActor(AActor* InActor) +{ + if (!UAnimationSharingManager::AnimationSharingEnabled()) + { + return; + } + + for (int32 SkeletonIndex = 0; SkeletonIndex < PerSkeletonData.Num(); ++SkeletonIndex) + { + UAnimSharingInstance* SkeletonData = PerSkeletonData[SkeletonIndex]; + const int32 ActorIndex = SkeletonData->RegisteredActors.IndexOfByKey(InActor); + + if (ActorIndex != INDEX_NONE ) + { + const FPerActorData& ActorData = SkeletonData->PerActorData[ActorIndex]; + + const bool bNeedsSwap = SkeletonData->PerActorData.Num() > 1 && ActorIndex != SkeletonData->PerActorData.Num() - 1; + + for (int32 ComponentIndex : ActorData.ComponentIndices) + { + SkeletonData->PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true); + SkeletonData->PerComponentData[ComponentIndex].Component->SetComponentTickEnabled(true); + SkeletonData->RemoveComponent(ComponentIndex); + } + + const int32 SwapIndex = SkeletonData->PerActorData.Num() - 1; + + // Remove actor index from any blend instances + for (FBlendInstance& Instance : SkeletonData->BlendInstances) + { + Instance.ActorIndices.Remove(ActorIndex); + + // If we are swapping and the actor we are swapping with is part of the instance make sure we update the actor index + const uint32 SwapActorIndex = bNeedsSwap ? Instance.ActorIndices.IndexOfByKey(SwapIndex) : INDEX_NONE; + if (SwapActorIndex != INDEX_NONE) + { + Instance.ActorIndices[SwapActorIndex] = ActorIndex; + } + } + + // Remove actor index from any running on demand instances + for (FOnDemandInstance& Instance : SkeletonData->OnDemandInstances) + { + Instance.ActorIndices.Remove(ActorIndex); + + // If we are swapping and the actor we are swapping with is part of the instance make sure we update the actor index + const uint32 SwapActorIndex = bNeedsSwap ? Instance.ActorIndices.IndexOfByKey(SwapIndex) : INDEX_NONE; + if (SwapActorIndex != INDEX_NONE) + { + Instance.ActorIndices[SwapActorIndex] = ActorIndex; + } + } + + // Remove actor index from any additive instances + for (FAdditiveInstance& Instance : SkeletonData->AdditiveInstances) + { + if (Instance.ActorIndex == ActorIndex) + { + Instance.ActorIndex = INDEX_NONE; + } + else if (bNeedsSwap && Instance.ActorIndex == SwapIndex) + { + Instance.ActorIndex = ActorIndex; + } + } + + if (bNeedsSwap) + { + // Swap actor index for all components which are part of the actor we are swapping with + for (uint32 ComponentIndex : SkeletonData->PerActorData[SwapIndex].ComponentIndices) + { + SkeletonData->PerComponentData[ComponentIndex].ActorIndex = ActorIndex; + } + + // Make sure we update the handle on the swapped actor + SkeletonData->PerActorData[SwapIndex].UpdateActorHandleDelegate.ExecuteIfBound(CreateActorHandle(SkeletonIndex, ActorIndex)); + } + + SkeletonData->PerActorData.RemoveAtSwap(ActorIndex, 1, false); + SkeletonData->RegisteredActors.RemoveAtSwap(ActorIndex, 1, false); + } + } +} + +void UAnimationSharingManager::UpdateSignificanceForActorHandle(uint32 InHandle, float InValue) +{ + // Retrieve actor + if (FPerActorData* ActorData = GetActorDataByHandle(InHandle)) + { + ActorData->SignificanceValue = InValue; + } +} + +FPerActorData* UAnimationSharingManager::GetActorDataByHandle(uint32 InHandle) +{ + FPerActorData* ActorDataPtr = nullptr; + uint8 SkeletonIndex = GetSkeletonIndexFromHandle(InHandle); + uint32 ActorIndex = GetActorIndexFromHandle(InHandle); + if (PerSkeletonData.IsValidIndex(SkeletonIndex)) + { + if (PerSkeletonData[SkeletonIndex]->PerActorData.IsValidIndex(ActorIndex)) + { + ActorDataPtr = &PerSkeletonData[SkeletonIndex]->PerActorData[ActorIndex]; + } + } + + return ActorDataPtr; +} + +void UAnimationSharingManager::ClearActorData() +{ + UnregisterAllActors(); + + for (UAnimSharingInstance* Data : PerSkeletonData) + { + Data->BlendInstances.Empty(); + Data->OnDemandInstances.Empty(); + } +} + +void UAnimationSharingManager::UnregisterAllActors() +{ + for (UAnimSharingInstance* Data : PerSkeletonData) + { + for (int32 ActorIndex = 0; ActorIndex < Data->RegisteredActors.Num(); ++ActorIndex) + { + AActor* RegisteredActor = Data->RegisteredActors[ActorIndex]; + if (RegisteredActor) + { + FPerActorData& ActorData = Data->PerActorData[ActorIndex]; + for (int32 ComponentIndex : ActorData.ComponentIndices) + { + Data->PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true); + Data->PerComponentData[ComponentIndex].Component->PrimaryComponentTick.bCanEverTick = true; + Data->PerComponentData[ComponentIndex].Component->SetComponentTickEnabled(true); + Data->PerComponentData[ComponentIndex].Component->bRecentlyRendered = false; + } + ActorData.ComponentIndices.Empty(); + } + } + + Data->PerActorData.Empty(); + Data->PerComponentData.Empty(); + Data->RegisteredActors.Empty(); + } +} + +void UAnimationSharingManager::SetMasterComponentsVisibility(bool bVisible) +{ + for (UAnimSharingInstance* Data : PerSkeletonData) + { + for (FPerStateData& StateData : Data->PerStateData) + { + for (USkeletalMeshComponent* Component : StateData.Components) + { + Component->SetVisibility(bVisible); + } + } + + for (FTransitionBlendInstance* Instance : Data->BlendInstanceStack.AvailableInstances) + { + Instance->GetComponent()->SetVisibility(bVisible); + } + + for (FTransitionBlendInstance* Instance : Data->BlendInstanceStack.InUseInstances) + { + Instance->GetComponent()->SetVisibility(bVisible); + } + + for (FAdditiveAnimationInstance* Instance : Data->AdditiveInstanceStack.AvailableInstances) + { + Instance->GetComponent()->SetVisibility(bVisible); + } + + for (FAdditiveAnimationInstance* Instance : Data->AdditiveInstanceStack.InUseInstances) + { + Instance->GetComponent()->SetVisibility(bVisible); + } + } +} + +bool UAnimationSharingManager::AnimationSharingEnabled() +{ + return GAnimationSharingEnabled == 1; +} + +bool UAnimationSharingManager::CreateAnimationSharingManager(UObject* WorldContextObject, const UAnimationSharingSetup* Setup) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + return FAnimSharingModule::CreateAnimationSharingManager(World, Setup); + } + + return false; +} + +void UAnimationSharingManager::SetDebugMaterial(USkeletalMeshComponent* Component, uint8 State) +{ +#if DEBUG_MATERIALS + if (GAnimationSharingDebugging >= 1 && DebugMaterials.IsValidIndex(State)) + { + const int32 NumMaterials = Component->GetNumMaterials(); + for (int32 Index = 0; Index < NumMaterials; ++Index) + { + Component->SetMaterial(Index, DebugMaterials[State]); + } + } +#endif +} + +void UAnimationSharingManager::SetDebugMaterialForActor(UAnimSharingInstance* Data, uint32 ActorIndex, uint8 State) +{ +#if DEBUG_MATERIALS + for (uint32 ComponentIndex : Data->PerActorData[ActorIndex].ComponentIndices) + { + SetDebugMaterial(Data->PerComponentData[ComponentIndex].Component, State); + } +#endif +} + +#if WITH_EDITOR +FName UAnimationSharingManager::GetPlatformName() +{ + const FString PlatformString = CVarAnimSharing_PreviewScalabilityPlatform.GetValueOnAnyThread(); + if (PlatformString.IsEmpty()) + { + ITargetPlatform* CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + return CurrentPlatform->GetPlatformInfo().PlatformGroupName; + } + + FName PlatformNameFromString(*PlatformString); + return PlatformNameFromString; +} +#endif + +void UAnimSharingInstance::BeginDestroy() +{ + Super::BeginDestroy(); + for (FPerActorData& ActorData : PerActorData) + { + for (uint32 ComponentIndex : ActorData.ComponentIndices) + { + PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true); + } + } + + RegisteredActors.Empty(); + PerActorData.Empty(); + PerComponentData.Empty(); + PerStateData.Empty(); + StateProcessor = nullptr; + StateEnum = nullptr; + BlendInstances.Empty(); + OnDemandInstances.Empty(); +} + +uint8 UAnimSharingInstance::DetermineStateForActor(uint32 ActorIndex, bool& bShouldProcess) +{ + const FPerActorData& ActorData = PerActorData[ActorIndex]; + int32 State = 0; + if (bNativeStateProcessor) + { + StateProcessor->ProcessActorState_Implementation(State, RegisteredActors[ActorIndex], ActorData.CurrentState, ActorData.OnDemandInstanceIndex != INDEX_NONE ? OnDemandInstances[ActorData.OnDemandInstanceIndex].State : INDEX_NONE, bShouldProcess); + } + else + { + StateProcessor->ProcessActorState(State, RegisteredActors[ActorIndex], ActorData.CurrentState, ActorData.OnDemandInstanceIndex != INDEX_NONE ? OnDemandInstances[ActorData.OnDemandInstanceIndex].State : INDEX_NONE, bShouldProcess); + } + + return FMath::Max(0, State); +} + +void UAnimSharingInstance::Setup(UAnimationSharingManager* AnimationSharingManager, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, const FAnimationSharingScalability* InScalabilitySettings, uint32 Index) +{ + USkeletalMesh* SkeletalMesh = SkeletonSetup.SkeletalMesh.LoadSynchronous(); + /** Retrieve the state processor to use */ + if (UAnimationSharingStateProcessor* Processor = SkeletonSetup.StateProcessorClass.GetDefaultObject()) + { + StateProcessor = Processor; + bNativeStateProcessor = SkeletonSetup.StateProcessorClass->HasAnyClassFlags(CLASS_Native); + } + + if (SkeletalMesh && StateProcessor) + { + SkeletalMeshBounds = SkeletalMesh->GetBounds().BoxExtent * 2; + ScalabilitySettings = InScalabilitySettings; + StateEnum = StateProcessor->GetAnimationStateEnum(); + const uint32 NumStates = StateEnum->NumEnums(); + PerStateData.AddDefaulted(NumStates); + + UWorld* World = GetWorld(); + SharingActor = World->SpawnActor(); + // Make sure the actor stays around when scrubbing through replays, states will be updated correctly in next tick + SharingActor->bReplayRewindable = true; + SignificanceManager = USignificanceManager::Get(World); + AnimSharingManager = AnimationSharingManager; + + /** Create runtime data structures for unique animation states */ + NumSetups = 0; + for (const FAnimationStateEntry& StateEntry : SkeletonSetup.AnimationStates) + { + const uint8 StateValue = StateEntry.State; + const uint32 StateIndex = StateEnum->GetIndexByValue(StateValue); + + checkf(!PerStateData.FindByPredicate([&](const FPerStateData& State) { return State.StateEnumValue == StateValue; }), TEXT("State already defined")); + + FPerStateData& StateData = PerStateData[StateIndex]; + StateData.StateEnumValue = StateValue; + SetupState(StateData, StateEntry, SkeletalMesh, SkeletonSetup, Index); + } + + /** Setup blend actors, if enabled*/ + if (ScalabilitySettings->UseBlendTransitions.Default) + { + const uint32 TotalNumberOfBlendActorsRequired = ScalabilitySettings->MaximumNumberConcurrentBlends.Default; + const float ZOffset = Index * SkeletalMeshBounds.Z * 2.f; + for (uint32 BlendIndex = 0; BlendIndex < TotalNumberOfBlendActorsRequired; ++BlendIndex) + { + const FVector SpawnLocation(BlendIndex * SkeletalMeshBounds.X, 0.f, ZOffset + SkeletalMeshBounds.Z); + const FName BlendComponentName(*(SkeletalMesh->GetName() + TEXT("_BlendComponent") + FString::FromInt(BlendIndex))); + USkeletalMeshComponent* BlendComponent = NewObject(SharingActor, BlendComponentName); + BlendComponent->RegisterComponent(); + BlendComponent->SetRelativeLocation(SpawnLocation); + BlendComponent->SetSkeletalMesh(SkeletalMesh); + BlendComponent->SetVisibility(GMasterComponentsVisible == 1); + + BlendComponent->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction()); + + FTransitionBlendInstance* BlendActor = new FTransitionBlendInstance(); + BlendActor->Initialise(BlendComponent, SkeletonSetup.BlendAnimBlueprint.Get()); + BlendInstanceStack.AddInstance(BlendActor); + } + } + } + else + { + UE_LOG(LogAnimationSharing, Error, TEXT("Invalid Skeletal Mesh or State Processing Class")); + } +} + +void UAnimSharingInstance::SetupState(FPerStateData& StateData, const FAnimationStateEntry& StateEntry, USkeletalMesh* SkeletalMesh, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, uint32 Index) +{ + /** Used for placing components into rows / columns at origin for debugging purposes */ + const float ZOffset = Index * SkeletalMeshBounds.Z * 2.f; + + /** Setup overall data and flags */ + StateData.bIsOnDemand = StateEntry.bOnDemand; + StateData.bIsAdditive = StateEntry.bAdditive; + StateData.AdditiveAnimationSequence = (StateEntry.bAdditive && StateEntry.AnimationSetups.IsValidIndex(0)) ? StateEntry.AnimationSetups[0].AnimSequence.LoadSynchronous() : nullptr; + + /** Keep hard reference to animation sequence */ + if (StateData.AdditiveAnimationSequence) + { + UsedAnimationSequences.Add(StateData.AdditiveAnimationSequence); + } + + StateData.BlendTime = StateEntry.BlendTime; + StateData.bReturnToPreviousState = StateEntry.bReturnToPreviousState; + StateData.bShouldForwardToState = StateEntry.bSetNextState; + StateData.ForwardStateValue = StateEntry.NextState; + + int32 MaximumNumberOfConcurrentInstances = StateEntry.MaximumNumberOfConcurrentInstances.Default; +#if WITH_EDITOR + const FName PlatformName = UAnimationSharingManager::GetPlatformName(); + MaximumNumberOfConcurrentInstances = StateEntry.MaximumNumberOfConcurrentInstances.GetValueForPlatformIdentifiers(PlatformName, PlatformName); +#endif + + /** Ensure that we spread our number over the number of enabled setups */ + const int32 NumInstancesPerSetup = [MaximumNumberOfConcurrentInstances, &StateEntry]() + { + int32 TotalEnabled = 0; + for (const FAnimationSetup& AnimationSetup : StateEntry.AnimationSetups) + { + bool bEnabled = AnimationSetup.Enabled.Default; +#if WITH_EDITOR + const FName PlatformName = UAnimationSharingManager::GetPlatformName(); + bEnabled = AnimationSetup.Enabled.GetValueForPlatformIdentifiers(PlatformName, PlatformName); +#endif + TotalEnabled += bEnabled ? 1 : 0; + } + + return (TotalEnabled > 0) ? FMath::CeilToInt((float)MaximumNumberOfConcurrentInstances / (float)TotalEnabled) : 0; + }(); + + UWorld* World = GetWorld(); + /** Setup animations used for this state and the number of permutations */ + TArray& Components = StateData.Components; + for (int32 SetupIndex = 0; SetupIndex < StateEntry.AnimationSetups.Num(); ++SetupIndex) + { + const FAnimationSetup& AnimationSetup = StateEntry.AnimationSetups[SetupIndex]; + /** User can setup either an AnimBP or AnimationSequence */ + UClass* AnimBPClass = AnimationSetup.AnimBlueprint.Get(); + UAnimSequence* AnimSequence = AnimationSetup.AnimSequence.LoadSynchronous(); + ensureMsgf(AnimBPClass != nullptr || AnimSequence != nullptr, TEXT("Animation setup without either an Animation Blueprint Class of Animation Sequence")); + + bool bEnabled = AnimationSetup.Enabled.Default; +#if WITH_EDITOR + bEnabled = AnimationSetup.Enabled.GetValueForPlatformIdentifiers(PlatformName, PlatformName); +#endif + + /** Only create component if the setup is enabled for this platform and we have a valid animation asset */ + if (bEnabled && (AnimBPClass || AnimSequence)) + { + int32 NumRandomizedInstances = AnimationSetup.NumRandomizedInstances.Default; +#if WITH_EDITOR + NumRandomizedInstances = AnimationSetup.NumRandomizedInstances.GetValueForPlatformIdentifiers(PlatformName, PlatformName); +#endif + const uint32 NumInstances = StateEntry.bOnDemand ? NumInstancesPerSetup : NumRandomizedInstances; + for (uint32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) + { + if (!StateData.bIsAdditive) + { + const FName StateComponentName(*(SkeletalMesh->GetName() + TEXT("_") + StateEnum->GetNameStringByIndex(StateEntry.State) + FString::FromInt(SetupIndex) + FString::FromInt(InstanceIndex))); + USkeletalMeshComponent* Component = NewObject(SharingActor, StateComponentName); + Component->RegisterComponent(); + /** Arrange component in correct row / column */ + Component->SetRelativeLocation(FVector(NumSetups * SkeletalMeshBounds.X, 0.f, ZOffset)); + /** Set shared skeletal mesh */ + Component->SetSkeletalMesh(SkeletalMesh); + Component->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; + Component->SetForcedLOD(1); + Component->SetVisibility(GMasterComponentsVisible == 1); + Component->bPropagateCurvesToSlaves = StateEntry.bRequiresCurves; + + if (AnimBPClass != nullptr && AnimSequence != nullptr) + { + Component->SetAnimInstanceClass(AnimBPClass); + if (UAnimSharingStateInstance* AnimInstance = Cast(Component->GetAnimInstance())) + { + AnimInstance->AnimationToPlay = AnimSequence; + if (InstanceIndex > 0) + { + const float Steps = (AnimSequence->SequenceLength * 0.9f) / (NumInstances); + const float StartTimeOffset = Steps * InstanceIndex; + AnimInstance->PermutationTimeOffset = StartTimeOffset; + } + + AnimInstance->PlayRate = StateData.bIsOnDemand ? 0.f : 1.0f; + + AnimInstance->Instance = this; + AnimInstance->StateIndex = StateEntry.State; + AnimInstance->ComponentIndex = Components.Num(); + + /** Set the current animation length length */ + StateData.AnimationLengths.Add(AnimSequence->SequenceLength); + } + } + else if (AnimSequence != nullptr) + { + Component->PlayAnimation(AnimSequence, true); + + /** If this is an on-demand state we pause the animation as we'll want to start it from the beginning anytime we start an on-demand instance */ + if (StateData.bIsOnDemand) + { + Component->Stop(); + } + else + { + if (InstanceIndex > 0) + { + const float Steps = (AnimSequence->SequenceLength * 0.9f) / (NumInstances); + const float StartTimeOffset = Steps * InstanceIndex; + Component->SetPosition(StartTimeOffset, false); + } + } + + /** Set the current animation length length */ + StateData.AnimationLengths.Add(AnimSequence->SequenceLength); + } + + /** Set material to red to indicate that it's not in use*/ + UAnimationSharingManager::SetDebugMaterial(Component, 0); + + Component->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction()); + Components.Add(Component); + } + else + { + const FVector SpawnLocation(FVector(NumSetups * SkeletalMeshBounds.X, 0.f, ZOffset)); + const FName AdditiveComponentName(*(SkeletalMesh->GetName() + TEXT("_") + StateEnum->GetNameStringByIndex(StateEntry.State) + FString::FromInt(InstanceIndex))); + USkeletalMeshComponent* AdditiveComponent = NewObject(SharingActor, AdditiveComponentName); + AdditiveComponent->RegisterComponent(); + AdditiveComponent->SetRelativeLocation(SpawnLocation); + AdditiveComponent->SetSkeletalMesh(SkeletalMesh); + AdditiveComponent->SetVisibility(GMasterComponentsVisible == 1); + + AdditiveComponent->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction()); + + FAdditiveAnimationInstance* AdditiveInstance = new FAdditiveAnimationInstance(); + AdditiveInstance->Initialise(AdditiveComponent, SkeletonSetup.AdditiveAnimBlueprint.Get()); + AdditiveInstanceStack.AddInstance(AdditiveInstance); + } + + ++NumSetups; + } + } + } + + float TotalLength = 0.f; + for (float Length : StateData.AnimationLengths) + { + TotalLength += Length; + } + const float AverageLength = (StateData.AnimationLengths.Num() > 0) ? TotalLength / FMath::Min((float)StateData.AnimationLengths.Num(), 1.f) : 0.f; + StateData.WiggleTime = AverageLength * StateEntry.WiggleTimePercentage; + + /** Randomizes the order of Components so we actually hit different animations when running on demand */ + if (StateData.bIsOnDemand && !StateData.bIsAdditive && StateEntry.AnimationSetups.Num() > 1) + { + TArray RandomizedComponents; + while (Components.Num() > 0) + { + const int32 RandomIndex = FMath::RandRange(0, Components.Num() - 1); + RandomizedComponents.Add(Components[RandomIndex]); + Components.RemoveAt(RandomIndex, 1); + } + + Components = RandomizedComponents; + } + + /** Initialize component (previous frame) usage flags */ + StateData.InUseComponentFrameBits.Init(false, Components.Num()); + /** This should enforce turning off the components tick during the first frame */ + StateData.PreviousInUseComponentFrameBits.Init(true, Components.Num()); + + StateData.SlaveTickRequiredFrameBits.Init(false, Components.Num()); +} + +void UAnimSharingInstance::TickDebugInformation() +{ +#if !UE_BUILD_SHIPPING +#if UE_BUILD_DEVELOPMENT + if (GMasterComponentsVisible && GAnimationSharingDebugging >= 2) + { + for (const FPerStateData& StateData : PerStateData) + { + for (int32 Index = 0; Index < StateData.InUseComponentFrameBits.Num(); ++Index) + { + const FString ComponentString = FString::Printf(TEXT("In Use %s - Required %s"), StateData.InUseComponentFrameBits[Index] ? TEXT("True") : TEXT("False"), StateData.SlaveTickRequiredFrameBits[Index] ? TEXT("True") : TEXT("False")); + DrawDebugString(GetWorld(), StateData.Components[Index]->GetComponentLocation() + FVector(0,0,StateData.Components[Index]->Bounds.BoxExtent.Z), ComponentString, nullptr, FColor::White, 0.016f, false); + } + } + } +#endif // UE_BUILD_DEVELOPMENT + + + for (int32 ActorIndex = 0; ActorIndex < RegisteredActors.Num(); ++ActorIndex) + { + // Non-const for DrawDebugString + AActor* Actor = RegisteredActors[ActorIndex]; + if (Actor) + { + const FPerActorData& ActorData = PerActorData[ActorIndex]; + const uint8 State = ActorData.CurrentState; + + const FString StateString = [&]() -> FString + { + /** Check whether or not we are currently blending */ + const uint32 BlendInstanceIndex = ActorData.BlendInstanceIndex; + if (BlendInstanceIndex != INDEX_NONE && BlendInstances.IsValidIndex(BlendInstanceIndex)) + { + const float TimeLeft = BlendInstances[BlendInstanceIndex].BlendTime - (GetWorld()->GetTimeSeconds() - BlendInstances[BlendInstanceIndex].EndTime); + return FString::Printf(TEXT("Blending states - %s to %s [%1.3f] (%i)"), *StateEnum->GetDisplayNameTextByValue(BlendInstances[BlendInstanceIndex].StateFrom).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendInstances[BlendInstanceIndex].StateTo).ToString(), TimeLeft, ActorData.BlendInstanceIndex); + } + + /** Check if we are part of an on-demand instance */ + const uint32 DemandInstanceIndex = ActorData.OnDemandInstanceIndex; + if (DemandInstanceIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(DemandInstanceIndex)) + { + return FString::Printf(TEXT("On demand state - %s [%i]"), *StateEnum->GetDisplayNameTextByValue(State).ToString(), ActorData.OnDemandInstanceIndex); + } + + /** Otherwise we should just be part of a state */ + return FString::Printf(TEXT("State - %s %1.2f"), *StateEnum->GetDisplayNameTextByValue(State).ToString(), ActorData.SignificanceValue); + }(); + + const FColor DebugColor = [&]() + { + const uint32 BlendInstanceIndex = ActorData.BlendInstanceIndex; + const uint32 DemandInstanceIndex = ActorData.OnDemandInstanceIndex; + + /** Colors match debug material colors */ + if (ActorData.bBlending && BlendInstanceIndex != INDEX_NONE) + { + return FColor::Blue; + } + else if (ActorData.bRunningOnDemand && DemandInstanceIndex != INDEX_NONE) + { + return FColor::Red; + } + + return FColor::Green; + }(); + +#if UE_BUILD_DEVELOPMENT + /** Draw text above AI pawn's head */ + DrawDebugString(GetWorld(), FVector(0.f, 0.f, 100.f), StateString, Actor, DebugColor, 0.016f, false); +#endif + if (GAnimationSharingDebugging >= 2) + { + const FString OnScreenString = FString::Printf(TEXT("%s\n\tState %s [%i]\n\t%s\n\tBlending %i On-Demand %i"), *Actor->GetName(), *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorData.PermutationIndex, *StateEnum->GetDisplayNameTextByValue(ActorData.PreviousState).ToString(), ActorData.bBlending, ActorData.bRunningOnDemand); + + GEngine->AddOnScreenDebugMessage(1337, 1, FColor::White, OnScreenString); + + const USkinnedMeshComponent* Component = PerComponentData[ActorData.ComponentIndices[0]].Component->MasterPoseComponent.Get(); +#if UE_BUILD_DEVELOPMENT + if (Component != nullptr) + { + DrawDebugLine(GetWorld(), Actor->GetActorLocation(), Component->GetComponentLocation(), FColor::Magenta); + } +#endif + } + + + } + } +#endif +} + +void UAnimSharingInstance::TickOnDemandInstances() +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateOnDemands); + for (int32 InstanceIndex = 0; InstanceIndex < OnDemandInstances.Num(); ++ InstanceIndex) + { + FOnDemandInstance& Instance = OnDemandInstances[InstanceIndex]; + checkf(Instance.bActive, TEXT("Container should be active at this point")); + + // Mark on-demand component as in-use + SetComponentUsage(true, Instance.State, Instance.UsedPerStateComponentIndex); + + const bool bShouldTick = DoAnyActorsRequireTicking(Instance); + if (bShouldTick) + { + // Mark component to tick + SetComponentTick(Instance.State, Instance.UsedPerStateComponentIndex); + } + + // Check and see whether or not the animation has finished + if (Instance.EndTime <= WorldTime) + { + // Set in-use flag to false this should set the component to not tick during the next TickAnimationStates + SetComponentUsage(false, Instance.State, Instance.UsedPerStateComponentIndex); + +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Finished on demand %s"), *StateEnum->GetDisplayNameTextByValue(Instance.State).ToString()); +#endif + auto SetActorState = [&](uint32 ActorIndex, uint8 NewState) + { + if (Instance.BlendToPermutationIndex != INDEX_NONE) + { + SetPermutationSlaveComponent(NewState, ActorIndex, Instance.BlendToPermutationIndex); + } + else + { + SetupSlaveComponent(NewState, ActorIndex); + } + + // Set actor states + PerActorData[ActorIndex].PreviousState = PerActorData[ActorIndex].CurrentState; + PerActorData[ActorIndex].CurrentState = NewState; + }; + + + // Set the components to their current state animation + for (uint32 ActorIndex : Instance.ActorIndices) + { + const uint32 CurrentState = PerActorData[ActorIndex].CurrentState; + // Return to the previous active animation state + if (Instance.bReturnToPreviousState) + { + //for (uint32 ActorIndex : Instance.ActorIndices) + { + // Retrieve previous state for the actor + const uint8 PreviousActorState = PerActorData[ActorIndex].PreviousState; + SetActorState(ActorIndex, PreviousActorState); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Returning [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(PreviousActorState).ToString()); +#endif + } + } + else if (Instance.ForwardState != (uint8)INDEX_NONE) + { + // We could forward it to a different state at this point + SetActorState(ActorIndex, Instance.ForwardState); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Forwarding [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(Instance.ForwardState).ToString()); +#endif + } + // Only do this if the state is different than the current on-demand one + else if (CurrentState != Instance.State) + { + // If the new state is not an on-demand one and we are not currently blending, if we are blending the blend will set the final master component + if (!PerStateData[CurrentState].bIsOnDemand || !Instance.bBlendActive) + { + SetActorState(ActorIndex, CurrentState); + + UAnimationSharingManager::SetDebugMaterialForActor(this, ActorIndex, 1); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Setting [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString()); +#endif + } + } + else + { + // Otherwise what do we do TODO +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("TODO-ing [%i]"), ActorIndex); +#endif + } + } + + // Clear out data for each actor part of this instance + for (uint32 ActorIndex : Instance.ActorIndices) + { + const bool bPartOfOtherOnDemand = PerActorData[ActorIndex].OnDemandInstanceIndex != InstanceIndex; + //ensureMsgf(!bPartOfOtherOnDemand, TEXT("Actor on demand index differs from current instance")); + + PerActorData[ActorIndex].OnDemandInstanceIndex = INDEX_NONE; + PerActorData[ActorIndex].bRunningOnDemand = false; + } + + // Remove this instance as it has finished work + RemoveOnDemandInstance(InstanceIndex); + + // Decrement index so we don't skip the swapped instance + --InstanceIndex; + } + else if (!Instance.bBlendActive && Instance.StartBlendTime <= WorldTime) + { + for (uint32 ActorIndex : Instance.ActorIndices) + { + // Whether or not we can/should actually blend + const bool bShouldBlend = ScalabilitySettings->UseBlendTransitions.Default && PerActorData[ActorIndex].SignificanceValue >= ScalabilitySettings->BlendSignificanceValue.Default; + + // Determine state to blend to + const uint8 BlendToState = [&]() -> uint8 + { + if (bShouldBlend) + { + bool bShouldProcess; + const uint32 DeterminedState = DetermineStateForActor(ActorIndex, bShouldProcess); + const uint32 CurrentState = PerActorData[ActorIndex].CurrentState != DeterminedState ? DeterminedState : PerActorData[ActorIndex].CurrentState; + + if (Instance.bReturnToPreviousState) + { + // Setup blend from on-demand animation into next state animation + return PerActorData[ActorIndex].PreviousState; + } + else if (Instance.ForwardState != (uint8)INDEX_NONE) + { + // Blend into the forward state + return Instance.ForwardState; + } + else if (PerActorData[ActorIndex].CurrentState != Instance.State) + { + // Blend to the actor's current state + return PerActorData[ActorIndex].CurrentState; + } + } + return INDEX_NONE; + }(); + + // Try to setup blending + if (BlendToState != (uint8)INDEX_NONE) + { + const uint32 BlendIndex = SetupBlendFromOnDemand(BlendToState, InstanceIndex, ActorIndex); + + if (BlendIndex != INDEX_NONE) + { + // TODO what if two actors have a different state they are blending to? --> Store permutation index + Instance.BlendToPermutationIndex = BlendInstances[BlendIndex].ToPermutationIndex; +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Blending [%i] out from %s to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(Instance.State).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendToState).ToString()); +#endif + } + } + + // OR results, some actors could not be blending + Instance.bBlendActive |= bShouldBlend; + } + } + } +} + +void UAnimSharingInstance::TickAdditiveInstances() +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateAdditives); + + for (int32 InstanceIndex = 0; InstanceIndex < AdditiveInstances.Num(); ++InstanceIndex) + { + FAdditiveInstance& Instance = AdditiveInstances[InstanceIndex]; + if (Instance.bActive) + { + const float WorldTimeSeconds = GetWorld()->GetTimeSeconds(); + if (WorldTimeSeconds >= Instance.EndTime) + { + // Finish + if (PerActorData.IsValidIndex(Instance.ActorIndex)) + { + PerActorData[Instance.ActorIndex].bRunningAdditive = false; + PerActorData[Instance.ActorIndex].AdditiveInstanceIndex = INDEX_NONE; + + // Set it to base component on top of the additive animation is playing + SetMasterComponentForActor(Instance.ActorIndex, Instance.AdditiveAnimationInstance->GetBaseComponent()); + } + FreeAdditiveInstance(Instance.AdditiveAnimationInstance); + RemoveAdditiveInstance(InstanceIndex); + --InstanceIndex; + } + } + else + { + Instance.bActive = true; + Instance.AdditiveAnimationInstance->Start(); + if (Instance.ActorIndex != INDEX_NONE) + { + SetMasterComponentForActor(Instance.ActorIndex, Instance.AdditiveAnimationInstance->GetComponent()); + } + } + } +} + +void UAnimSharingInstance::TickActorStates() +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_TickActorStates); + /** Tick each registered actor's state */ + for (int32 ActorIndex = 0; ActorIndex < RegisteredActors.Num(); ++ActorIndex) + { + /** Ensure Actor is still available */ + const AActor* Actor = RegisteredActors[ActorIndex]; + if (Actor) + { + FPerActorData& ActorData = PerActorData[ActorIndex]; + checkf(ActorData.ComponentIndices.Num(), TEXT("Registered Actor without SkeletalMeshComponents")); + + // Update actor and component visibility + ActorData.bRequiresTick = ActorData.SignificanceValue >= ScalabilitySettings->TickSignificanceValue.Default; + for (int32 ComponentIndex : ActorData.ComponentIndices) + { + if (PerComponentData[ComponentIndex].Component->LastRenderTime > (WorldTime - 1.f)) + { + PerComponentData[ComponentIndex].Component->bRecentlyRendered = true; + ActorData.bRequiresTick = true; + } + } + + // Determine current state for Actor + uint8& PreviousState = ActorData.CurrentState; + bool bShouldProcess = false; + const uint8 CurrentState = DetermineStateForActor(ActorIndex, bShouldProcess); + + // Determine whether we should blend according to the scalability settings + const bool bShouldBlend = ScalabilitySettings->UseBlendTransitions.Default && ActorData.SignificanceValue >= ScalabilitySettings->BlendSignificanceValue.Default; + + /** If the state is different we need to change animations and setup a transition */ + if (CurrentState != PreviousState) + { + /** When we are currently running an on-demand state we do not want as state change to impact the current animation */ + const bool bShouldNotProcess = ActorData.bRunningOnDemand && !PerStateData[CurrentState].bIsOnDemand; + + auto UpdateState = [&]() + { +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i state to %i previous %i | %i"), ActorIndex, CurrentState, PreviousState, ActorData.PermutationIndex); +#endif + ActorData.PreviousState = PreviousState; + ActorData.CurrentState = CurrentState; + }; + + /** If the processor explicitly outputs that the change in state should not impact behavior, just change state and do nothing */ + if (!bShouldProcess || bShouldNotProcess) + { + UpdateState(); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s while running on demand %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(ActorData.PreviousState).ToString(), ActorIndex); +#endif + } + /** Play additive animation only if actor isn't already playing one */ + else if (PerStateData[CurrentState].bIsAdditive && !ActorData.bRunningAdditive) + { + const uint32 AdditiveInstanceIndex = SetupAdditiveInstance(CurrentState, PreviousState, ActorData.PermutationIndex); + if (AdditiveInstanceIndex != INDEX_NONE) + { + ActorData.bRunningAdditive = true; + ActorData.AdditiveInstanceIndex = AdditiveInstanceIndex; + AdditiveInstances[AdditiveInstanceIndex].ActorIndex = ActorIndex; + } + } + /** If we are _already_ running an on-demand instance and the new state is also an on-demand we'll have to blend the new state in*/ + else if (PerStateData[CurrentState].bIsOnDemand) + { + /** If the new state is different than the currently running on-demand state, this could happen if we previously only updated the state and not processed it */ + const bool bSetupInstance = (!ActorData.bRunningOnDemand || (ActorData.bRunningOnDemand && OnDemandInstances[ActorData.OnDemandInstanceIndex].State != CurrentState)); + const uint32 OnDemandIndex = bSetupInstance ? SetupOnDemandInstance(CurrentState) : INDEX_NONE; + + if (OnDemandIndex != INDEX_NONE) + { + // Make sure we end any current blends + RemoveFromCurrentBlend(ActorIndex); + RemoveFromCurrentOnDemand(ActorIndex); + + bool bShouldSwitch = true; + if (bShouldBlend && !FMath::IsNearlyZero(PerStateData[CurrentState].BlendTime)) + { + if (ActorData.bRunningOnDemand) + { + /** Setup a blend between the current and a new instance*/ + const uint32 BlendInstanceIndex = SetupBlendBetweenOnDemands(ActorData.OnDemandInstanceIndex, OnDemandIndex, ActorIndex); + ActorData.BlendInstanceIndex = BlendInstanceIndex; + } + else + { + /** Setup a blend to an on-demand state/instance */ + const uint32 BlendInstanceIndex = SetupBlendToOnDemand(PreviousState, OnDemandIndex, ActorIndex); + ActorData.BlendInstanceIndex = BlendInstanceIndex; + } + + /** Blend was not succesfully set up so switch anyway */ + bShouldSwitch = (ActorData.BlendInstanceIndex == INDEX_NONE); + } + + if (bShouldSwitch) + { + /** Not blending so just switch to other on-demand instance */ + SwitchBetweenOnDemands(ActorData.OnDemandInstanceIndex, OnDemandIndex, ActorIndex); + } + + /** Add the current actor to the on-demand instance*/ + OnDemandInstances[OnDemandIndex].ActorIndices.Add(ActorIndex); + /** Also change actor data accordingly*/ + ActorData.OnDemandInstanceIndex = OnDemandIndex; + ActorData.bRunningOnDemand = true; + + UpdateState(); + } + } + /** Otherwise blend towards the new shared state */ + else + { + /** If actor is within blending distance setup/reuse a blend instance*/ + bool bShouldSwitch = true; + if (bShouldBlend) + { + const uint32 BlendInstanceIndex = SetupBlend(PreviousState, CurrentState, ActorIndex); + ActorData.BlendInstanceIndex = BlendInstanceIndex; + /** Blend was not succesfully set up so switch anyway */ + bShouldSwitch = (ActorData.BlendInstanceIndex == INDEX_NONE); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s with blend %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(PreviousState).ToString(), ActorIndex); +#endif + } + /** Otherwise just switch it to the new state */ + if (bShouldSwitch) + { + SetupSlaveComponent(CurrentState, ActorIndex); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(PreviousState).ToString(), ActorIndex); +#endif + } + + UpdateState(); + } + } + /** Flag the currently master component as in-use */ + else if (!ActorData.bRunningOnDemand && !ActorData.bBlending) + { +#if LOG_STATES + if (!PerStateData[ActorData.CurrentState].Components.IsValidIndex(ActorData.PermutationIndex)) + { + UE_LOG(LogAnimationSharing, Log, TEXT("Invalid permutation for actor %i is out of range of %i for state %s by actor %i"), ActorData.PermutationIndex, PerStateData[ActorData.CurrentState].Components.Num(), *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorIndex); + } + else if (!PerStateData[ActorData.CurrentState].Components[ActorData.PermutationIndex]->IsComponentTickEnabled()) + { + UE_LOG(LogAnimationSharing, Log, TEXT("Component not active %i for state %s by actor %i"), ActorData.PermutationIndex, *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorIndex); + } +#endif + + SetComponentUsage(true, ActorData.CurrentState, ActorData.PermutationIndex); + } + + // Propagate visibility to master component + if (ActorData.bRequiresTick) + { + SetComponentTick(ActorData.CurrentState, ActorData.PermutationIndex); + } + } + } +} + +void UAnimSharingInstance::RemoveFromCurrentBlend(int32 ActorIndex) +{ + if (PerActorData[ActorIndex].bBlending && PerActorData[ActorIndex].BlendInstanceIndex != INDEX_NONE && BlendInstances.IsValidIndex(PerActorData[ActorIndex].BlendInstanceIndex)) + { + FBlendInstance& OldBlendInstance = BlendInstances[PerActorData[ActorIndex].BlendInstanceIndex]; + SetMasterComponentForActor(ActorIndex, OldBlendInstance.TransitionBlendInstance->GetToComponent()); + OldBlendInstance.ActorIndices.Remove(ActorIndex); + PerActorData[ActorIndex].BlendInstanceIndex = INDEX_NONE; + } +} + +void UAnimSharingInstance::RemoveFromCurrentOnDemand(int32 ActorIndex) +{ + if (PerActorData[ActorIndex].bRunningOnDemand && PerActorData[ActorIndex].OnDemandInstanceIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(PerActorData[ActorIndex].OnDemandInstanceIndex)) + { + OnDemandInstances[PerActorData[ActorIndex].OnDemandInstanceIndex].ActorIndices.Remove(ActorIndex); + } +} + +void UAnimSharingInstance::TickBlendInstances() +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateBlends); + for (int32 InstanceIndex = 0; InstanceIndex < BlendInstances.Num(); ++InstanceIndex) + { + FBlendInstance& Instance = BlendInstances[InstanceIndex]; + checkf(Instance.bActive, TEXT("Blends should be active at this point")); + + /** Check whether or not the blend has ended */ + if (Instance.EndTime <= WorldTime) + { +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Finished blend %s from %s"), *StateEnum->GetDisplayNameTextByValue(Instance.StateTo).ToString(), *StateEnum->GetDisplayNameTextByValue(Instance.StateFrom).ToString()); +#endif + + // Finish blend into unique animation, need to just set it to use the correct component + const bool bToStateIsOnDemand = PerStateData[Instance.StateTo].bIsOnDemand; + const bool bFromStateIsOnDemand = PerStateData[Instance.StateFrom].bIsOnDemand; + + // If we were blending to an on-demand state we need to set the on-demand component as the new master component + if (bToStateIsOnDemand) + { + for (uint32 ActorIndex : Instance.ActorIndices) + { + SetMasterComponentForActor(ActorIndex, Instance.TransitionBlendInstance->GetToComponent()); + PerActorData[ActorIndex].PermutationIndex = 0; +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i to on-demand component %i"), ActorIndex, Instance.ToOnDemandInstanceIndex); +#endif + + for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices) + { + UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 0); + } + } + } + /** Otherwise if the state we were blending from was not on-demand we set the new state component as the new master component, + if we are blending from an on-demand state FOnDemandInstance with set the correct master component when it finishes */ + else if (!bFromStateIsOnDemand) + { + for (uint32 ActorIndex : Instance.ActorIndices) + { + if (PerActorData[ActorIndex].CurrentState == Instance.StateTo) + { +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i to state %i | %i"), ActorIndex, Instance.StateTo, Instance.ToPermutationIndex); +#endif + SetPermutationSlaveComponent(Instance.StateTo, ActorIndex, Instance.ToPermutationIndex); +#if !UE_BUILD_SHIPPING + for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices) + { + UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 1); + } +#endif + } + + } + } + + // Free up the used blend actor + FreeBlendInstance(Instance.TransitionBlendInstance); + + // Clear flags and index on the actor data as the blend has finished + for (uint32 ActorIndex : Instance.ActorIndices) + { + PerActorData[ActorIndex].BlendInstanceIndex = INDEX_NONE; + PerActorData[ActorIndex].bBlending = 0; + } + + // Remove this blend instance as it has finished + RemoveBlendInstance(InstanceIndex); + --InstanceIndex; + } + else + { + // Check whether or not the blend has started, if not set up the actors as slaves at this point + if (!Instance.bBlendStarted) + { + for (uint32 ActorIndex : Instance.ActorIndices) + { + SetMasterComponentForActor(ActorIndex, Instance.TransitionBlendInstance->GetComponent()); + + for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices) + { + UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 2); + } + } + + Instance.bBlendStarted = true; + } + + const bool bShouldTick = DoAnyActorsRequireTicking(Instance); + + if (!PerStateData[Instance.StateFrom].bIsOnDemand) + { + SetComponentUsage(true, Instance.StateFrom, Instance.FromPermutationIndex); + if (bShouldTick) + { + SetComponentTick(Instance.StateFrom, Instance.FromPermutationIndex); + } + } + + if (!PerStateData[Instance.StateTo].bIsOnDemand) + { + SetComponentUsage(true, Instance.StateTo, Instance.ToPermutationIndex); + if (bShouldTick) + { + SetComponentTick(Instance.StateTo, Instance.ToPermutationIndex); + } + } + } + } +} + +void UAnimSharingInstance::TickAnimationStates() +{ + for (FPerStateData& StateData : PerStateData) + { + for (int32 Index = 0; Index < StateData.Components.Num(); ++Index) + { + const bool bPreviousState = StateData.PreviousInUseComponentFrameBits[Index]; + const bool bCurrentState = StateData.InUseComponentFrameBits[Index]; + + const bool bShouldTick = StateData.SlaveTickRequiredFrameBits[Index]; + + if (bCurrentState != bPreviousState) + { + if (bCurrentState) + { + // Turn on + UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 1); + StateData.Components[Index]->SetComponentTickEnabled(true); + } + else + { + // Turn off + UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 0); + StateData.Components[Index]->SetComponentTickEnabled(false); + } + } + else if (!bCurrentState && StateData.Components[Index]->IsComponentTickEnabled()) + { + // Turn off + UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 0); + StateData.Components[Index]->SetComponentTickEnabled(false); + } + + StateData.Components[Index]->bRecentlyRendered = bShouldTick; + StateData.Components[Index]->VisibilityBasedAnimTickOption = bShouldTick ? EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones : EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; + } + + // Set previous to current and reset current bits + StateData.PreviousInUseComponentFrameBits = StateData.InUseComponentFrameBits; + StateData.InUseComponentFrameBits.Init(false, StateData.PreviousInUseComponentFrameBits.Num()); + StateData.SlaveTickRequiredFrameBits.Init(false, StateData.SlaveTickRequiredFrameBits.Num()); + + /** Reset on demand index for next frame */ + StateData.CurrentFrameOnDemandIndex = INDEX_NONE; + } +} + +void UAnimSharingInstance::SetComponentUsage(bool bUsage, uint8 StateIndex, uint32 ComponentIndex) +{ + // TODO component index should always be valid +#if LOG_STATES + if (!PerStateData[StateIndex].InUseComponentFrameBits.IsValidIndex(ComponentIndex)) + { + UE_LOG(LogAnimationSharing, Log, TEXT("Invalid set component usage %i is out of range of %i for state %s by component %i"), ComponentIndex, PerStateData[StateIndex].Components.Num(), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString(), ComponentIndex); + } +#endif + + if (PerStateData.IsValidIndex(StateIndex) && PerStateData[StateIndex].InUseComponentFrameBits.IsValidIndex(ComponentIndex)) + { + PerStateData[StateIndex].InUseComponentFrameBits[ComponentIndex] = bUsage; + } +} + +void UAnimSharingInstance::SetComponentTick(uint8 StateIndex, uint32 ComponentIndex) +{ + if (PerStateData[StateIndex].SlaveTickRequiredFrameBits.IsValidIndex(ComponentIndex)) + { + PerStateData[StateIndex].SlaveTickRequiredFrameBits[ComponentIndex] = true; + } +} + +void UAnimSharingInstance::FreeBlendInstance(FTransitionBlendInstance* Instance) +{ + Instance->Stop(); + BlendInstanceStack.FreeInstance(Instance); +} + +void UAnimSharingInstance::FreeAdditiveInstance(FAdditiveAnimationInstance* Instance) +{ + Instance->Stop(); + AdditiveInstanceStack.FreeInstance(Instance); +} + +void UAnimSharingInstance::SetMasterComponentForActor(uint32 ActorIndex, USkeletalMeshComponent* Component) +{ + // Always ensure the component is ticking + if (Component) + { + Component->SetComponentTickEnabled(true); + } + + const FPerActorData& ActorData = PerActorData[ActorIndex]; + // Do not update the component of the additive actor itself, otherwise update the base component + if (ActorData.bRunningAdditive && AdditiveInstances.IsValidIndex(ActorData.AdditiveInstanceIndex) && AdditiveInstances[ActorData.AdditiveInstanceIndex].AdditiveAnimationInstance->GetComponent() != Component) + { + AdditiveInstances[ActorData.AdditiveInstanceIndex].BaseComponent = Component; + AdditiveInstances[ActorData.AdditiveInstanceIndex].AdditiveAnimationInstance->UpdateBaseComponent(Component); + + return; + } + + for (uint32 ComponentIndex : ActorData.ComponentIndices) + { + PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(Component, true); + } +} + +void UAnimSharingInstance::SetupSlaveComponent(uint8 CurrentState, uint32 ActorIndex) +{ + const FPerStateData& StateData = PerStateData[CurrentState]; + if (!StateData.bIsOnDemand) + { + const uint32 PermutationIndex = DeterminePermutationIndex(ActorIndex, CurrentState); + SetPermutationSlaveComponent(CurrentState, ActorIndex, PermutationIndex); + } + else + { + const uint32 OnDemandInstanceIndex = SetupOnDemandInstance(CurrentState); + + if (OnDemandInstanceIndex != INDEX_NONE) + { + USkeletalMeshComponent* MasterComponent = StateData.Components[OnDemandInstances[OnDemandInstanceIndex].UsedPerStateComponentIndex]; + SetMasterComponentForActor(ActorIndex, MasterComponent); + OnDemandInstances[OnDemandInstanceIndex].ActorIndices.Add(ActorIndex); + + PerActorData[ActorIndex].OnDemandInstanceIndex = OnDemandInstanceIndex; + PerActorData[ActorIndex].bRunningOnDemand = true; + + // TODO do we need to reset + PerActorData[ActorIndex].PermutationIndex = 0; + } + } +} + +void UAnimSharingInstance::SetPermutationSlaveComponent(uint8 StateIndex, uint32 ActorIndex, uint32 PermutationIndex) +{ + const FPerStateData& StateData = PerStateData[StateIndex]; + + // TODO Min should not be needed if PermutationIndex is always valid + PermutationIndex = FMath::Min((uint32)StateData.Components.Num() - 1, PermutationIndex); +#if LOG_STATES + if (!StateData.Components.IsValidIndex(PermutationIndex)) + { + UE_LOG(LogAnimationSharing, Log, TEXT("Invalid set component usage %i is out of range of %i for state %s by actor %i"), PermutationIndex, StateData.Components.Num(), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString(), ActorIndex); + } +#endif + + SetMasterComponentForActor(ActorIndex, StateData.Components[PermutationIndex]); + PerActorData[ActorIndex].PermutationIndex = PermutationIndex; + UAnimationSharingManager::SetDebugMaterial(StateData.Components[PermutationIndex], 1); +} + +uint32 UAnimSharingInstance::DeterminePermutationIndex(uint32 ActorIndex, uint8 State) const +{ + const FPerStateData& StateData = PerStateData[State]; + const TArray& Components = StateData.Components; + + // This can grow to be more intricate to take into account surrounding actors? + const uint32 PermutationIndex = FMath::RandHelper(Components.Num()); + checkf(Components.IsValidIndex(PermutationIndex), TEXT("Not enough MasterComponents initialised!")); + + return PermutationIndex; +} + +uint32 UAnimSharingInstance::SetupBlend(uint8 FromState, uint8 ToState, uint32 ActorIndex) +{ + const bool bConcurrentBlendsReached = !BlendInstanceStack.InstanceAvailable(); + const bool bOnDemand = PerStateData[ToState].bIsOnDemand; + + uint32 BlendInstanceIndex = INDEX_NONE; + if (!bConcurrentBlendsReached) + { + BlendInstanceIndex = BlendInstances.IndexOfByPredicate([&](const FBlendInstance& Instance) + { + return (!Instance.bActive && // The instance should not have started yet + Instance.StateFrom == FromState && // It should be blending from the same state + Instance.StateTo == ToState && // It should be blending to the same state + Instance.bOnDemand == bOnDemand && // It should match whether or not it is an on-demand state QQQ is this needed? + Instance.FromPermutationIndex == PerActorData[ActorIndex].PermutationIndex); // It should be blending from the same permutation inside of the state + }); + + FBlendInstance* BlendInstance = BlendInstanceIndex != INDEX_NONE ? &BlendInstances[BlendInstanceIndex] : nullptr; + + if (!BlendInstance) + { + BlendInstance = &BlendInstances.AddDefaulted_GetRef(); + BlendInstanceIndex = BlendInstances.Num() - 1; + BlendInstance->bActive = false; + BlendInstance->FromOnDemandInstanceIndex = BlendInstance->ToOnDemandInstanceIndex = INDEX_NONE; + BlendInstance->StateFrom = FromState; + BlendInstance->StateTo = ToState; + BlendInstance->BlendTime = CalculateBlendTime( ToState); + BlendInstance->bOnDemand = bOnDemand; + BlendInstance->EndTime = GetWorld()->GetTimeSeconds() + BlendInstance->BlendTime; + BlendInstance->TransitionBlendInstance = BlendInstanceStack.GetInstance(); + + BlendInstance->TransitionBlendInstance->GetComponent()->SetComponentTickEnabled(true); + + // Setup permutation indices to and from we are blending + BlendInstance->FromPermutationIndex = PerActorData[ActorIndex].PermutationIndex; + BlendInstance->ToPermutationIndex = DeterminePermutationIndex( ActorIndex, ToState); + } + + checkf(BlendInstance, TEXT("Unable to create blendcontainer")); + + BlendInstance->ActorIndices.Add(ActorIndex); + PerActorData[ActorIndex].bBlending = true; + } + + return BlendInstanceIndex; +} + +uint32 UAnimSharingInstance::SetupBlendFromOnDemand(uint8 ToState, uint32 OnDemandInstanceIndex, uint32 ActorIndex) +{ + const uint8 FromState = OnDemandInstances[OnDemandInstanceIndex].State; + const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex); + + if (BlendInstanceIndex != INDEX_NONE) + { + BlendInstances[BlendInstanceIndex].FromOnDemandInstanceIndex = OnDemandInstanceIndex; + } + + return BlendInstanceIndex; +} + +uint32 UAnimSharingInstance::SetupBlendBetweenOnDemands(uint8 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex) +{ + const uint8 FromState = OnDemandInstances[FromOnDemandInstanceIndex].State; + const uint8 ToState = OnDemandInstances[ToOnDemandInstanceIndex].State; + const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex); + + if (BlendInstanceIndex != INDEX_NONE) + { + BlendInstances[BlendInstanceIndex].FromOnDemandInstanceIndex = FromOnDemandInstanceIndex; + BlendInstances[BlendInstanceIndex].ToOnDemandInstanceIndex = ToOnDemandInstanceIndex; + } + + return BlendInstanceIndex; +} + +uint32 UAnimSharingInstance::SetupBlendToOnDemand(uint8 FromState, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex) +{ + const uint8 ToState = OnDemandInstances[ToOnDemandInstanceIndex].State; + const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex); + + if (BlendInstanceIndex != INDEX_NONE) + { + BlendInstances[BlendInstanceIndex].ToOnDemandInstanceIndex = ToOnDemandInstanceIndex; + } + + return BlendInstanceIndex; +} + +void UAnimSharingInstance::SwitchBetweenOnDemands(uint32 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex) +{ + /** Remove this actor from the currently running on-demand instance */ + if (FromOnDemandInstanceIndex != INDEX_NONE) + { + OnDemandInstances[FromOnDemandInstanceIndex].ActorIndices.Remove(ActorIndex); + } + + const FOnDemandInstance& Instance = OnDemandInstances[ToOnDemandInstanceIndex]; + const uint32 ComponentIndex = Instance.UsedPerStateComponentIndex; + const uint32 StateIndex = Instance.State; + PerActorData[ActorIndex].PermutationIndex = 0; + SetMasterComponentForActor(ActorIndex, PerStateData[StateIndex].Components[ComponentIndex]); +} + +uint32 UAnimSharingInstance::SetupOnDemandInstance(uint8 StateIndex) +{ + uint32 InstanceIndex = INDEX_NONE; + + FPerStateData& StateData = PerStateData[StateIndex]; + if (StateData.CurrentFrameOnDemandIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(StateData.CurrentFrameOnDemandIndex)) + { + InstanceIndex = StateData.CurrentFrameOnDemandIndex; + } + else + { + // Otherwise we'll need to kick one of right now so try and set one up + if (StateData.Components.Num()) + { + const uint32 AvailableIndex = StateData.InUseComponentFrameBits.FindAndSetFirstZeroBit(); + + if (AvailableIndex != INDEX_NONE) + { + FOnDemandInstance& Instance = OnDemandInstances.AddDefaulted_GetRef(); + InstanceIndex = OnDemandInstances.Num() - 1; + StateData.CurrentFrameOnDemandIndex = InstanceIndex; + + Instance.bActive = 0; + Instance.bBlendActive = 0; + Instance.State = StateIndex; + Instance.ForwardState = StateData.bShouldForwardToState ? StateData.ForwardStateValue : INDEX_NONE; + Instance.UsedPerStateComponentIndex = AvailableIndex; + Instance.bReturnToPreviousState = StateData.bReturnToPreviousState; + Instance.StartTime = 0.f; + Instance.BlendToPermutationIndex = INDEX_NONE; + + const float WorldTimeSeconds = GetWorld()->GetTimeSeconds(); + Instance.EndTime = WorldTimeSeconds + StateData.AnimationLengths[AvailableIndex]; + Instance.StartBlendTime = Instance.EndTime - CalculateBlendTime(StateIndex); + + USkeletalMeshComponent* FreeComponent = StateData.Components[AvailableIndex]; + + UAnimationSharingManager::SetDebugMaterial(FreeComponent, 1); + + FreeComponent->SetComponentTickEnabled(true); + FreeComponent->SetPosition(0.f, false); + FreeComponent->Play(false); +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Setup on demand state %s"), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString()); +#endif + } + else + { + // Next resort + const float MaxStartTime = WorldTime - PerStateData[StateIndex].WiggleTime; + float WiggleStartTime = TNumericLimits::Max(); + float NonWiggleStartTime = TNumericLimits::Max(); + int32 WiggleIndex = INDEX_NONE; + int32 NonWiggleIndex = INDEX_NONE; + for (int32 RunningInstanceIndex = 0; RunningInstanceIndex < OnDemandInstances.Num(); ++RunningInstanceIndex) + { + FOnDemandInstance& Instance = OnDemandInstances[RunningInstanceIndex]; + + if (Instance.State == StateIndex) + { + if (Instance.StartTime <= MaxStartTime && Instance.StartTime < WiggleStartTime) + { + WiggleStartTime = Instance.StartTime; + WiggleIndex = RunningInstanceIndex; + } + else if (Instance.StartTime < NonWiggleStartTime) + { + NonWiggleStartTime = Instance.StartTime; + NonWiggleIndex = RunningInstanceIndex; + } + } + } + + // Snap to on demand instance that has started last within the number of wiggle frames + if (WiggleIndex != INDEX_NONE) + { + InstanceIndex = WiggleIndex; + } + // Snap to closest on demand instance outside of the number of wiggle frames + else if (NonWiggleIndex != INDEX_NONE) + { + InstanceIndex = NonWiggleIndex; + } + else + { + // No instances available and none actually currently running this state, should probably up the number of available concurrent on demand instances at this point + UE_LOG(LogAnimationSharing, Warning, TEXT("No more on demand components available")); + } + } + } + } + + return InstanceIndex; +} + +uint32 UAnimSharingInstance::SetupAdditiveInstance(uint8 StateIndex, uint8 FromState, uint8 StateComponentIndex) +{ + uint32 InstanceIndex = INDEX_NONE; + + const FPerStateData& StateData = PerStateData[StateIndex]; + if (AdditiveInstanceStack.InstanceAvailable()) + { + FAdditiveAnimationInstance* AnimationInstance = AdditiveInstanceStack.GetInstance(); + FAdditiveInstance& Instance = AdditiveInstances.AddDefaulted_GetRef(); + Instance.bActive = false; + Instance.AdditiveAnimationInstance = AnimationInstance; + Instance.BaseComponent = PerStateData[FromState].Components[StateComponentIndex]; + const float WorldTimeSeconds = GetWorld()->GetTimeSeconds(); + Instance.EndTime = WorldTimeSeconds + StateData.AdditiveAnimationSequence->SequenceLength; + Instance.State = StateIndex; + + InstanceIndex = AdditiveInstances.Num() - 1; + AnimationInstance->Setup(Instance.BaseComponent, StateData.AdditiveAnimationSequence); + } + + return InstanceIndex; +} + +void UAnimSharingInstance::KickoffInstances() +{ + SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_KickoffInstances); + for (FBlendInstance& BlendInstance : BlendInstances) + { + if (!BlendInstance.bActive) + { + BlendInstance.bBlendStarted = false; + + FString ActorIndicesString; + for (uint32 ActorIndex : BlendInstance.ActorIndices) + { + if (ActorIndex != BlendInstance.ActorIndices.Last()) + { + ActorIndicesString += FString::Printf(TEXT("%i, "), ActorIndex); + } + else + { + ActorIndicesString += FString::Printf(TEXT("%i"), ActorIndex); + } + } +#if LOG_STATES + UE_LOG(LogAnimationSharing, Log, TEXT("Starting blend from %s to %s [%s]"), *StateEnum->GetDisplayNameTextByValue(BlendInstance.StateFrom).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendInstance.StateTo).ToString(), *ActorIndicesString); +#endif + + // TODO should be able to assume permutation indices are valid here + BlendInstance.FromPermutationIndex = FMath::Min((uint32)PerStateData[BlendInstance.StateFrom].Components.Num() - 1, BlendInstance.FromPermutationIndex); + BlendInstance.ToPermutationIndex = FMath::Min((uint32)PerStateData[BlendInstance.StateTo].Components.Num() - 1, BlendInstance.ToPermutationIndex); + + USkeletalMeshComponent* From = PerStateData[BlendInstance.StateFrom].Components[BlendInstance.FromPermutationIndex]; + USkeletalMeshComponent* To = PerStateData[BlendInstance.StateTo].Components[BlendInstance.ToPermutationIndex]; + + if (PerStateData[BlendInstance.StateTo].bIsOnDemand && (BlendInstance.ToOnDemandInstanceIndex != INDEX_NONE)) + { + To = PerStateData[BlendInstance.StateTo].Components[OnDemandInstances[BlendInstance.ToOnDemandInstanceIndex].UsedPerStateComponentIndex]; + } + + if (PerStateData[BlendInstance.StateFrom].bIsOnDemand && (BlendInstance.FromOnDemandInstanceIndex != INDEX_NONE)) + { + const uint32 UsedComponentIndex = OnDemandInstances[BlendInstance.FromOnDemandInstanceIndex].UsedPerStateComponentIndex; + From = PerStateData[BlendInstance.StateFrom].Components[UsedComponentIndex]; + } + + for (uint32 ActorIndex : BlendInstance.ActorIndices) + { + PerActorData[ActorIndex].PermutationIndex = BlendInstance.ToPermutationIndex; + PerActorData[ActorIndex].bBlending = true; + } + + BlendInstance.TransitionBlendInstance->Setup(From, To, BlendInstance.BlendTime); + BlendInstance.bActive = true; + } + } + + for (FOnDemandInstance& OnDemandInstance : OnDemandInstances) + { + if (!OnDemandInstance.bActive) + { + OnDemandInstance.bActive = true; + OnDemandInstance.StartTime = WorldTime; + } + } +} + +float UAnimSharingInstance::CalculateBlendTime(uint8 StateIndex) const +{ + checkf(PerStateData.IsValidIndex(StateIndex), TEXT("Invalid State index")); + return PerStateData[StateIndex].BlendTime; +} + +void UAnimSharingInstance::RemoveComponent(int32 ComponentIndex) +{ + if (PerComponentData.Num() > 1 && ComponentIndex != PerComponentData.Num() - 1) + { + // Index of the component we will swap with + const uint32 SwapIndex = PerComponentData.Num() - 1; + + // Find actor for component we will swap with + const uint32 SwapActorIndex = PerComponentData[SwapIndex].ActorIndex; + + // Update component index in the actor to match with ComponentIndex (which it will be swapped with) + const uint32 ActorDataComponentIndex = PerActorData[SwapActorIndex].ComponentIndices.IndexOfByKey(SwapIndex); + if (ActorDataComponentIndex != INDEX_NONE) + { + PerActorData[SwapActorIndex].ComponentIndices[ActorDataComponentIndex] = ComponentIndex; + } + } + + PerComponentData.RemoveAtSwap(ComponentIndex, 1, false); +} + +void UAnimSharingInstance::RemoveBlendInstance(int32 InstanceIndex) +{ + FBlendInstance& Instance = BlendInstances[InstanceIndex]; + + // Index we could swap with + const uint32 SwapIndex = BlendInstances.Num() - 1; + if (BlendInstances.Num() > 1 && InstanceIndex != SwapIndex) + { + FBlendInstance& SwapInstance = BlendInstances[SwapIndex]; + // Remap all of the actors to point to our new index + for (uint32 ActorIndex : SwapInstance.ActorIndices) + { + PerActorData[ActorIndex].BlendInstanceIndex = InstanceIndex; + } + } + + BlendInstances.RemoveAtSwap(InstanceIndex, 1, false); +} + +void UAnimSharingInstance::RemoveOnDemandInstance(int32 InstanceIndex) +{ + const FOnDemandInstance& Instance = OnDemandInstances[InstanceIndex]; + + // Index we could swap with + const uint32 SwapIndex = OnDemandInstances.Num() - 1; + if (OnDemandInstances.Num() > 1 && InstanceIndex != SwapIndex) + { + const FOnDemandInstance& SwapInstance = OnDemandInstances[SwapIndex]; + // Remap all of the actors to point to our new index + for (uint32 ActorIndex : SwapInstance.ActorIndices) + { + // Only remap if it's still part of this instance + const bool bPartOfOtherOnDemand = PerActorData[ActorIndex].OnDemandInstanceIndex != InstanceIndex; + // Could be swapping with other instance in which case we should update the index + const bool bShouldUpdateIndex = !bPartOfOtherOnDemand || (PerActorData[ActorIndex].OnDemandInstanceIndex == SwapIndex); + + if (bShouldUpdateIndex) + { + PerActorData[ActorIndex].OnDemandInstanceIndex = InstanceIndex; + } + } + } + + // Remove and swap + OnDemandInstances.RemoveAtSwap(InstanceIndex, 1, false); +} + +void UAnimSharingInstance::RemoveAdditiveInstance(int32 InstanceIndex) +{ + FAdditiveInstance& Instance = AdditiveInstances[InstanceIndex]; + + // Index we could swap with + const uint32 SwapIndex = AdditiveInstances.Num() - 1; + if (AdditiveInstances.Num() > 1 && InstanceIndex != SwapIndex) + { + FAdditiveInstance& SwapInstance = AdditiveInstances[SwapIndex]; + // Remap all of the actors to point to our new index + if (SwapInstance.ActorIndex != INDEX_NONE) + { + PerActorData[SwapInstance.ActorIndex].AdditiveInstanceIndex = InstanceIndex; + } + } + + AdditiveInstances.RemoveAtSwap(InstanceIndex, 1, false); +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp new file mode 100644 index 000000000000..a4cbfe067f3d --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingModule.h" +#include "Engine/Engine.h" +#include "Components/SkeletalMeshComponent.h" +#include "AnimationSharingManager.h" + +IMPLEMENT_MODULE( FAnimSharingModule, AnimationSharing); + +TMap FAnimSharingModule::WorldAnimSharingManagers; + +void FAnimSharingModule::StartupModule() +{ + FWorldDelegates::OnPostWorldCleanup.AddStatic(&FAnimSharingModule::OnWorldCleanup); +} + +void FAnimSharingModule::AddReferencedObjects(FReferenceCollector& Collector) +{ + for (TPair& WorldAnimSharingManagerPair : WorldAnimSharingManagers) + { + Collector.AddReferencedObject(WorldAnimSharingManagerPair.Value, WorldAnimSharingManagerPair.Key); + } + +#if DEBUG_MATERIALS + for (UMaterialInterface* Material : UAnimationSharingManager::DebugMaterials) + { + Collector.AddReferencedObject(Material); + } +#endif +} + +bool FAnimSharingModule::CreateAnimationSharingManager(UWorld* InWorld, const UAnimationSharingSetup* Setup) +{ + if (InWorld && InWorld->IsGameWorld() && Setup && UAnimationSharingManager::AnimationSharingEnabled() && !WorldAnimSharingManagers.Contains(InWorld)) + { + UAnimationSharingManager* Manager = NewObject(InWorld); + Manager->Initialise(Setup); + WorldAnimSharingManagers.Add(InWorld, Manager); + + return true; + } + + return false; +} + +void FAnimSharingModule::OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources) +{ + WorldAnimSharingManagers.Remove(World); +} + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp new file mode 100644 index 000000000000..1f977d82b69d --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingSetup.h" + +UAnimationSharingSetup::UAnimationSharingSetup(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + +} + +#if WITH_EDITOR +void UAnimationSharingSetup::PostLoad() +{ + Super::PostLoad(); + + /** Ensure all data required for the UI is loaded */ + for (const FPerSkeletonAnimationSharingSetup& SharingSetup : SkeletonSetups) + { + SharingSetup.Skeleton.LoadSynchronous(); + SharingSetup.SkeletalMesh.LoadSynchronous(); + + for (const FAnimationStateEntry& Entry : SharingSetup.AnimationStates) + { + for (const FAnimationSetup& AnimSetup : Entry.AnimationSetups) + { + AnimSetup.AnimSequence.LoadSynchronous(); + } + } + } +} +#endif // WITH_EDITOR diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp new file mode 100644 index 000000000000..34c234cc8c6b --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp @@ -0,0 +1,72 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TransitionBlendInstance.h" +#include "AnimationSharingInstances.h" +#include "AnimationSharingManager.h" + +FTransitionBlendInstance::FTransitionBlendInstance() : SkeletalMeshComponent(nullptr), TransitionInstance(nullptr), FromComponent(nullptr), ToComponent(nullptr), BlendTime(0.f), bBlendState(false) {} + +void FTransitionBlendInstance::Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBPClass) +{ + if (InSkeletalMeshComponent && InAnimationBPClass) + { + SkeletalMeshComponent = InSkeletalMeshComponent; + SkeletalMeshComponent->SetAnimInstanceClass(InAnimationBPClass); + SkeletalMeshComponent->SetComponentTickEnabled(false); + SkeletalMeshComponent->SetForcedLOD(0); + SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; + TransitionInstance = Cast(SkeletalMeshComponent->GetAnimInstance()); + } +} + +void FTransitionBlendInstance::Setup(USkeletalMeshComponent* InFromComponent, USkeletalMeshComponent* InToComponent, float InBlendTime) +{ + UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 1); + SkeletalMeshComponent->SetComponentTickEnabled(true); + BlendTime = InBlendTime; + if (TransitionInstance) + { + if (TransitionInstance->bBlendBool) + { + TransitionInstance->FromComponent = FromComponent = InFromComponent; + TransitionInstance->ToComponent = ToComponent = InToComponent; + } + else + { + TransitionInstance->FromComponent = FromComponent = InToComponent; + TransitionInstance->ToComponent = ToComponent = InFromComponent; + } + + TransitionInstance->BlendTime = InBlendTime; + TransitionInstance->bBlendBool = bBlendState = !TransitionInstance->bBlendBool; + + SkeletalMeshComponent->AddTickPrerequisiteComponent(FromComponent); + SkeletalMeshComponent->AddTickPrerequisiteComponent(ToComponent); + } +} + +void FTransitionBlendInstance::Stop() +{ + if (TransitionInstance) + { + UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 0); + SkeletalMeshComponent->SetComponentTickEnabled(false); + SkeletalMeshComponent->RemoveTickPrerequisiteComponent(FromComponent); + SkeletalMeshComponent->RemoveTickPrerequisiteComponent(ToComponent); + } +} + +USkeletalMeshComponent* FTransitionBlendInstance::GetComponent() const +{ + return SkeletalMeshComponent; +} + +USkeletalMeshComponent* FTransitionBlendInstance::GetToComponent() const +{ + return bBlendState == false ? ToComponent : FromComponent; +} + +USkeletalMeshComponent* FTransitionBlendInstance::GetFromComponent() const +{ + return bBlendState == false ? FromComponent : ToComponent; +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h new file mode 100644 index 000000000000..dd224303facc --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" + +class UAnimSharingAdditiveInstance; +class UAnimSequence; + +struct ANIMATIONSHARING_API FAdditiveAnimationInstance +{ +public: + FAdditiveAnimationInstance(); + + void Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBP); + void Setup(USkeletalMeshComponent* InBaseComponent, UAnimSequence* InAnimSequence); + void UpdateBaseComponent(USkeletalMeshComponent* InBaseComponent); + void Stop(); + void Start(); + + USkeletalMeshComponent* GetComponent() const; + USkeletalMeshComponent* GetBaseComponent() const; + +protected: + USkeletalMeshComponent * SkeletalMeshComponent; + UAnimSharingAdditiveInstance* AdditiveInstance; + UAnimSequence* AdditiveAnimationSequence; + USkeletalMeshComponent* BaseComponent; + bool bLoopingState; +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h new file mode 100644 index 000000000000..384125229e26 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h @@ -0,0 +1,80 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Animation/AnimInstance.h" +#include "AnimationSharingInstances.generated.h" + +UCLASS(transient, Blueprintable) +class ANIMATIONSHARING_API UAnimSharingStateInstance : public UAnimInstance +{ + friend class UAnimSharingInstance; + + GENERATED_UCLASS_BODY() + +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing) + UAnimSequence* AnimationToPlay; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing) + float PermutationTimeOffset; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing) + float PlayRate; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing) + bool bStateBool; + + UFUNCTION(BlueprintCallable, Category = AnimationSharing) + void GetInstancedActors(TArray& Actors); + +private: + uint8 StateIndex; + uint8 ComponentIndex; + class UAnimSharingInstance* Instance; +}; + +UCLASS(transient, Blueprintable) +class UAnimSharingTransitionInstance : public UAnimInstance +{ + friend struct FTransitionBlendInstance; + + GENERATED_UCLASS_BODY() +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition) + TWeakObjectPtr FromComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition) + TWeakObjectPtr ToComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition) + float BlendTime; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition) + bool bBlendBool; +}; + + +UCLASS(transient, Blueprintable) +class UAnimSharingAdditiveInstance : public UAnimInstance +{ + friend struct FAdditiveAnimationInstance; + + GENERATED_UCLASS_BODY() +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive) + TWeakObjectPtr BaseComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive) + TWeakObjectPtr AdditiveAnimation; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive) + float Alpha; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive) + bool bStateBool; +}; + + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h new file mode 100644 index 000000000000..a799b9ff8220 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h @@ -0,0 +1,555 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "AnimationSharingModule.h" +#include "AnimationSharingTypes.h" + +#include "Tickable.h" +#include "UObject/SoftObjectPtr.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/DeveloperSettings.h" +#include "Animation/AnimBlueprint.h" +#include "AdditiveAnimationInstance.h" +#include "TransitionBlendInstance.h" + +#include "AnimationSharingManager.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogAnimationSharing, Log, All); +DECLARE_STATS_GROUP(TEXT("Animation Sharing Manager"), STATGROUP_AnimationSharing, STATCAT_Advanced); + +#define DEBUG_MATERIALS (UE_EDITOR && !UE_BUILD_SHIPPING) + +DECLARE_DELEGATE_OneParam(FUpdateActorHandle, int32); + +class USignificanceManager; +typedef uint32 AnimationSharingDataHandle; + +/** Structure which holds data about a currently in progress blend between two states */ +struct FBlendInstance +{ + /** Flag whether or not this instance is currently active */ + bool bActive; + /** Flag whether or not the actor's part of this have been setup as a slave component to the blend actor, this is done so the blend actor atleast ticks once (otherwise it can pop from the previous blend end pose) */ + bool bBlendStarted; + /** Flag whether or not this instance is blending towards an on-demand state */ + bool bOnDemand; + /** World time in seconds at which the blend has finished (calculated at start of blend world time + blend duration) */ + float EndTime; + /** Duration of the blend */ + float BlendTime; + /** State value to blend from */ + uint8 StateFrom; + /** State value to blend to */ + uint8 StateTo; + + /** Permutation indices from and to which we are blending, used to ensure we 'forward' the actor to the correct MasterPoseComponent when finished blending */ + uint32 FromPermutationIndex; + uint32 ToPermutationIndex; + + /** Actor used for blending between the two states */ + FTransitionBlendInstance* TransitionBlendInstance; + /** Indices of actors who are set up as slaves to BlendActor's main skeletal mesh component */ + TArray ActorIndices; + + /** Optional index into OnDemandInstances from which we are blending */ + uint32 FromOnDemandInstanceIndex; + /** Optional index into OnDemandInstance to which we are blending */ + uint32 ToOnDemandInstanceIndex; +}; + +/** Structure which holds data about a currently running on-demand state animation instance */ +struct FOnDemandInstance +{ + /** Flag whether or not instance is active*/ + bool bActive; + bool bBlendActive; + + /** Flag whether or not the component should be 'returned' to the state they were in before the on-demand animation */ + bool bReturnToPreviousState; + + /** State value which is active */ + uint8 State; + + /** State value which the components should be set to when the on-demand animation has finished playing (used when !bReturnToPreviousState) */ + uint8 ForwardState; + + /** Time at which this instance was started */ + float StartTime; + + /** Time at which this on demand instance should blend out into the 'next' state the actor is in */ + float StartBlendTime; + + /** World time in seconds at which the animation has finished playing (calculated at start of blend world time + animation sequence length) */ + float EndTime; + + /** Index into Components array for the current state data which is used for playing the animation*/ + uint32 UsedPerStateComponentIndex; + + /** Permutation index that we are blending to before the end of the animation */ + uint32 BlendToPermutationIndex; + + /** Indices of actors who are set up as slaves to the skeletal mesh component running the animation */ + TArray ActorIndices; +}; + +struct FAdditiveInstance +{ + /** Flag whether or not instance is active */ + bool bActive; + + /** State index this instance is running */ + uint8 State; + + /** Time at which this instance finishes */ + float EndTime; + + /** Current actor indices as part of this instance */ + uint32 ActorIndex; + + /** Skeletal mesh component on which the additive animation is applied */ + USkeletalMeshComponent* BaseComponent; + + /** Actor used for playing the additive animation */ + FAdditiveAnimationInstance* AdditiveAnimationInstance; +}; + +/** Structure which holds data about a unique state which is linked to an enumeration value defined by the user. The data is populated from the user exposed FAnimationStateEntry */ +struct FPerStateData +{ + FPerStateData() : bIsOnDemand(false), bIsAdditive(false), BlendTime(0.f), CurrentFrameOnDemandIndex(INDEX_NONE), StateEnumValue(INDEX_NONE), AdditiveAnimationSequence(nullptr) {} + + /** Flag whether or not this state is an on-demand state, this means that we kick off a unique animation when needed */ + bool bIsOnDemand; + + /** Flag whether or not this state is an additive state */ + bool bIsAdditive; + + /** Flag whether or not we should return to the previous state, only used when this state is an on-demand one*/ + bool bReturnToPreviousState; + + /** Flag whether or not ForwardStateValue should be used hwen the animation has finished*/ + bool bShouldForwardToState; + + /** Duration of blending when blending to this state */ + float BlendTime; + + /** This is (re-)set every frame, and allows for quickly finding an on-demand instance which was setup this frame */ + uint32 CurrentFrameOnDemandIndex; + /** Number of 'wiggle' frames, this is used when we run out of available entries in Components, if one of the FOnDemandInstance has started NumWiggleFrames ago or earlier, + it is used instead of a brand new one */ + float WiggleTime; + + /** State value to which the actors part of an FOnDemandInstance should be set to when its animation has finished */ + uint8 ForwardStateValue; + /** Enum value linked to this state */ + uint8 StateEnumValue; + + /** Animation Sequence that is used for Additive States */ + UAnimSequence* AdditiveAnimationSequence; + + /** Components setup to play animations for this state */ + TArray Components; + /** Bits keeping track which of the components are in-use, in case of On Demand state this is managed by FOnDemandInstance, otherwise we clear and populate the flags each frame */ + TBitArray<> InUseComponentFrameBits; + TBitArray<> PreviousInUseComponentFrameBits; + + /** Bits keeping track whether or not any of the slave components requires the master component to tick */ + TBitArray<> SlaveTickRequiredFrameBits; + + /** Length of the animations used for an on-demand state, array as it could contain different animation permutations */ + TArray AnimationLengths; +}; + +struct FPerComponentData +{ + /** Skeletal mesh component registered for this component */ + USkeletalMeshComponent* Component; + /** Index to the owning actor (used to index PerActorData) */ + int32 ActorIndex; +}; + +struct FPerActorData +{ + /** Current state value (used to index PerStateData) */ + uint8 CurrentState; + /** Previous state value (used to index PerStateData) */ + uint8 PreviousState; + /** Permutation index (used to index Components array inside of PerStateData) */ + uint8 PermutationIndex; + + /** Flag whether or not we are currently blending */ + bool bBlending; + /** Flag whether or not we are currently part of an on-demand animation state */ + bool bRunningOnDemand; + + /** Flag whether or not we are currently part of an additive animation state */ + bool bRunningAdditive; + + /** Cached significance value */ + float SignificanceValue; + + /** Flag whether or not this actor requires the master component to tick */ + bool bRequiresTick; + + /** Index to blend instance which is currently driving this actor's animation */ + uint32 BlendInstanceIndex; + /** Index to on demand instance which is running accord to our current state (or previous state) */ + uint32 OnDemandInstanceIndex; + /** Index to additive instance which is running on top of our state */ + uint32 AdditiveInstanceIndex; + + /** Indices of the Components owned by this actor (used to index into PerComponentData) */ + TArray ComponentIndices; + + /** Register delegate called when actor is swapped and the handle should be updated */ + FUpdateActorHandle UpdateActorHandleDelegate; +}; + +template +struct FInstanceStack +{ + ~FInstanceStack() + { + for (InstanceType* Instance : AvailableInstances) + { + delete Instance; + } + AvailableInstances.Empty(); + + for (InstanceType* Instance : InUseInstances) + { + delete Instance; + } + InUseInstances.Empty(); + } + + + /** Return whether instance are available */ + bool InstanceAvailable() const + { + return AvailableInstances.Num() != 0; + } + + /** Get an available instance */ + InstanceType* GetInstance() + { + InstanceType* Instance = nullptr; + if (AvailableInstances.Num()) + { + Instance = AvailableInstances.Pop(false); + InUseInstances.Add(Instance); + } + + return Instance; + } + + /** Return instance back */ + void FreeInstance(InstanceType* Instance) + { + AvailableInstances.Add(Instance); + InUseInstances.RemoveSwap(Instance); + } + + /** Add a new instance to the 'stack' */ + void AddInstance(InstanceType* Instance) + { + AvailableInstances.Add(Instance); + } + + TArray AvailableInstances; + TArray InUseInstances; +}; + +UCLASS() +class UAnimSharingInstance : public UObject +{ + GENERATED_BODY() + +public: + // Begin UObject overrides + virtual void BeginDestroy() override; + // End UObject overrides + + /** This uses the StateProcessor to determine the state index the actor is currently in */ + uint8 DetermineStateForActor(uint32 ActorIndex, bool& bShouldProcess); + + /** Initial set up of all animation sharing data and states */ + void Setup(UAnimationSharingManager* AnimationSharingManager, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, const FAnimationSharingScalability* ScalabilitySettings, uint32 Index); + /** Populates data for a state setup */ + void SetupState(FPerStateData& StateData, const FAnimationStateEntry& StateEntry, USkeletalMesh* SkeletalMesh, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, uint32 Index); + + /** Retrieves a blend instance, this could either mean reusing an already in progress one or a brand new one (if available according to scalability settings) + returns whether or not the setup was successful */ + uint32 SetupBlend(uint8 FromState, uint8 ToState, uint32 ActorIndex); + + /** Retrieves a blend instance, and sets up a blend from a currently running On-Demand instance to ToState */ + uint32 SetupBlendFromOnDemand(uint8 ToState, uint32 OnDemandInstanceIndex, uint32 ActorIndex); + + /** Retrieves a blend instance, and sets up a blend between a currently running On-Demand instance and another one which was started this frame */ + uint32 SetupBlendBetweenOnDemands(uint8 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex); + + /** Retrieves a blend instance, and setups up a blend to an On-Demand instance from a regular animation state */ + uint32 SetupBlendToOnDemand(uint8 FromState, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex); + + /** Switches between on-demand instances directly, without blending */ + void SwitchBetweenOnDemands(uint32 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex); + + /** Retrieves a blend instance, this could either mean reusing an already in progress one or a brand new one (if available according to scalability settings) + returns an index into OnDemandInstances array or INDEX_NONE if unable to setup an instance */ + uint32 SetupOnDemandInstance(uint8 StateIndex); + + /** Retrieves an additive instance, these are unique and cannot be reused */ + uint32 SetupAdditiveInstance(uint8 StateIndex, uint8 FromState, uint8 StateComponentIndex); + + /** Retrieves the blend-time for this specific state */ + float CalculateBlendTime(uint8 StateIndex) const; + + /** Kicks off the blend and on-demand instances at the end of the current frame tick, this sets up the blend instance with the correct components to blend between */ + void KickoffInstances(); + + /** Ticks all currently running blend instances, checks whether or not the blend is finished and forwards the actor/components to the correct animation state */ + void TickBlendInstances(); + + /** Ticks all currently running on-demand instances, this checks whether or not the animation has finished or if we have to start blending out of the state already */ + void TickOnDemandInstances(); + + template + bool DoAnyActorsRequireTicking(const InstanceType& Instance) + { + bool bShouldTick = false; + for (uint32 ActorIndex : Instance.ActorIndices) + { + if (PerActorData[ActorIndex].bRequiresTick) + { + bShouldTick = true; + break; + } + } + + return bShouldTick; + } + + /** Ticks all currently running additive animation instances, this checks whether or not it has finished yet and sets the base-component as the master component when it has */ + void TickAdditiveInstances(); + + /** Ticks all Actor Data entries and determines their current state, if changed since last tick it will alter their animation accordingly */ + void TickActorStates(); + + /** Ticks various types of debugging data / drawing (not active in shipping build) */ + void TickDebugInformation(); + + /** Ticks all unique animation states, this checks which components are currently used and turns of those which currently don't have any slaves */ + void TickAnimationStates(); + + /** Removal functions which also make sure that indices of other data-structres are correctly remapped */ + void RemoveBlendInstance(int32 InstanceIndex); + void RemoveOnDemandInstance(int32 InstanceIndex); + void RemoveAdditiveInstance(int32 InstanceIndex); + void RemoveComponent(int32 ComponentIndex); + + /** Removal functions which also make sure the actor is set to the correct master pose component */ + void RemoveFromCurrentBlend(int32 ActorIndex); + void RemoveFromCurrentOnDemand(int32 ActorIndex); + + /** Frees up a Blend instance and resets its state */ + void FreeBlendInstance(FTransitionBlendInstance* Instance); + + /** Frees up an Additive Animation instance and resets it state*/ + void FreeAdditiveInstance(FAdditiveAnimationInstance* Instance); + + /** Sets up all components of an actor to be slaves of Component */ + void SetMasterComponentForActor(uint32 ActorIndex, USkeletalMeshComponent* Component); + + /** Sets up the correct MasterPoseComponent for the passed in Component and State indices */ + void SetupSlaveComponent(uint8 CurrentState, uint32 ActorIndex); + + /** Sets up the correct MasterPoseComponent according to the state and permutation indices */ + void SetPermutationSlaveComponent(uint8 StateIndex, uint32 ActorIndex, uint32 PermutationIndex); + + /** Determines a permutation index for the given actor and state */ + uint32 DeterminePermutationIndex(uint32 ActorIndex, uint8 State) const; + + /** Marks the component as either used/not-used, this is used to disable ticking of components which are not in use*/ + void SetComponentUsage(bool bUsage, uint8 StateIndex, uint32 ComponentIndex); + + /** Sets the whether or not any of the slave components are visible */ + void SetComponentTick(uint8 StateIndex, uint32 ComponentIndex); + + /** Actors currently registered to be animation driven by the AnimManager using this setup */ + UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing) + TArray RegisteredActors; + + /** Per actor data, matches RegisteredActors*/ + TArray PerActorData; + /** Per component state data indexed from FPerActorData.ComponentIndices */ + TArray PerComponentData; + + /** Array of unique state data */ + TArray PerStateData; + + /** Blend and Additive actors data structure */ + FInstanceStack BlendInstanceStack; + FInstanceStack AdditiveInstanceStack; + + /** (Blueprint)class instance used for determining the state enum value for each registered actor */ + UPROPERTY(EditAnywhere, Transient, Category = AnimationSharing) + UAnimationSharingStateProcessor* StateProcessor; + bool bNativeStateProcessor; + + /** Currently running blend instances */ + TArray BlendInstances; + /** Currently running on-demand instance */ + TArray OnDemandInstances; + /** Currently running additive instances */ + TArray AdditiveInstances; + + UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing) + TArray UsedAnimationSequences; + + /** Significance manager used for retrieve AI actor significance values */ + USignificanceManager* SignificanceManager; + + /** Animation sharing manager for the current world */ + UAnimationSharingManager* AnimSharingManager; + + /** Enum class set up by the user to 'describe' the animation states */ + UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing) + UEnum* StateEnum; + + /** Actor to which all the running SkeletalMeshComponents used for the sharing are attached to */ + UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing) + AActor* SharingActor; + + /** Platform specific scalability settings */ + const FAnimationSharingScalability* ScalabilitySettings; + + /** Bounds for the currently used skeletal mesh */ + FVector SkeletalMeshBounds; + + /** Number of animation setups */ + uint32 NumSetups; + + /** Holds the current frame world time */ + float WorldTime; +}; + +USTRUCT() +struct FTickAnimationSharingFunction : public FTickFunction +{ + GENERATED_USTRUCT_BODY() + + FTickAnimationSharingFunction() : Manager(nullptr) {} + + // Begin FTickFunction overrides + virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; + virtual FString DiagnosticMessage() override; + virtual FName DiagnosticContext(bool bDetailed) override; + // End FTickFunction overrides + + class UAnimationSharingManager* Manager; +}; + + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithCopy = false + }; +}; + +UCLASS(config = Engine, defaultconfig) +class ANIMATIONSHARING_API UAnimationSharingManager : public UObject +{ + GENERATED_BODY() + +public: + // Begin UObject overrides + virtual void BeginDestroy() override; + virtual UWorld* GetWorld()const override; + // End UObject overrides + + /** Returns the AnimationSharing Manager, nullptr if none was set up */ + UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (WorldContext = "WorldContextObject")) + static UAnimationSharingManager* GetAnimationSharingManager(UObject* WorldContextObject); + + /** Create an Animation Sharing Manager using the provided Setup */ + UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (WorldContext = "WorldContextObject")) + static bool CreateAnimationSharingManager(UObject* WorldContextObject, const UAnimationSharingSetup* Setup); + + /** Register an Actor with this Animation Sharing manager, according to the SharingSkeleton */ + UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (DisplayName = "Register Actor")) + void RegisterActorWithSkeletonBP(AActor* InActor, const USkeleton* SharingSkeleton); + + /** Returns whether or not the animation sharing is enabled */ + UFUNCTION(BlueprintPure, Category = AnimationSharing) + static bool AnimationSharingEnabled(); + + /** Returns the AnimationSharing Manager for a specific UWorld, nullptr if none was set up */ + static UAnimationSharingManager* GetManagerForWorld(UWorld* InWorld); + + /** Registers actor with the animation sharing system */ + void RegisterActor(AActor* InActor, FUpdateActorHandle Delegate); + + /** Registers actor with the animation sharing system according to the SharingSkeleton's sharing setup (if available) */ + void RegisterActorWithSkeleton(AActor* InActor, const USkeleton* SharingSkeleton, FUpdateActorHandle Delegate); + + /** Unregisters actor with the animation sharing system */ + void UnregisterActor(AActor* InActor); + + /** Update cached significance for registered actor */ + void UpdateSignificanceForActorHandle(uint32 InHandle, float InValue); + + /** Ensures all actor data is cleared */ + void ClearActorData(); + + /** Ensures all currently registered actors are removed */ + void UnregisterAllActors(); + + /** Sets the visibility of currently used Master Pose Components */ + void SetMasterComponentsVisibility(bool bVisible); + + /** Initialize sharing data structures */ + void Initialise(const UAnimationSharingSetup* InSetup); + + /** Returns current scalability settings */ + const FAnimationSharingScalability& GetScalabilitySettings() const; + + void Tick(float DeltaTime); + FTickAnimationSharingFunction& GetTickFunction(); +protected: + + /** Populates all data required for a Skeleton setup */ + void SetupPerSkeletonData(const FPerSkeletonAnimationSharingSetup& SkeletonSetup); + + /** Dealing with Actor data and handles */ + uint32 CreateActorHandle(uint8 SkeletonIndex, uint32 ActorIndex) const; + uint8 GetSkeletonIndexFromHandle(uint32 InHandle) const; + uint32 GetActorIndexFromHandle(uint32 InHandle) const; + FPerActorData* GetActorDataByHandle(uint32 InHandle); +protected: + /** Array of unique skeletons, matches PerSkeletonData array entries*/ + TArray Skeletons; + + /** Sharing data required for the unique Skeleton setups */ + UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing) + TArray PerSkeletonData; + + /** Platform specific scalability settings */ + FAnimationSharingScalability ScalabilitySettings; + + /** Tick function for this manager */ + FTickAnimationSharingFunction TickFunction; +public: +#if DEBUG_MATERIALS + static TArray DebugMaterials; +#endif + static void SetDebugMaterial(USkeletalMeshComponent* Component, uint8 State); + static void SetDebugMaterialForActor(UAnimSharingInstance* Data, uint32 ActorIndex, uint8 State); + +#if WITH_EDITOR + static FName GetPlatformName(); +#endif +}; diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h new file mode 100644 index 000000000000..ba268f27b01f --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h @@ -0,0 +1,36 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Modules/ModuleManager.h" +#include "UObject/GCObject.h" +#include "Engine/World.h" + +class UAnimationSharingManager; +class UAnimationSharingSetup; + +class ANIMATIONSHARING_API FAnimSharingModule : public FDefaultGameModuleImpl, public FGCObject +{ +public: + // Begin IModuleInterface overrides + virtual void StartupModule() override; + // End IModuleInterface overrides + + // Begin FGCObject overrides + virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + // End FGCObject overrides + + FORCEINLINE static UAnimationSharingManager* Get(const UWorld* World) + { + return WorldAnimSharingManagers.FindRef(World); + } + + /** Creates an animation sharing manager for the given UWorld (must be a Game World) */ + static bool CreateAnimationSharingManager(UWorld* InWorld, const UAnimationSharingSetup* Setup); +private: + static void OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources); + static TMap WorldAnimSharingManagers; +}; diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h new file mode 100644 index 000000000000..821007a4ebd5 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "AnimationSharingTypes.h" + +#include "AnimationSharingSetup.generated.h" + +UCLASS(hidecategories = Object, Blueprintable, config = Engine) +class ANIMATIONSHARING_API UAnimationSharingSetup : public UObject +{ + GENERATED_UCLASS_BODY() +public: + +#if WITH_EDITOR + virtual void PostLoad() override; +#endif // WITH_EDITOR + + UPROPERTY(EditAnywhere, config, Category = AnimationSharing) + TArray SkeletonSetups; + + UPROPERTY(EditAnywhere, config, Category = AnimationSharing) + FAnimationSharingScalability ScalabilitySettings; +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h new file mode 100644 index 000000000000..d525f4b188b5 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h @@ -0,0 +1,181 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Engine/DeveloperSettings.h" +#include "PerPlatformProperties.h" +#include "Animation/AnimBlueprint.h" +#include "Animation/AnimSequence.h" +#include "Animation/Skeleton.h" +#include "Engine/SkeletalMesh.h" +#include "UObject/Class.h" +#include "Animation/AnimInstance.h" +#include "AnimationSharingInstances.h" +#include "AnimationSharingTypes.generated.h" + +USTRUCT() +struct FAnimationSetup +{ + GENERATED_BODY() +public: + FAnimationSetup() : AnimSequence(nullptr), AnimBlueprint(nullptr), NumRandomizedInstances(1), Enabled(true) {} + + /** Animation Sequence to play for this particular setup */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TSoftObjectPtr AnimSequence; + + /** Animation blueprint to use for playing back the Animation Sequence */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = AnimationSharing) + TSubclassOf AnimBlueprint; + + /** The number of randomized instances created from this setup, it will create instance with different start time offsets (Length / Number of Instance) * InstanceIndex */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + FPerPlatformInt NumRandomizedInstances; + + /** Whether or not this setup is enabled for specific platforms */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + FPerPlatformBool Enabled; +}; + +USTRUCT() +struct FAnimationStateEntry +{ + GENERATED_BODY() +public: + FAnimationStateEntry() : State(0), bOnDemand(false), bAdditive(false), BlendTime(0.f), bReturnToPreviousState(false), bSetNextState(false), NextState(0), MaximumNumberOfConcurrentInstances(1), WiggleTimePercentage(0.1f), bRequiresCurves(false) {} + + /** Enum value linked to this state */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + uint8 State; + + /** Per state animation setup */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TArray AnimationSetups; + + /** Flag whether or not this state is an on-demand state, this means that we kick off a unique animation when needed */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + bool bOnDemand; + + /** Whether or not this state is an additive state */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand")) + bool bAdditive; + + /** Duration of blending when blending to this state */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "!bAdditive")) + float BlendTime; + + /** Flag whether or not we should return to the previous state, only used when this state is an on-demand one*/ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand")) + bool bReturnToPreviousState; + + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand")) + bool bSetNextState; + + /** State value to which the actors part of an on demand state should be set to when its animation has finished */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bSetNextState")) + uint8 NextState; + + /** Number of instances that will be created for this state (platform-specific) */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta=(EditCondition="bOnDemand")) + FPerPlatformInt MaximumNumberOfConcurrentInstances; + + /** Percentage of 'wiggle' frames, this is used when we run out of available entries in Components, if one of the on-demand animations has started SequenceLength * WiggleFramePercentage ago or earlier, + it is used instead of a brand new one */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand", UIMin="0.0", UIMax="1.0")) + float WiggleTimePercentage; + + /** Whether or not this animation requires curves or morphtargets to function correctly for slave components */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = AnimationSharing) + bool bRequiresCurves; +}; + +UCLASS(Blueprintable) +class ANIMATIONSHARING_API UAnimationSharingStateProcessor : public UObject +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintNativeEvent, Category = AnimationSharing) + void ProcessActorState(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess); + void ProcessActorState_Implementation(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess) + { + ProcessActorState_Internal(OutState, InActor, CurrentState, OnDemandState, bShouldProcess); + } + + UFUNCTION(BlueprintNativeEvent, Category = AnimationSharing) + UEnum* GetAnimationStateEnum(); + UEnum* GetAnimationStateEnum_Implementation() + { + return GetAnimationStateEnum_Internal(); + } + + virtual void ProcessActorStates(TArray& OutStates, const TArray& InActors, const TArray& CurrentStates, const TArray& OnDemandStates, TArray& ShouldProcessFlags) + { + for (int32 Index = 0; Index < InActors.Num(); ++Index) + { + ProcessActorState(OutStates[Index], InActors[Index], CurrentStates[Index], OnDemandStates[Index], ShouldProcessFlags[Index]); + } + } + + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TSoftObjectPtr AnimationStateEnum; +protected: + virtual UEnum* GetAnimationStateEnum_Internal() { return AnimationStateEnum.LoadSynchronous(); } + virtual void ProcessActorState_Internal(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess) {} +}; + +USTRUCT() +struct FPerSkeletonAnimationSharingSetup +{ + GENERATED_BODY() +public: + FPerSkeletonAnimationSharingSetup() : Skeleton(nullptr), SkeletalMesh(nullptr), BlendAnimBlueprint(nullptr), AdditiveAnimBlueprint(nullptr), StateProcessorClass(nullptr) {} + + /** Skeleton compatible with the animation sharing setup */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TSoftObjectPtr Skeleton; + + /** Skeletal mesh used to setup skeletal mesh components */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TSoftObjectPtr SkeletalMesh; + + /** Animation blueprint used to perform the blending between states */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (DisplayName="Animation Blueprint for Blending")) + TSubclassOf BlendAnimBlueprint; + + /** Animation blueprint used to apply additive animation on top of other states */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (DisplayName = "Animation Blueprint for Additive Animation")) + TSubclassOf AdditiveAnimBlueprint; + + /** Interface class used when determining which state an actor is in */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TSubclassOf StateProcessorClass; + + /** Definition of different animation states */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + TArray AnimationStates; +}; + +USTRUCT() +struct FAnimationSharingScalability +{ + GENERATED_BODY() +public: + FAnimationSharingScalability() : UseBlendTransitions(true), BlendSignificanceValue(0.5f), MaximumNumberConcurrentBlends(1) {} + + /** Flag whether or not to use blend transitions between states */ + UPROPERTY(EditAnywhere, Category = AnimationSharing) + FPerPlatformBool UseBlendTransitions; + + /** Significance value tied to whether or not a transition should be blended */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions")) + FPerPlatformFloat BlendSignificanceValue; + + /** Maximum number of blends which can be running concurrently */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions")) + FPerPlatformInt MaximumNumberConcurrentBlends; + + /** Significance value tied to whether or not the master pose components should be ticking */ + UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions")) + FPerPlatformFloat TickSignificanceValue; +}; + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h new file mode 100644 index 000000000000..0ece3a9b1ff0 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" + +class UAnimSharingTransitionInstance; + +struct ANIMATIONSHARING_API FTransitionBlendInstance +{ +public: + FTransitionBlendInstance(); + void Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBP); + void Setup(USkeletalMeshComponent* InFromComponent, USkeletalMeshComponent* InToComponent, float InBlendTime); + void Stop(); + + USkeletalMeshComponent* GetComponent() const; + USkeletalMeshComponent* GetToComponent() const; + USkeletalMeshComponent* GetFromComponent() const; + +protected: + USkeletalMeshComponent * SkeletalMeshComponent; + UAnimSharingTransitionInstance* TransitionInstance; + USkeletalMeshComponent* FromComponent; + USkeletalMeshComponent* ToComponent; + float BlendTime; + bool bBlendState; +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs new file mode 100644 index 000000000000..20d11299c409 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class AnimationSharingEd : ModuleRules +{ + public AnimationSharingEd(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "AssetTools", + "AnimationSharing", + "Slate", + "SlateCore", + "PropertyEditor", + "EditorStyle" + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "AnimationSharingEd/Private" + }); + } + +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp new file mode 100644 index 000000000000..04a4ebed6f4a --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp @@ -0,0 +1,43 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingEdModule.h" +#include "AssetTypeActions_AnimationSharingSetup.h" +#include "AssetToolsModule.h" +#include "SetupDetailsViewCustomizations.h" + +IMPLEMENT_MODULE(FAnimSharingEdModule, AnimationSharingEd ); + +void FAnimSharingEdModule::StartupModule() +{ + FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule(); + + IAssetTools& AssetTools = AssetToolsModule.Get(); + AssetAction = new FAssetTypeActions_AnimationSharingSetup(); + AssetTools.RegisterAssetTypeActions(MakeShareable(AssetAction)); + + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + { + PropertyModule.RegisterCustomPropertyTypeLayout("PerSkeletonAnimationSharingSetup", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPerSkeletonAnimationSharingSetupCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("AnimationStateEntry", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAnimationStateEntryCustomization::MakeInstance)); + + PropertyModule.RegisterCustomPropertyTypeLayout("AnimationSetup", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAnimationSetupCustomization::MakeInstance)); + } +} + +void FAnimSharingEdModule::ShutdownModule() +{ + if (UObjectInitialized()) + { + FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule(); + IAssetTools& AssetTools = AssetToolsModule.Get(); + AssetTools.UnregisterAssetTypeActions(AssetAction->AsShared()); + + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + { + PropertyModule.UnregisterCustomPropertyTypeLayout("PerSkeletonAnimationSharingSetup"); + PropertyModule.UnregisterCustomPropertyTypeLayout("AnimationStateEntry"); + PropertyModule.UnregisterCustomPropertyTypeLayout("AnimationSetup"); + } + } +} + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp new file mode 100644 index 000000000000..7186c5fd6f24 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp @@ -0,0 +1,22 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationSharingSetupFactory.h" +#include "AnimationSharingSetup.h" +#include "AssetTypeCategories.h" + +UAnimationSharingSetupFactory::UAnimationSharingSetupFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAnimationSharingSetup::StaticClass(); + bCreateNew = true; +} + +UObject* UAnimationSharingSetupFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} + +uint32 UAnimationSharingSetupFactory::GetMenuCategories() const +{ + return EAssetTypeCategories::Animation; +} diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp new file mode 100644 index 000000000000..9a57bbd8db28 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp @@ -0,0 +1,379 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SetupDetailsViewCustomizations.h" +#include "IDetailChildrenBuilder.h" +#include "EditorStyleSet.h" +#include "UObject/UObjectGlobals.h" +#include "Widgets/Input/STextComboBox.h" +#include "AnimationSharingTypes.h" + +#define LOCTEXT_NAMESPACE "AnimationSharingSetupCustomization" + +TSharedRef FPerSkeletonAnimationSharingSetupCustomization::MakeInstance() +{ + return MakeShareable(new FPerSkeletonAnimationSharingSetupCustomization()); +} + +void FPerSkeletonAnimationSharingSetupCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + SkeletonPropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, Skeleton)); + if (SkeletonPropertyHandle.IsValid()) + { + HeaderRow + .NameContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + .VAlign(VAlign_Center) + [ + // Show the name of the asset or actor + SNew(STextBlock) + .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont")) + .Text(this, &FPerSkeletonAnimationSharingSetupCustomization::GetSkeletonName) + ] + ]; + } +} + +void FPerSkeletonAnimationSharingSetupCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + uint32 NumChildren; + StructPropertyHandle->GetNumChildren(NumChildren); + + const TArray SkeletonDisabledProperties = + { + GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, SkeletalMesh), + GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, StateProcessorClass), + GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, BlendAnimBlueprint), + GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AdditiveAnimBlueprint) + }; + + const TArray EnumDisabledProperties = + { + GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AnimationStates) + }; + + TSharedPtr ProcessorProperty = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, + StateProcessorClass)); + + void* StructPtr = nullptr; + StructPropertyHandle->GetValueData(StructPtr); + + for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) + { + TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); + IDetailPropertyRow& Property = StructBuilder.AddProperty(ChildHandle); + + const FName& PropertyName = ChildHandle->GetProperty()->GetFName(); + + /** Properties disabled by an invalid USkeleton */ + if (SkeletonDisabledProperties.Contains(PropertyName)) + { + Property.IsEnabled(TAttribute::Create([this]() -> bool + { + UObject* Object = nullptr; + return (SkeletonPropertyHandle->GetValue(Object) == FPropertyAccess::Success) && (Object != nullptr); + })); + } + + /** Properties disabled by invalid UEnum class */ + if (EnumDisabledProperties.Contains(PropertyName)) + { + Property.IsEnabled(TAttribute::Create([ProcessorProperty]() -> bool + { + const UEnum* EnumClass = GetStateEnumClass(ProcessorProperty); + return (EnumClass != nullptr); + })); + } + + /** Disable additive Anim BP property if there aren't any additive states in the setup */ + if (StructPtr) + { + if (PropertyName == GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AdditiveAnimBlueprint)) + { + Property.IsEnabled(TAttribute::Create([StructPtr]() -> bool + { + FPerSkeletonAnimationSharingSetup* SetupStruct = (FPerSkeletonAnimationSharingSetup*)StructPtr; + if (SetupStruct) + { + const bool bContainsAdditive = SetupStruct->AnimationStates.ContainsByPredicate([](FAnimationStateEntry& Entry) -> bool + { + return Entry.bAdditive; + }); + + return bContainsAdditive; + } + + return false; + })); + } + } + } +} + +FText FPerSkeletonAnimationSharingSetupCustomization::GetSkeletonName() const +{ + UObject* SkeletonObject; + FPropertyAccess::Result Result = SkeletonPropertyHandle->GetValue(SkeletonObject); + + FText Name = LOCTEXT("None", "None"); + if (Result == FPropertyAccess::Success) + { + if (SkeletonObject) + { + Name = FText::AsCultureInvariant(SkeletonObject->GetName()); + } + } + + return Name; +} + +TSharedRef FAnimationStateEntryCustomization::MakeInstance() +{ + return MakeShareable(new FAnimationStateEntryCustomization()); +} + +void FAnimationStateEntryCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + TSharedPtr ParentHandle = PropertyHandle->GetParentHandle()->GetParentHandle(); + + // We make the assumption here that the parent handle is the array part of the FPerSkeletonAnimationSharingSetup + ProcessorPropertyHandle = ParentHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, StateProcessorClass)); + + StatePropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, State)); + if (StatePropertyHandle.IsValid()) + { + HeaderRow + .NameContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + .VAlign(VAlign_Center) + [ + // Show the name of the asset or actor + SNew(STextBlock) + .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont")) + .Text(this, &FAnimationStateEntryCustomization::GetStateName, StatePropertyHandle) + ] + ]; + } +} + +void FAnimationStateEntryCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + uint32 NumChildren; + StructPropertyHandle->GetNumChildren(NumChildren); + + const TArray OnDemandProperties = { GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bReturnToPreviousState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bSetNextState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, NextState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, WiggleTimePercentage) }; + + const TArray EnumProperties = { GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, State), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, NextState) }; + + TSharedPtr OnDemandHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bOnDemand)); + TSharedPtr AdditiveHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bAdditive)); + + for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) + { + TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); + + // Hide any on-demand settings when either the state is not an on-demand or it is but an additive as well + TAttribute VisibilityAttribute = TAttribute::Create([OnDemandHandle, AdditiveHandle]() -> EVisibility + { + if (OnDemandHandle.IsValid() && AdditiveHandle.IsValid()) + { + bool bOnDemandValue = false; + OnDemandHandle->GetValue(bOnDemandValue); + + bool bAdditiveValue = false; + AdditiveHandle->GetValue(bAdditiveValue); + + return bOnDemandValue && !bAdditiveValue ? EVisibility::Visible : EVisibility::Collapsed; + } + + return EVisibility::Visible; + }); + + if (EnumProperties.Contains(ChildHandle->GetProperty()->GetFName())) + { + FDetailWidgetRow& WidgetRow = CreateEnumSelectionWidget(ChildHandle, StructBuilder); + if (OnDemandProperties.Contains(ChildHandle->GetProperty()->GetFName())) + { + WidgetRow.Visibility(VisibilityAttribute); + } + } + else + { + IDetailPropertyRow& PropertyRow = StructBuilder.AddProperty(ChildHandle); + if (OnDemandProperties.Contains(ChildHandle->GetProperty()->GetFName())) + { + PropertyRow.Visibility(VisibilityAttribute); + } + } + } +} + +FText FAnimationStateEntryCustomization::GetStateName(TSharedPtr PropertyHandle) const +{ + uint8 EnumValue; + FPropertyAccess::Result Result = PropertyHandle->GetValue(EnumValue); + + FText Name = LOCTEXT("None", "None"); + if (Result == FPropertyAccess::Success) + { + if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle)) + { + Name = EnumClass->GetDisplayNameTextByIndex(EnumValue); + } + else + { + FFormatNamedArguments Args; + Args.Add(TEXT("EnumIndex"), EnumValue); + Name = FText::Format(LOCTEXT("EnumIndexValue", "Enum Index {EnumIndex}"), Args); + } + } + + return Name; +} + +FDetailWidgetRow& FAnimationStateEntryCustomization::CreateEnumSelectionWidget(TSharedRef ChildHandle, IDetailChildrenBuilder& StructBuilder) +{ + GenerateEnumComboBoxItems(); + + TSharedPtr CurrentlySelected = GetSelectedEnum(ChildHandle); + + FDetailWidgetRow& DetailRow = StructBuilder.AddCustomRow(LOCTEXT("EnumStateSearchLabel", "State")) + .NameContent() + [ + ChildHandle->CreatePropertyNameWidget() + ] + .ValueContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + [ + SNew(STextComboBox) + .OptionsSource(&ComboBoxItems) + .InitiallySelectedItem(CurrentlySelected) + .OnSelectionChanged(this, &FAnimationStateEntryCustomization::SelectedEnumChanged, ChildHandle) + .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont")) + ] + ]; + + return DetailRow; +} + +const TArray> FAnimationStateEntryCustomization::GetComboBoxSourceItems() const +{ + TArray> Items; + + if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle)) + { + const int32 NumEnums = EnumClass->NumEnums(); + for (int32 Index = 0; Index < NumEnums; ++Index) + { + Items.Add(MakeShareable(new FString(EnumClass->GetDisplayNameTextByIndex(Index).ToString()))); + } + } + + return Items; +} + +const TSharedPtr FAnimationStateEntryCustomization::GetSelectedEnum(TSharedPtr PropertyHandle) const +{ + const TSharedPtr* StringPtr = ComboBoxItems.FindByPredicate([this, PropertyHandle](TSharedPtr SharedString) { + return *SharedString == GetStateName(PropertyHandle).ToString(); + }); + + return StringPtr != nullptr ? *StringPtr : MakeShareable(new FString(GetStateName(PropertyHandle).ToString())); +} + +void FAnimationStateEntryCustomization::SelectedEnumChanged(TSharedPtr Selection, ESelectInfo::Type SelectInfo, TSharedRef PropertyHandle) +{ + if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle)) + { + if (Selection && SelectInfo != ESelectInfo::Type::Direct) + { + uint8 NewEnumValue = EnumClass->GetValueByIndex(ComboBoxItems.IndexOfByKey(Selection)); + PropertyHandle->SetValue(NewEnumValue); + } + + } +} + +void FAnimationStateEntryCustomization::GenerateEnumComboBoxItems() +{ + if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle)) + { + if (EnumClass != CachedComboBoxEnumClass) + { + ComboBoxItems.Empty(); + const int32 NumEnums = EnumClass->NumEnums(); + for (int32 Index = 0; Index < NumEnums; ++Index) + { + ComboBoxItems.Add(MakeShareable(new FString(EnumClass->GetDisplayNameTextByIndex(Index).ToString()))); + } + + CachedComboBoxEnumClass = EnumClass; + } + } +} + +UEnum* GetStateEnumClass(const TSharedPtr& InProperty) +{ + UObject* EnumObject = nullptr; + if (InProperty.IsValid()) + { + UObject* ProcessorObject = nullptr; + InProperty->GetValue(ProcessorObject); + UClass* ProcessorClass = Cast(ProcessorObject); + if (ProcessorClass) + { + UAnimationSharingStateProcessor* Processor = ProcessorClass->GetDefaultObject(); + return Processor->GetAnimationStateEnum(); + } + } + + return nullptr; +} + + +TSharedRef FAnimationSetupCustomization::MakeInstance() +{ + return MakeShareable(new FAnimationSetupCustomization()); +} + +void FAnimationSetupCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + uint32 NumChildren; + StructPropertyHandle->GetNumChildren(NumChildren); + const FName AnimSequencePropertyName = GET_MEMBER_NAME_CHECKED(FAnimationSetup, AnimSequence); + AnimSequencePropertyHandle = StructPropertyHandle->GetChildHandle(AnimSequencePropertyName); + + for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) + { + TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); + IDetailPropertyRow& Property = StructBuilder.AddProperty(ChildHandle); + + /** Disable all properties if there is not a valid Animation Sequence provided */ + if (AnimSequencePropertyHandle.IsValid() && (ChildHandle->GetProperty()->GetFName() != AnimSequencePropertyName)) + { + Property.IsEnabled(TAttribute::Create([this]() -> bool + { + if ( AnimSequencePropertyHandle.Get()) + { + UObject* ObjectPtr = nullptr; + if (AnimSequencePropertyHandle->GetValue(ObjectPtr) == FPropertyAccess::Success) + { + return ObjectPtr != nullptr; + } + } + + return false; + })); + } + } +} + +#undef LOCTEXT_NAMESPACE // "AnimationSharingSetupCustomization" + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h new file mode 100644 index 000000000000..a4b63d179e7b --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h @@ -0,0 +1,22 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +/** +* The public interface to this module +*/ +class FAnimSharingEdModule : public IModuleInterface +{ +public: + FAnimSharingEdModule() {} + + virtual void StartupModule(); + virtual void ShutdownModule(); +private: + class FAssetTypeActions_AnimationSharingSetup* AssetAction; +}; + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h new file mode 100644 index 000000000000..4577dafea068 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h @@ -0,0 +1,25 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Factories/Factory.h" +#include "AnimationSharingSetupFactory.generated.h" + +struct FAssetData; +class SWindow; + +UCLASS(hidecategories = Object, MinimalAPI) +class UAnimationSharingSetupFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual uint32 GetMenuCategories() const; + //~ Begin UFactory Interface +}; + + + diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h new file mode 100644 index 000000000000..bec0bfc20627 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimationSharingManager.h" +#include "AssetTypeCategories.h" +#include "AssetTypeActions_Base.h" + +class FAssetTypeActions_AnimationSharingSetup : public FAssetTypeActions_Base +{ +public: + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimationSharingSetup", "AnimationSharingSetup"); } + virtual FColor GetTypeColor() const override { return FColor(255, 10, 255); } + virtual UClass* GetSupportedClass() const override { return UAnimationSharingManager::StaticClass(); } + virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; } +}; diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h new file mode 100644 index 000000000000..60fdb9f3a9a0 --- /dev/null +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h @@ -0,0 +1,74 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" + +static UEnum* GetStateEnumClass(const TSharedPtr& InProperty); + +class FPerSkeletonAnimationSharingSetupCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + FPerSkeletonAnimationSharingSetupCustomization() {} + virtual ~FPerSkeletonAnimationSharingSetupCustomization() {} + + /** Begin IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + /** End IPropertyTypeCustomization interface */ + +protected: + FText GetSkeletonName() const; + + TSharedPtr SkeletonPropertyHandle; +}; + +class FAnimationStateEntryCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + FAnimationStateEntryCustomization() : StatePropertyHandle(nullptr), ProcessorPropertyHandle(nullptr), CachedComboBoxEnumClass(nullptr) {} + virtual ~FAnimationStateEntryCustomization() {} + + /** Begin IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + /** End IPropertyTypeCustomization interface */ + +protected: + FText GetStateName(TSharedPtr PropertyHandle) const; + FDetailWidgetRow& CreateEnumSelectionWidget(TSharedRef ChildHandle, IDetailChildrenBuilder& StructBuilder); + const TArray> GetComboBoxSourceItems() const; + const TSharedPtr GetSelectedEnum(TSharedPtr PropertyHandle) const; + void SelectedEnumChanged(TSharedPtr Selection, ESelectInfo::Type SelectInfo, TSharedRef PropertyHandle); + void GenerateEnumComboBoxItems(); + +protected: + TSharedPtr StatePropertyHandle; + TSharedPtr ProcessorPropertyHandle; + + UEnum* CachedComboBoxEnumClass; + TArray> ComboBoxItems; +}; + +class FAnimationSetupCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + FAnimationSetupCustomization() {} + virtual ~FAnimationSetupCustomization() {} + + /** Begin IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override {} + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + /** End IPropertyTypeCustomization interface */ + +protected: + TSharedPtr AnimSequencePropertyHandle; +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp index 7a4464bd0035..6949cbeb8d56 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp @@ -88,8 +88,13 @@ void SAlembicImportOptions::Construct(const FArguments& InArgs) SNew(SHeaderRow) + SHeaderRow::Column("ShouldImport") - .DefaultLabel(FText::FromString(TEXT("Include"))) .FillWidth(0.1f) + .DefaultLabel(FText::FromString(TEXT("Include"))) + [ + SNew(SCheckBox) + .HAlign(HAlign_Center) + .OnCheckStateChanged(this, &SAlembicImportOptions::OnToggleAllItems) + ] + SHeaderRow::Column("TrackName") .DefaultLabel(LOCTEXT("TrackNameHeader", "Track Name")) @@ -157,11 +162,24 @@ bool SAlembicImportOptions::CanImport() const return true; } -void SAlembicImportOptions::OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem) +void SAlembicImportOptions::OnToggleAllItems(ECheckBoxState CheckType) { + /** Set all items to top level checkbox state */ for (FPolyMeshDataPtr& Item : PolyMeshData) { - Item->PolyMesh->bShouldImport = (Item == ClickedItem); + Item->PolyMesh->bShouldImport = CheckType == ECheckBoxState::Checked; + } +} + +void SAlembicImportOptions::OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem) +{ + /** Toggle state on / off for the selected list entry */ + for (FPolyMeshDataPtr& Item : PolyMeshData) + { + if (Item == ClickedItem) + { + Item->PolyMesh->bShouldImport = !Item->PolyMesh->bShouldImport; + } } } diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h index 01080bba276f..240b70ba745d 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h @@ -86,6 +86,7 @@ private: TSharedRef OnGenerateWidgetForList(FPolyMeshDataPtr InItem, const TSharedRef& OwnerTable); bool CanImport() const; + void OnToggleAllItems(ECheckBoxState CheckType); void OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem); private: UAbcImportSettings* ImportSettings; diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp index 5faaf061ef69..fc280beef002 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp @@ -833,6 +833,9 @@ void FControlRigEditor::SetJointTransform(const FName& InJoint, const FTransform EditorSkelComp->RebuildDebugDrawSkeleton(); } + // I don't think I have to mark dirty here. + // FBlueprintEditorUtils::MarkBlueprintAsModified(GetControlRigBlueprint()); + // I don't think I have to mark dirty here. // FBlueprintEditorUtils::MarkBlueprintAsModified(GetControlRigBlueprint()); { diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp new file mode 100644 index 000000000000..603d5926bc9b --- /dev/null +++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimationBudgetBlueprintLibrary.h" +#include "AnimationBudgetAllocatorModule.h" +#include "Modules/ModuleManager.h" +#include "IAnimationBudgetAllocator.h" +#include "Engine/Engine.h" + +void UAnimationBudgetBlueprintLibrary::EnableAnimationBudget(UObject* WorldContextObject, bool bEnabled) +{ + if(UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + FAnimationBudgetAllocatorModule& AnimationBudgetAllocatorModule = FModuleManager::LoadModuleChecked("AnimationBudgetAllocator"); + IAnimationBudgetAllocator* AnimationBudgetAllocator = AnimationBudgetAllocatorModule.GetBudgetAllocatorForWorld(World); + if(AnimationBudgetAllocator) + { + AnimationBudgetAllocator->SetEnabled(bEnabled); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h new file mode 100644 index 000000000000..06487547bb38 --- /dev/null +++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h @@ -0,0 +1,23 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AnimationBudgetBlueprintLibrary.generated.h" + +/** + * Function library to expose the budget allocator to Blueprints + */ +UCLASS(meta = (ScriptName = "Animation Budget")) +class UAnimationBudgetBlueprintLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + + /** + * Enable/disable the animation budgeting system. + * Note that the system can also be disabled 'globally' via CVar, which overrides this setting. + */ + UFUNCTION(BlueprintCallable, Category = "Animation Budget", meta=(WorldContext="WorldContextObject")) + static void EnableAnimationBudget(UObject* WorldContextObject, bool bEnabled); +}; \ No newline at end of file diff --git a/Engine/Source/Developer/AssetTools/AssetTools.Build.cs b/Engine/Source/Developer/AssetTools/AssetTools.Build.cs index 57749b00489f..581e4aab21da 100644 --- a/Engine/Source/Developer/AssetTools/AssetTools.Build.cs +++ b/Engine/Source/Developer/AssetTools/AssetTools.Build.cs @@ -59,6 +59,7 @@ public class AssetTools : ModuleRules "SkeletalMeshEditor", "AnimationEditor", "AnimationBlueprintEditor", + "AnimationModifiers" } ); @@ -83,6 +84,7 @@ public class AssetTools : ModuleRules "SkeletalMeshEditor", "AnimationEditor", "AnimationBlueprintEditor", + "AnimationModifiers" } ); } diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp index bde27ea3951c..9a675c57fd47 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp @@ -38,6 +38,7 @@ #include "AssetTypeActions/AssetTypeActions_AnimationAsset.h" #include "AssetTypeActions/AssetTypeActions_AnimBlueprint.h" #include "AssetTypeActions/AssetTypeActions_AnimComposite.h" +#include "AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h" #include "AssetTypeActions/AssetTypeActions_AnimMontage.h" #include "AssetTypeActions/AssetTypeActions_AnimSequence.h" #include "AssetTypeActions/AssetTypeActions_BlendSpace.h" @@ -162,6 +163,7 @@ UAssetToolsImpl::UAssetToolsImpl(const FObjectInitializer& ObjectInitializer) RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimationAsset)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimBlueprint)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimComposite)); + RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimCurveCompressionSettings)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimMontage)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimSequence)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AimOffset)); diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp new file mode 100644 index 000000000000..4b9adac4ae89 --- /dev/null +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp @@ -0,0 +1,110 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h" +#include "Animation/AnimSequence.h" +#include "Dialogs/Dialogs.h" +#include "EditorStyleSet.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Misc/ScopedSlowTask.h" +#include "UObject/UObjectIterator.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +void FAssetTypeActions_AnimCurveCompressionSettings::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + TSharedRef AssetEditor = FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); + + auto SettingAssets = GetTypedWeakObjectPtrs(InObjects); + if (SettingAssets.Num() == 1) + { + TSharedPtr PluginCommands = MakeShareable(new FUICommandList); + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension, SettingAssets[0])); + AssetEditor->AddToolbarExtender(ToolbarExtender); + + AssetEditor->RegenerateMenusAndToolbars(); + } +} + +void FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr CurveSettings) +{ + Builder.BeginSection("Compress"); + Builder.AddToolBarButton( + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, CurveSettings) + ), + NAME_None, + LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"), + LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression") + ); + Builder.EndSection(); +} + +void FAssetTypeActions_AnimCurveCompressionSettings::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) +{ + auto SettingAssets = GetTypedWeakObjectPtrs(InObjects); + + if (SettingAssets.Num() != 1) + { + return; + } + + MenuBuilder.AddMenuEntry( + LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"), + LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression.Small"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, SettingAssets[0]) + ) + ); +} + +void FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression(TWeakObjectPtr CurveSettings) +{ + if (!CurveSettings.IsValid()) + { + return; + } + + UAnimCurveCompressionSettings* Settings = CurveSettings.Get(); + + TArray AnimSeqsToRecompress; + for (TObjectIterator It; It; ++It) + { + UAnimSequence* AnimSeq = *It; + if (AnimSeq->GetOutermost() == GetTransientPackage()) + { + continue; + } + + if (AnimSeq->CurveCompressionSettings == Settings) + { + AnimSeqsToRecompress.Add(AnimSeq); + } + } + + if (AnimSeqsToRecompress.Num() == 0) + { + return; + } + + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("NumAnimSequences"), FText::AsNumber(AnimSeqsToRecompress.Num())); + FText DialogText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_CompressWarningText", "{NumAnimSequences} animation sequences are about to compress."), Arguments); + const EAppReturnType::Type DlgResult = OpenMsgDlgInt(EAppMsgType::OkCancel, DialogText, LOCTEXT("AnimCurveCompressionSettings_CompressWarning", "Warning")); + if (DlgResult != EAppReturnType::Ok) + { + return; + } + + const FText StatusText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_Compressing", "Compressing '{0}' animations"), FText::AsNumber(AnimSeqsToRecompress.Num())); + FScopedSlowTask LoadingAnimSlowTask(AnimSeqsToRecompress.Num(), StatusText); + LoadingAnimSlowTask.MakeDialog(); + + for (UAnimSequence* AnimSeq : AnimSeqsToRecompress) + { + LoadingAnimSlowTask.EnterProgressFrame(); + AnimSeq->RequestSyncAnimRecompression(false); + } +} diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h new file mode 100644 index 000000000000..fef0cd3247fb --- /dev/null +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h @@ -0,0 +1,27 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "Animation/AnimCurveCompressionSettings.h" + +class FAssetTypeActions_AnimCurveCompressionSettings : public FAssetTypeActions_Base +{ +public: + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimCurveCompressionSettings", "Curve Compression Settings"); } + virtual FColor GetTypeColor() const override { return FColor(255, 255, 0); } + virtual UClass* GetSupportedClass() const override { return UAnimCurveCompressionSettings::StaticClass(); } + virtual bool CanFilter() override { return true; } + virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; } + + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + + virtual bool HasActions(const TArray& InObjects) const override { return true; } + virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; + +private: + void AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr CurveSettings); + void ExecuteCompression(TWeakObjectPtr CurveSettings); +}; diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp index e444698e0d5a..7854b01a1f6c 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp @@ -15,7 +15,8 @@ #include "AssetTools.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" - +#include "IAnimationModifiersModule.h" +#include "Algo/Transform.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" @@ -44,6 +45,13 @@ void FAssetTypeActions_AnimSequence::GetActions( const TArray& InObjec FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteReimportWithNewSource, Sequences)) ); + MenuBuilder.AddMenuEntry( + LOCTEXT("AnimSequence_AddAnimationModifier", "Add Animation Modifier(s)"), + LOCTEXT("AnimSequence_AddAnimationModifierTooltip", "Apply new animation modifier(s)."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimationModifier"), + FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteAddNewAnimationModifier, Sequences)) + ); + FAssetTypeActions_AnimationAsset::GetActions(InObjects, MenuBuilder); } @@ -137,6 +145,26 @@ void FAssetTypeActions_AnimSequence::ExecuteNewPoseAsset(TArray> Objects) +{ + TArray AnimSequences; + + Algo::TransformIf(Objects, AnimSequences, + [](const TWeakObjectPtr& WeakAnimSequence) + { + return WeakAnimSequence.Get() && WeakAnimSequence->IsA(); + }, + [](const TWeakObjectPtr& WeakAnimSequence) + { + return WeakAnimSequence.Get(); + }); + + if (IAnimationModifiersModule* Module = FModuleManager::Get().LoadModulePtr("AnimationModifiers")) + { + Module->ShowAddAnimationModifierWindow(AnimSequences); + } +} + bool FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimComposite(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const { UAnimCompositeFactory* CompositeFactory = CastChecked(AssetFactory); diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h index e2f214d40a2f..b6f760d762b5 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h @@ -38,6 +38,9 @@ private: /** Handler for when Create Pose Asset is selected */ void ExecuteNewPoseAsset(TArray> Objects) const; + /** Handler for when Add Animation Modifier is selected */ + void ExecuteAddNewAnimationModifier(TArray> Objects); + /** Delegate handler passed to CreateAnimationAssets when creating AnimComposites */ bool ConfigureFactoryForAnimComposite(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const; diff --git a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs index 03968f2bd787..5dce24d4e223 100644 --- a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs +++ b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs @@ -27,20 +27,6 @@ namespace UnrealBuildTool.Rules AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer"); AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib"); AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction"); - - if (Target.bCompileSimplygon == true) - { - AddEngineThirdPartyPrivateDynamicDependencies(Target, "SimplygonMeshReduction"); - - if (Target.bCompileSimplygonSSF == true) - { - DynamicallyLoadedModuleNames.AddRange( - new string[] { - "SimplygonSwarm" - } - ); - } - } } } } diff --git a/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs b/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs index e91a9436d4bf..78897c3716c7 100644 --- a/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs +++ b/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs @@ -75,20 +75,6 @@ public class MeshUtilities : ModuleRules AddEngineThirdPartyPrivateStaticDependencies(Target, "DX9"); } - if (Target.bCompileSimplygon == true) - { - AddEngineThirdPartyPrivateDynamicDependencies(Target, "SimplygonMeshReduction"); - - if (Target.bCompileSimplygonSSF == true) - { - DynamicallyLoadedModuleNames.AddRange( - new string[] { - "SimplygonSwarm" - } - ); - } - } - // EMBREE if (Target.Platform == UnrealTargetPlatform.Win64) { diff --git a/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp b/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp deleted file mode 100644 index c5c2d7002590..000000000000 --- a/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp +++ /dev/null @@ -1,3179 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "CoreMinimal.h" -#include "IMeshReductionInterfaces.h" -#include "MeshUtilities.h" -#include "MaterialUtilities.h" -#include "SimplygonTypes.h" -#include "StaticMeshResources.h" -#include "Components/SplineMeshComponent.h" -#include "SimplygonSDK.h" -#include "ProfilingDebugging/ScopedTimers.h" -#include "Misc/ScopedSlowTask.h" -#include "Modules/ModuleManager.h" -#include "HAL/Runnable.h" -#include "HAL/RunnableThread.h" -#include "Engine/SkeletalMesh.h" -#include "Misc/CommandLine.h" -#include "Misc/Paths.h" -#include "Misc/FileHelper.h" -#include "Components/SkinnedMeshComponent.h" -#include "Templates/UniquePtr.h" -#include "Features/IModularFeatures.h" -#include "Rendering/SkeletalMeshModel.h" -#include "AnimationBlueprintLibrary.h" -#include "AnimationRuntime.h" - -#include "MeshMergeData.h" -#include "Rendering/MultiSizeIndexContainer.h" - -#include "MeshDescription.h" -#include "MeshAttributes.h" -#include "MeshAttributeArray.h" -#include "MeshDescriptionOperations.h" - -// Standard Simplygon channels have some issues with extracting color data back from simplification, -// so we use this workaround with user channels -static const char* USER_MATERIAL_CHANNEL_METALLIC = "UserMetallic"; -static const char* USER_MATERIAL_CHANNEL_ROUGHNESS = "UserRoughness"; -static const char* USER_MATERIAL_CHANNEL_SPECULAR = "UserSpecular"; - -static const TCHAR* SG_UE_INTEGRATION_REV = TEXT("@305"); - -#ifdef __clang__ - // SimplygonSDK.h uses 'deprecated' pragma which Clang does not recognize - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown pragma ignored [-Wunknown-pragmas] -#endif - -#include "SimplygonSDK.h" - -#ifdef __clang__ - #pragma clang diagnostic pop -#endif - -#include "MeshBoneReduction.h" -#include "ComponentReregisterContext.h" -//@third party code BEGIN SIMPLYGON -#include "ImageUtils.h" - -// Notes about IChartAggregator: -// - Available since Simplygon 7.0 (defined(SIMPLYGONSDK_VERSION) && SIMPLYGONSDK_VERSION >= 0x700). -// - Use of pure IChartAggregator will re-introduce bugs with UVs outside of 0..1 range, so UVs should -// be scaled. -// - IChartAggregator probably needs more settings to be provided, because it will grow number of mesh -// vertices a lot (up to 3x of mesh face count). -#define USE_SIMPLYGON_CHART_AGGREGATOR 0 - -// Use Engine's FLayoutUV class to generate non-overlapping texture coordinates. If disabled, Simplygon -// will be used. -#define USE_FLAYOUT_UV 0 - -//#define DEBUG_PROXY_MESH - -#define LOCTEXT_NAMESPACE "SimplygonMeshReduction" - -class FSimplygonMeshReductionModule : public IMeshReductionModule -{ -public: - // IModuleInterface interface. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IMeshReductionModule interface. - virtual class IMeshReduction* GetStaticMeshReductionInterface() override; - virtual class IMeshReduction* GetSkeletalMeshReductionInterface() override; - virtual class IMeshMerging* GetMeshMergingInterface() override; - virtual class IMeshMerging* GetDistributedMeshMergingInterface() override; - virtual FString GetName() override; -}; - -DEFINE_LOG_CATEGORY_STATIC(LogSimplygon, Log, All); -IMPLEMENT_MODULE(FSimplygonMeshReductionModule, SimplygonMeshReduction); - -#define SIMPLYGON_COLOR_CHANNEL "VertexColors" - -/** Receives error messages generated by Simplygon. These errors are presented via a message box. */ -class FDefaultErrorHandler : public SimplygonSDK::rerrorhandler -{ -public : - virtual void HandleError( - SimplygonSDK::IObject* Object, - const char* InterfaceName, - const char* MethodName, - SimplygonSDK::rid ErrorType, - const char* ErrorText ) - { - FString ErrorString = FString::Printf(TEXT("Simplygon Error:\n\nInterface: %s\nMethod: %s\nError: (%d) %s"), - ANSI_TO_TCHAR(InterfaceName), - ANSI_TO_TCHAR(MethodName), - ErrorType, - ANSI_TO_TCHAR(ErrorText) - ); - UE_LOG(LogSimplygon, Log, TEXT("%s"), *ErrorString); - //FMessageDialog::Open(EAppMsgType::Ok, *ErrorString); - } -}; - -/** Receives progress events from Simplygon and updates the status window. */ -class FDefaultEventHandler : public SimplygonSDK::robserver -{ -public: - - FDefaultEventHandler() : Task(nullptr), PreviousProgress( 0 ) {} - FScopedSlowTask* Task; - - int32 PreviousProgress; - - virtual void Execute( - SimplygonSDK::IObject* Object, - SimplygonSDK::rid EventId, - void* EventParameterBlock, - unsigned int EventParameterBlockSize ) - { - if ( EventId == SimplygonSDK::SG_EVENT_PROGRESS ) - { - check( sizeof(int32) == EventParameterBlockSize ); - int32 ProgressPercent = *((int32*)EventParameterBlock); - - if (IsInGameThread()) - { - if ( Task != nullptr) - { - // @todo: jurre this is temporary fix for UE-48222 - //Task->EnterProgressFrame(ProgressPercent - PreviousProgress); - PreviousProgress = ProgressPercent; - } - } - - // We are required to pass '1' back through the EventParametersBlock for the process to continue. - *((int32*)EventParameterBlock) = 1; - } - } -}; - -/** Winding modes. */ -enum EWindingMode -{ - /** Maintain the winding of the mesh. */ - WINDING_Keep, - /** Reverse the winding of the mesh. */ - WINDING_Reverse, - WINDING_MAX -}; - -static void* GSimplygonSDKDLLHandle = nullptr; - -class FSimplygonMeshReduction - : public IMeshReduction - , public IMeshMerging -{ -public: - virtual ~FSimplygonMeshReduction() - { - } - - virtual const FString& GetVersionString() const override - { - return VersionString; - } - - virtual FString GetName() override - { - return FString("SimplygonMeshReduction"); - } - - bool ReduceLODModel( - const FSkeletalMeshLODModel * SrcModel, - FSkeletalMeshLODModel *& OutModel, - const FBoxSphereBounds & Bounds, - float &MaxDeviation, - const FReferenceSkeleton& RefSkeleton, - const FSkeletalMeshOptimizationSettings& Settings, - const TArray& BoneMatrices, - const int32 LODIndex - ) - { - const bool bUsingMaxDeviation = (Settings.ReductionMethod != SMOT_NumOfTriangles && Settings.MaxDeviationPercentage > 0.0f); - const bool bUsingReductionRatio = (Settings.ReductionMethod != SMOT_MaxDeviation && Settings.NumOfTrianglesPercentage < 1.0f); - const bool bProcessGeometry = ( bUsingMaxDeviation || bUsingReductionRatio ); - const bool bProcessBones = (Settings.MaxBonesPerVertex < MAX_TOTAL_INFLUENCES); - const bool bOptimizeMesh = (bProcessGeometry || bProcessBones); - - // We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance - MaxDeviation = 0.0f; - - if ( bOptimizeMesh ) - { - //Create a simplygon scene. The scene by default contains a bone table that is required for bone reduction. - SimplygonSDK::spScene Scene = SDK->CreateScene(); - check( Scene ); - - // Create bone hierarchy for simplygon. - TArray BoneTableIDs; - CreateSkeletalHierarchy(Scene, RefSkeleton, BoneTableIDs); - - // Create a new scene mesh object - SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromSkeletalLODModel( Scene, *SrcModel, BoneTableIDs, BoneMatrices, LODIndex); - - FDefaultEventHandler SimplygonEventHandler; - SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor(); - ReductionProcessor->AddObserver( &EventHandler, SimplygonSDK::SG_EVENT_PROGRESS ); - ReductionProcessor->SetScene(Scene); - - SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings(); - RepairSettings->SetWeldDist( Settings.WeldingThreshold ); - RepairSettings->SetTjuncDist( Settings.WeldingThreshold ); - - SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings(); - SetReductionSettings( Settings, Bounds.SphereRadius, GeometryData->GetTriangleCount(), ReductionSettings ); - - SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings(); - SetNormalSettings( Settings, NormalSettings ); - - if(bProcessBones) - { - SimplygonSDK::spBoneSettings BoneSettings = ReductionProcessor->GetBoneSettings(); - SetBoneSettings( Settings, BoneSettings ); - } - - ReductionProcessor->RunProcessing(); - - // We require the max deviation if we're calculating the LOD's display distance. - MaxDeviation = ReductionProcessor->GetResultDeviation(); - CreateSkeletalLODModelFromGeometry(GeometryData, RefSkeleton, OutModel); - // return true if it created any vertice - return (OutModel->NumVertices > 0); - } - - return false; - } - - bool RemoveMeshSection(FSkeletalMeshLODModel* Model, int32 SectionIndex) - { - // Need a valid section - if (!Model->Sections.IsValidIndex(SectionIndex)) - { - return false; - } - - FSkelMeshSection& SectionToRemove = Model->Sections[SectionIndex]; - - if (SectionToRemove.CorrespondClothAssetIndex != INDEX_NONE) - { - // Can't remove this, clothing currently relies on it - return false; - } - - const uint32 NumVertsToRemove = SectionToRemove.GetNumVertices(); - const uint32 BaseVertToRemove = SectionToRemove.BaseVertexIndex; - const uint32 NumIndicesToRemove = SectionToRemove.NumTriangles * 3; - const uint32 BaseIndexToRemove = SectionToRemove.BaseIndex; - - - // Strip indices - Model->IndexBuffer.RemoveAt(BaseIndexToRemove, NumIndicesToRemove); - - Model->Sections.RemoveAt(SectionIndex); - - // Fixup indices above base vert - for (uint32& Index : Model->IndexBuffer) - { - if (Index >= BaseVertToRemove) - { - Index -= NumVertsToRemove; - } - } - - Model->NumVertices -= NumVertsToRemove; - - // Fixup anything needing section indices - for (FSkelMeshSection& Section : Model->Sections) - { - // Push back clothing indices - if (Section.CorrespondClothAssetIndex > SectionIndex) - { - Section.CorrespondClothAssetIndex--; - } - - // Removed indices, rebase further sections - if (Section.BaseIndex > BaseIndexToRemove) - { - Section.BaseIndex -= NumIndicesToRemove; - } - - // Remove verts, rebase further sections - if (Section.BaseVertexIndex > BaseVertToRemove) - { - Section.BaseVertexIndex -= NumVertsToRemove; - } - } - return true; - } - - /** internal only access function, so that you can use with register or with no-register */ - void Reduce( - USkeletalMesh* SkeletalMesh, - FSkeletalMeshModel* SkeletalMeshResource, - int32 LODIndex - ) - { - //If the Current LOD is an import from file - bool OldLodWasFromFile = SkeletalMesh->IsValidLODIndex(LODIndex) && SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified == false; - - // Insert a new LOD model entry if needed. - if (LODIndex == SkeletalMeshResource->LODModels.Num()) - { - SkeletalMeshResource->LODModels.Add(0); - } - - // We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance - float MaxDeviation = 0.0f; - - // Swap in a new model, delete the old. - check(LODIndex < SkeletalMeshResource->LODModels.Num()); - FSkeletalMeshLODModel** LODModels = SkeletalMeshResource->LODModels.GetData(); - //delete LODModels[LODIndex]; -- keep model valid until we'll be ready to replace it; required to be able to refresh UI with mesh stats - - // Copy over LOD info from LOD0 if there is no previous info. - if (LODIndex == SkeletalMesh->GetLODNum()) - { - // if there is no LOD, add one more - SkeletalMesh->AddLODInfo(); - } - - // get settings - FSkeletalMeshOptimizationSettings& Settings = SkeletalMesh->GetLODInfo(LODIndex)->ReductionSettings; - - // select which mesh we're reducing from - // use BaseLOD - int32 BaseLOD = 0; - FSkeletalMeshModel* SkelResource = SkeletalMesh->GetImportedModel(); - FSkeletalMeshLODModel* SrcModel = &SkelResource->LODModels[0]; - - // only allow to set BaseLOD if the LOD is less than this - if (Settings.BaseLOD > 0) - { - if (Settings.BaseLOD < LODIndex && SkeletalMeshResource->LODModels.IsValidIndex(Settings.BaseLOD)) - { - BaseLOD = Settings.BaseLOD; - SrcModel = &SkeletalMeshResource->LODModels[BaseLOD]; - } - else - { - // warn users - UE_LOG(LogSimplygon, Warning, TEXT("Building LOD %d - Invalid Base LOD entered. Using Base LOD 0"), LODIndex); - } - } - - //Reducing Base LOD, we need to use the temporary data so it can be iterative - if (BaseLOD == LODIndex && SkelResource->OriginalReductionSourceMeshData.IsValidIndex(BaseLOD) && !SkelResource->OriginalReductionSourceMeshData[BaseLOD]->IsEmpty()) - { - TMap> TempLODMorphTargetData; - SkelResource->OriginalReductionSourceMeshData[BaseLOD]->LoadReductionData(*SrcModel, TempLODMorphTargetData); - } - else - { - check(BaseLOD < LODIndex); - } - - // now try bone reduction process if it's setup - TMap BonesToRemove; - - IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked("MeshBoneReduction"); - IMeshBoneReduction * MeshBoneReductionInterface = MeshBoneReductionModule.GetMeshBoneReductionInterface(); - - TArray BoneNames; - const int32 NumBones = SkeletalMesh->RefSkeleton.GetNum(); - for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) - { - BoneNames.Add(SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex)); - } - - // get the relative to ref pose matrices - TArray RelativeToRefPoseMatrices; - RelativeToRefPoseMatrices.AddUninitialized(NumBones); - // if it has bake pose, gets ref to local matrices using bake pose - if (const UAnimSequence* BakePoseAnim = SkeletalMesh->GetBakePose(LODIndex)) - { - TArray BonePoses; - UAnimationBlueprintLibrary::GetBonePosesForFrame(BakePoseAnim, BoneNames, 0, true, BonePoses, SkeletalMesh); - - const FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton; - const TArray& RefPoseInLocal = RefSkeleton.GetRefBonePose(); - - // get component ref pose - TArray RefPoseInCS; - FAnimationRuntime::FillUpComponentSpaceTransforms(RefSkeleton, RefPoseInLocal, RefPoseInCS); - - // calculate component space bake pose - TArray ComponentSpacePose, ComponentSpaceRefPose, AnimPoseMatrices; - ComponentSpacePose.AddUninitialized(NumBones); - ComponentSpaceRefPose.AddUninitialized(NumBones); - AnimPoseMatrices.AddUninitialized(NumBones); - - // to avoid scale issue, we use matrices here - for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) - { - ComponentSpaceRefPose[BoneIndex] = RefPoseInCS[BoneIndex].ToMatrixWithScale(); - AnimPoseMatrices[BoneIndex] = BonePoses[BoneIndex].ToMatrixWithScale(); - } - - for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) - { - const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); - if (ParentIndex != INDEX_NONE) - { - ComponentSpacePose[BoneIndex] = AnimPoseMatrices[BoneIndex] * ComponentSpacePose[ParentIndex]; - } - else - { - ComponentSpacePose[BoneIndex] = AnimPoseMatrices[BoneIndex]; - } - } - - // calculate relative to ref pose transform and convert to matrices - for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) - { - RelativeToRefPoseMatrices[BoneIndex] = ComponentSpaceRefPose[BoneIndex].Inverse() * ComponentSpacePose[BoneIndex]; - } - } - else - { - for (int32 Index = 0; Index < NumBones; ++Index) - { - RelativeToRefPoseMatrices[Index] = FMatrix::Identity; - } - } - - FSkeletalMeshLODModel * NewModel = new FSkeletalMeshLODModel(); - LODModels[LODIndex] = NewModel; - - // Reduce LOD model with SrcMesh - if (ReduceLODModel(SrcModel, NewModel, SkeletalMesh->GetImportedBounds(), MaxDeviation, SkeletalMesh->RefSkeleton, Settings, RelativeToRefPoseMatrices, LODIndex)) - { - // See if we'd like to remove extra bones first - if (MeshBoneReductionInterface->GetBoneReductionData(SkeletalMesh, LODIndex, BonesToRemove)) - { - // fix up chunks to remove the bones that set to be removed - for (int32 SectionIndex = 0; SectionIndex < NewModel->Sections.Num(); ++SectionIndex) - { - MeshBoneReductionInterface->FixUpSectionBoneMaps(NewModel->Sections[SectionIndex], BonesToRemove); - } - } - - if (OldLodWasFromFile) - { - SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Empty(); - } - - // If base lod has a customized LODMaterialMap and this LOD doesn't (could have if changes are applied instead of freshly generated, copy over the data into new new LOD - if (SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Num() == 0 && SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() != 0) - { - SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap; - } - else - { - // Assuming the reducing step has set all material indices correctly, we double check if something went wrong - // make sure we don't have more materials - int32 TotalSectionCount = NewModel->Sections.Num(); - if (SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Num() > TotalSectionCount) - { - SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap; - // Something went wrong during the reduce step during regenerate - check(SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() == TotalSectionCount || SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() == 0); - } - } - - // Flag this LOD as having been simplified. - SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified = true; - SkeletalMesh->bHasBeenSimplified = true; - } - else - { - // Bulk data arrays need to be locked before a copy can be made. - SrcModel->RawPointIndices.Lock(LOCK_READ_ONLY); - SrcModel->LegacyRawPointIndices.Lock(LOCK_READ_ONLY); - *NewModel = *SrcModel; - SrcModel->RawPointIndices.Unlock(); - SrcModel->LegacyRawPointIndices.Unlock(); - - // See if we'd like to remove extra bones first - if (MeshBoneReductionInterface->GetBoneReductionData(SkeletalMesh, LODIndex, BonesToRemove)) - { - // fix up chunks to remove the bones that set to be removed - for (int32 SectionIndex = 0; SectionIndex < NewModel->Sections.Num(); ++SectionIndex) - { - MeshBoneReductionInterface->FixUpSectionBoneMaps(NewModel->Sections[SectionIndex], BonesToRemove); - } - } - - //Clean up some section data - for (int32 SectionIndex = SrcModel->Sections.Num()-1; SectionIndex >= 0; --SectionIndex) - { - //New model should be reset to -1 value - NewModel->Sections[SectionIndex].GenerateUpToLodIndex = -1; - int8 GenerateUpToLodIndex = SrcModel->Sections[SectionIndex].GenerateUpToLodIndex; - if (GenerateUpToLodIndex != -1 && GenerateUpToLodIndex < LODIndex) - { - //Remove the section - RemoveMeshSection(NewModel, SectionIndex); - } - } - - SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap; - // Required bones are recalculated later on. - NewModel->RequiredBones.Empty(); - SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified = true; - SkeletalMesh->bHasBeenSimplified = true; - } - - SkeletalMesh->CalculateRequiredBones(SkeletalMeshResource->LODModels[LODIndex], SkeletalMesh->RefSkeleton, &BonesToRemove); - } - - virtual void ReduceMeshDescription( - FMeshDescription& OutReducedMesh, - float& OutMaxDeviation, - const FMeshDescription& InMesh, - const FOverlappingCorners& InOverlappingCorners, - const struct FMeshReductionSettings& InSettings - ) override - { - SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(InMesh); - check(GeometryData); - - SimplygonSDK::spScene Scene = SDK->CreateScene(); - - SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh(); - Mesh->SetGeometry(GeometryData); - Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh")))); - Scene->GetRootNode()->AddChild(Mesh); - - SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor(); - ReductionProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS); - ReductionProcessor->SetScene(Scene); - - SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings(); - RepairSettings->SetWeldDist(InSettings.WeldingThreshold); - RepairSettings->SetTjuncDist(InSettings.WeldingThreshold); - - SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings(); - SetReductionSettings(ReductionSettings, InSettings, GeometryData->GetTriangleCount()); - - //Set visibility settings - SimplygonSDK::spVisibilitySettings VisibilitySettings = ReductionProcessor->GetVisibilitySettings(); - VisibilitySettings->SetCullOccludedGeometry(InSettings.bCullOccluded); - int32 TempAggressiveness = InSettings.VisibilityAggressiveness + 1; //+1 because there is an offset in aggressiveness options - VisibilitySettings->SetVisibilityWeightsPower(TempAggressiveness); - VisibilitySettings->SetUseVisibilityWeightsInReducer(InSettings.bVisibilityAided); - - SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings(); - SetNormalSettings(NormalSettings, InSettings); - - ReductionProcessor->RunProcessing(); - - SimplygonSDK::spSceneMesh ReducedMesh = SimplygonSDK::Cast(Scene->GetRootNode()->GetChild(0)); - CreateRawMeshFromGeometry(OutReducedMesh, ReducedMesh->GetGeometry(), WINDING_Keep); - OutMaxDeviation = ReductionProcessor->GetResultDeviation(); - } - - virtual bool ReduceSkeletalMesh( - USkeletalMesh* SkeletalMesh, - int32 LODIndex, - bool bReregisterComponent = true - ) override - { - check( SkeletalMesh ); - check( LODIndex >= 0 ); - check( LODIndex <= SkeletalMesh->GetLODNum() ); - - FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel(); - check(SkeletalMeshResource); - check( LODIndex <= SkeletalMeshResource->LODModels.Num() ); - - if (bReregisterComponent) - { - TComponentReregisterContext ReregisterContext; - SkeletalMesh->ReleaseResources(); - SkeletalMesh->ReleaseResourcesFence.Wait(); - - Reduce(SkeletalMesh, SkeletalMeshResource, LODIndex); - - SkeletalMesh->PostEditChange(); - SkeletalMesh->InitResources(); - } - else - { - Reduce(SkeletalMesh, SkeletalMeshResource, LODIndex); - } - - return true; - } - - virtual bool IsSupported() const override - { - return true; - } - - /** - * Returns true if mesh reduction is active. Active mean there will be a reduction of the vertices or triangle number - */ - virtual bool IsReductionActive(const FMeshReductionSettings &ReductionSettings) const - { - float Threshold_One = (1.0f - KINDA_SMALL_NUMBER); - float Threshold_Zero = (0.0f + KINDA_SMALL_NUMBER); - return ReductionSettings.PercentTriangles < Threshold_One || ReductionSettings.MaxDeviation > Threshold_Zero; - } - - virtual bool IsReductionActive(const FSkeletalMeshOptimizationSettings &ReductionSettings) const - { - float Threshold_One = (1.0f - KINDA_SMALL_NUMBER); - float Threshold_Zero = (0.0f + KINDA_SMALL_NUMBER); - switch (ReductionSettings.ReductionMethod) - { - case SkeletalMeshOptimizationType::SMOT_NumOfTriangles: - { - return ReductionSettings.NumOfTrianglesPercentage < Threshold_One; - } - break; - case SkeletalMeshOptimizationType::SMOT_MaxDeviation: - { - return ReductionSettings.MaxDeviationPercentage > Threshold_Zero; - } - break; - case SkeletalMeshOptimizationType::SMOT_TriangleOrDeviation: - { - return ReductionSettings.NumOfTrianglesPercentage < Threshold_One || ReductionSettings.MaxDeviationPercentage > Threshold_Zero; - } - break; - } - - return false; - } - - static void Destroy() - { - if (GSimplygonSDKDLLHandle != nullptr) - { - typedef int(*DeinitializeSimplygonSDKPtr)(); - DeinitializeSimplygonSDKPtr DeinitializeSimplygonSDK = (DeinitializeSimplygonSDKPtr) FPlatformProcess::GetDllExport(GSimplygonSDKDLLHandle, TEXT("DeinitializeSimplygonSDK")); - DeinitializeSimplygonSDK(); - FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle); - GSimplygonSDKDLLHandle = nullptr; - } - } - - static FSimplygonMeshReduction* Create() - { - if (FParse::Param(FCommandLine::Get(), TEXT("NoSimplygon"))) - { - //The user specified that simplygon should not be used - UE_LOG(LogSimplygon, Log, TEXT("Simplygon is disabled with -NoSimplygon flag")); - return NULL; - } - //@third party BEGIN SIMPLYGON - const FString SUPPORTED_SIMPLYGON_VERSION = "8"; - //@third party END SIMPLYGON - - // Load d3d compiler first - FString DllPath; -#if !PLATFORM_64BITS - DllPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Windows/DirectX/x86/d3dcompiler_47.dll")); -#else - DllPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Windows/DirectX/x64/d3dcompiler_47.dll")); -#endif - if (!FPaths::FileExists(DllPath)) - { - UE_LOG(LogSimplygon, Log, TEXT("Could not find d3dcompiler_47 DLL, which is required for loading Simplygon.")); - return NULL; - } - else - { - void* D3DHandle = FPlatformProcess::GetDllHandle(*DllPath); - } - - - DllPath = (FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/NotForLicensees/Simplygon"))); - FString DllFilename(FPaths::Combine(*DllPath, TEXT("SimplygonSDKRuntimeReleasex64.dll"))); - - // If the DLL just doesn't exist, fail gracefully. Licensees and Subscribers will not necessarily have Simplygon. - if( !FPaths::FileExists(DllFilename) ) - { - UE_LOG(LogSimplygon,Log,TEXT("Simplygon DLL not present - disabling.")); - return NULL; - } - - // Otherwise fail - GSimplygonSDKDLLHandle = FPlatformProcess::GetDllHandle(*DllFilename); - if (GSimplygonSDKDLLHandle == NULL) - { - int32 ErrorNum = FPlatformMisc::GetLastError(); - TCHAR ErrorMsg[1024]; - FPlatformMisc::GetSystemErrorMessage( ErrorMsg, 1024, ErrorNum ); - UE_LOG(LogSimplygon,Error,TEXT("Failed to get Simplygon DLL handle: %s (%d)"),ErrorMsg,ErrorNum); - return NULL; - } - - // Get API function pointers of interest - typedef void (*GetInterfaceVersionSimplygonSDKPtr)(ANSICHAR*); - GetInterfaceVersionSimplygonSDKPtr GetInterfaceVersionSimplygonSDK = (GetInterfaceVersionSimplygonSDKPtr)FPlatformProcess::GetDllExport( GSimplygonSDKDLLHandle, TEXT( "GetInterfaceVersionSimplygonSDK" ) ); - - typedef int (*InitializeSimplygonSDKPtr)(const char* LicenseData , SimplygonSDK::ISimplygonSDK** OutInterfacePtr); - InitializeSimplygonSDKPtr InitializeSimplygonSDK = (InitializeSimplygonSDKPtr)FPlatformProcess::GetDllExport( GSimplygonSDKDLLHandle, TEXT( "InitializeSimplygonSDK" ) ); - - if ((GetInterfaceVersionSimplygonSDK == NULL) || (InitializeSimplygonSDK == NULL)) - { - // Couldn't find the functions we need. - UE_LOG(LogSimplygon, Log,TEXT("Failed to acquire Simplygon DLL exports.")); - FPlatformProcess::FreeDllHandle( GSimplygonSDKDLLHandle ); - GSimplygonSDKDLLHandle = NULL; - return NULL; - } - - //@third party BEGIN SIMPLYGON - //NOTE: Only major version check is required. This removes the dependency to update each time the SDK is updated to a minor release with bug fixes - //TODO : Tokenize the version string. This would allow for better granularity when a tight coupling to the dll is required. (i.e Custom Dll Version) - FString MajorVersion, MinorAndBuildRevision; - FString SimplygonHeaderVersioString = ANSI_TO_TCHAR(SimplygonSDK::GetHeaderVersion()); - SimplygonHeaderVersioString.Split(TEXT("."), &MajorVersion, &MinorAndBuildRevision); - - if (SUPPORTED_SIMPLYGON_VERSION.Compare(MajorVersion) != 0) - { - UE_LOG(LogSimplygon, Log, TEXT("Simplygon version doesn't match the version expected by the Simplygon UE4 integration")); - UE_LOG(LogSimplygon, Log, TEXT("Min version %s, found version %s"), *SUPPORTED_SIMPLYGON_VERSION, ANSI_TO_TCHAR(SimplygonSDK::GetHeaderVersion())); - FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle); - GSimplygonSDKDLLHandle = NULL; - return NULL; - } - //@third party END SIMPLYGON - - - ANSICHAR VersionHash[200]; - GetInterfaceVersionSimplygonSDK(VersionHash); - if (FCStringAnsi::Strcmp(VersionHash, SimplygonSDK::GetInterfaceVersionHash()) != 0) - { - UE_LOG(LogSimplygon, Log,TEXT("Library version mismatch. Header=%s Lib=%s"),ANSI_TO_TCHAR(SimplygonSDK::GetInterfaceVersionHash()),ANSI_TO_TCHAR(VersionHash)); - FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle); - GSimplygonSDKDLLHandle = NULL; - return NULL; - } - - // Workaround for the fact Simplygon stomps memory - class FSimplygonLicenseData - { - uint8* LicenseDataMem; - int32 RealDataOffset; - public: - FSimplygonLicenseData(const TCHAR* InDllPath) - : LicenseDataMem(nullptr) - , RealDataOffset(0) - { - TArray LicenseFileContents; - if (FFileHelper::LoadFileToArray(LicenseFileContents, *FPaths::Combine(InDllPath, TEXT("Simplygon_8_license.dat")), FILEREAD_Silent)) - { - if (LicenseFileContents.Num() > 0) - { - // Allocate with a big slack at the beginning and end to workaround the fact Simplygon stomps memory - const int32 LicenseDataSizeWithSlack = LicenseFileContents.Num() * 3 * sizeof(uint8); - LicenseDataMem = (uint8*)FMemory::Malloc(LicenseDataSizeWithSlack); - FMemory::Memzero(LicenseDataMem, LicenseDataSizeWithSlack); - // Copy data to somewhere in the middle of allocated memory - RealDataOffset = LicenseFileContents.Num(); - FMemory::Memcpy(LicenseDataMem + RealDataOffset, LicenseFileContents.GetData(), LicenseFileContents.Num() * sizeof(uint8)); - } - } - } - ~FSimplygonLicenseData() - { - FMemory::Free(LicenseDataMem); - } - const char* GetLicenseData() const - { - return (const char*)(LicenseDataMem + RealDataOffset); - } - bool IsValid() const - { - return !!LicenseDataMem; - } - } LicenseDataContainer(*DllPath); - - FSimplygonMeshReduction* Result = nullptr; - if (LicenseDataContainer.IsValid()) - { - SimplygonSDK::ISimplygonSDK* SDK = NULL; - int32 InitResult = InitializeSimplygonSDK(LicenseDataContainer.GetLicenseData(), &SDK); - - if (InitResult != SimplygonSDK::SG_ERROR_NOERROR && InitResult != SimplygonSDK::SG_ERROR_ALREADYINITIALIZED) - { - UE_LOG(LogSimplygon, Log, TEXT("Failed to initialize Simplygon. Return code: %d."), InitResult); - FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle); - GSimplygonSDKDLLHandle = nullptr; - } - else - { - Result = new FSimplygonMeshReduction(SDK); - } - } - else - { - UE_LOG(LogSimplygon, Log, TEXT("Failed to load Simplygon license file.")); - } - - return Result; - } - - struct FMaterialCastingProperties - { - bool bCastMaterials; - bool bCastNormals; - bool bCastMetallic; - bool bCastRoughness; - bool bCastSpecular; - - FMaterialCastingProperties() - : bCastMaterials(false) - , bCastNormals(false) - , bCastMetallic(false) - , bCastRoughness(false) - , bCastSpecular(false) - { - } - }; - - // Processor is spRemeshingProcessor or spAggregationProcessor - template - void SimplygonProcessLOD( - ProcessorClass Processor, - const TArray& DataArray, - const TArray& FlattenedMaterials, - const FSimplygonMaterialLODSettings& MaterialLODSettings, - FFlattenMaterial& OutMaterial) - { - if (!MaterialLODSettings.bActive) - { - UE_LOG(LogSimplygon, Log, TEXT("Processing with %s."), *FString(Processor->GetClass())); - Processor->RunProcessing(); - UE_LOG(LogSimplygon, Log, TEXT("Processing done.")); - return; - } - - // Setup the mapping image used for casting - SetupMappingImage( - MaterialLODSettings, - Processor->GetMappingImageSettings(), - /*InAggregateProcess = */ TAreTypesEqual::Value, - /*InRemoveUVs = */ true); - - // Convert FFlattenMaterial array to Simplygon materials - SimplygonSDK::spMaterialTable InputMaterialTable = SDK->CreateMaterialTable(); - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable InputTextureTable = Processor->GetScene()->GetTextureTable(); - CreateSGMaterialFromFlattenMaterial(FlattenedMaterials, MaterialLODSettings, InputMaterialTable, InputTextureTable, true); - //@third party END SIMPLYGON - - - // Perform LOD processing - UE_LOG(LogSimplygon, Log, TEXT("Processing with %s."), *FString(Processor->GetClass())); - double ProcessingTime = 0.0; - { - FScopedDurationTimer ProcessingTimer(ProcessingTime); - Processor->RunProcessing(); - } - UE_LOG(LogSimplygon, Log, TEXT("Processing done in %.2f s."), ProcessingTime); - - // Cast input materials to output materials and convert to FFlattenMaterial - UE_LOG(LogSimplygon, Log, TEXT("Casting materials.")); - SimplygonSDK::spMappingImage MappingImage = Processor->GetMappingImage(); - //@third party BEGIN SIMPLYGON - SimplygonSDK::spMaterial SgMaterial = RebakeMaterials(MaterialLODSettings, MappingImage, InputMaterialTable, InputTextureTable); - CreateFlattenMaterialFromSGMaterial(SgMaterial, InputTextureTable, OutMaterial); - //@third party END SIMPLYGON - - // Fill flatten material samples alpha values with 255 (to prevent the data from being optimized away) - OutMaterial.FillAlphaValues(255); - - if (FlattenedMaterials.Num()) - { - OutMaterial.BlendMode = FlattenedMaterials[0].BlendMode; - OutMaterial.bTwoSided = FlattenedMaterials[0].bTwoSided; - OutMaterial.bDitheredLODTransition = FlattenedMaterials[0].bDitheredLODTransition; - OutMaterial.EmissiveScale = FlattenedMaterials[0].EmissiveScale; - } - - UE_LOG(LogSimplygon, Log, TEXT("Casting done.")); - } - - - class FProxyLODTask : FRunnable - { - public: - FProxyLODTask(const TArray& InData, - const struct FMeshProxySettings& InProxySettings, - const TArray& InputMaterials, - const FGuid InJobGUID, FProxyCompleteDelegate& InDelegate, - SimplygonSDK::ISimplygonSDK* InSDK, FSimplygonMeshReduction* InReduction) - : Data(InData) - , ProxySettings(InProxySettings) - , Materials(InputMaterials) - , JobGUID(InJobGUID) - , Delegate(InDelegate) - , SDK(InSDK) - , Reduction(InReduction) - { - } - - virtual bool Init() - { - return true; - } - - virtual uint32 Run() - { - FMeshDescription OutProxyMesh; - FFlattenMaterial OutMaterial; - - if (!Data.Num()) - { - UE_LOG(LogSimplygon, Log, TEXT("The selected meshes are not valid. Make sure to select static meshes only.")); - OutProxyMesh.Empty(); - return 1; - } - - //Create a Simplygon Scene - SimplygonSDK::spScene Scene = SDK->CreateScene(); - - SimplygonSDK::spGeometryValidator GeometryValidator = SDK->CreateGeometryValidator(); - TArray GlobalTexcoordBounds; - - - for (int32 MeshIndex = 0; MeshIndex < Data.Num(); ++MeshIndex) - { - GlobalTexcoordBounds.Append(Data[MeshIndex].TexCoordBounds); - } - - //For each raw mesh in array create a scene mesh and populate with geometry data - for (int32 MeshIndex = 0; MeshIndex < Data.Num(); ++MeshIndex) - { - SimplygonSDK::spGeometryData GeometryData = Reduction->CreateGeometryFromRawMesh(*Data[MeshIndex].RawMesh, Data[MeshIndex].TexCoordBounds, Data[MeshIndex].NewUVs); - if (!GeometryData) - { - UE_LOG(LogSimplygon, Warning, TEXT("Geometry data is NULL")); - continue; - } - - GeometryData->CleanupNanValues(); - - //Validate the geometry - Reduction->ValidateGeometry(GeometryValidator, GeometryData); - - check(GeometryData) - -#ifdef DEBUG_PROXY_MESH - SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter(); - objexp->SetExportFilePath("d:/BeforeProxyMesh.obj"); - objexp->SetSingleGeometry(GeometryData); - objexp->RunExport(); -#endif - - SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh(); - Mesh->SetGeometry(GeometryData); - Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh%d"), MeshIndex))); - Scene->GetRootNode()->AddChild(Mesh); - - } - - //Create a remesher - SimplygonSDK::spRemeshingProcessor RemeshingProcessor = SDK->CreateRemeshingProcessor(); - - //Setup the remesher - // TODO add more settings back in - RemeshingProcessor->GetRemeshingSettings()->SetOnScreenSize(ProxySettings.ScreenSize); - RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance(ProxySettings.MergeDistance); - RemeshingProcessor->SetScene(Scene); - - FSimplygonMaterialLODSettings MaterialLODSettings(ProxySettings.MaterialSettings); - MaterialLODSettings.bActive = true; - - // Process data - Reduction->SimplygonProcessLOD(RemeshingProcessor, Data, Materials, MaterialLODSettings, OutMaterial); - - //Collect the proxy mesh - SimplygonSDK::spSceneMesh ProxyMesh = SimplygonSDK::Cast(Scene->GetRootNode()->GetChild(0)); - -#ifdef DEBUG_PROXY_MESH - SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter(); - objexp->SetExportFilePath("d:/AfterProxyMesh.obj"); - objexp->SetSingleGeometry(ProxyMesh->GetGeometry()); - objexp->RunExport(); -#endif - - //Convert geometry data to raw mesh data - SimplygonSDK::spGeometryData outGeom = ProxyMesh->GetGeometry(); - Reduction->CreateRawMeshFromGeometry(OutProxyMesh, ProxyMesh->GetGeometry(), WINDING_Keep); - - // Default smoothing - TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - - for (const FEdgeID& EdgeID : OutProxyMesh.Edges().GetElementIDs()) - { - EdgeHardnesses[EdgeID] = false; - } - - Delegate.ExecuteIfBound(OutProxyMesh, OutMaterial, JobGUID); - - return 0; - } - - virtual void Stop() - { - - } - - void StartJobAsync() - { - Thread = FRunnableThread::Create(this, TEXT("SimplygonMeshReductionTask"), 0, TPri_BelowNormal); - } - - bool IsRunning() const - { - return (Thread != nullptr); - } - void Wait() - { - Thread->WaitForCompletion(); - } - private: - FRunnableThread* Thread; - TArray Data; - struct FMeshProxySettings ProxySettings; - TArray Materials; - FGuid JobGUID; - FProxyCompleteDelegate Delegate; - SimplygonSDK::ISimplygonSDK* SDK; - FSimplygonMeshReduction* Reduction; - }; - - virtual void ProxyLOD(const TArray& InData, - const struct FMeshProxySettings& InProxySettings, - const TArray& InputMaterials, - const FGuid InJobGUID) override - { - FScopedSlowTask SlowTask(100.f, (LOCTEXT("SimplygonProxyLOD_ProxyLOD", "Generating Proxy Mesh using Simplygon"))); - SlowTask.MakeDialog(); - - FMeshDescription OutProxyMesh; - UStaticMesh::RegisterMeshAttributes(OutProxyMesh); - FFlattenMaterial OutMaterial; - - if (!InData.Num()) - { - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("The selected meshes are not valid. Make sure to select static meshes only.")); - OutProxyMesh.Empty(); - return; - } - - //Create a Simplygon Scene - SimplygonSDK::spScene Scene = SDK->CreateScene(); - - SimplygonSDK::spGeometryValidator GeometryValidator = SDK->CreateGeometryValidator(); - TArray GlobalTexcoordBounds; - - for (int32 MeshIndex = 0; MeshIndex < InData.Num(); ++MeshIndex) - { - GlobalTexcoordBounds.Append(InData[MeshIndex].TexCoordBounds); - } - - // Create Selection Sets for tagging geometry for processing and clipping geometry - SimplygonSDK::spSelectionSet processingSet = SDK->CreateSelectionSet(); - processingSet->SetName("RemeshingProcessingSet"); - SimplygonSDK::spSelectionSet clippingGeometrySet = SDK->CreateSelectionSet(); - clippingGeometrySet->SetName("ClippingObjectSet"); - - //For each raw mesh in array create a scene mesh and populate with geometry data - for (int32 MeshIndex = 0; MeshIndex < InData.Num(); ++MeshIndex) - { - SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(*InData[MeshIndex].RawMesh, InData[MeshIndex].TexCoordBounds, InData[MeshIndex].NewUVs); - if (!GeometryData) - { - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate Geometry Data")); - continue; - } - - GeometryData->CleanupNanValues(); - - //Validate the geometry - ValidateGeometry(GeometryValidator, GeometryData); - -#ifdef DEBUG_PROXY_MESH - SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter(); - objexp->SetExportFilePath("d:/BeforeProxyMesh.obj"); - objexp->SetSingleGeometry(GeometryData); - objexp->RunExport(); -#endif - SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh(); - Mesh->SetGeometry(GeometryData); - Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh%d"), MeshIndex))); - Scene->GetRootNode()->AddChild(Mesh); - - // if mesh is clipping geometry add to clipping set else processing set - if (!InData[MeshIndex].bIsClippingMesh) - { - processingSet->AddItem(Mesh->GetNodeGUID()); - } - else if (InData[MeshIndex].bIsClippingMesh) - { - clippingGeometrySet->AddItem(Mesh->GetNodeGUID()); - } - } - - //add the sets to the scene - Scene->GetSelectionSetTable()->AddItem(processingSet); - Scene->GetSelectionSetTable()->AddItem(clippingGeometrySet); - - //Create a remesher - SimplygonSDK::spRemeshingProcessor RemeshingProcessor = SDK->CreateRemeshingProcessor(); - RemeshingProcessor->Clear(); - - //Setup the remesher - EventHandler.Task = &SlowTask; - RemeshingProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS); - RemeshingProcessor->GetRemeshingSettings()->SetOnScreenSize(InProxySettings.ScreenSize); - RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance(InProxySettings.MergeDistance); - if (InProxySettings.bUseHardAngleThreshold) - { - // Otherwise the default Remeshing setting of 80-degree will be used. - RemeshingProcessor->GetRemeshingSettings()->SetHardEdgeAngleInRadians(FMath::DegreesToRadians(InProxySettings.HardAngleThreshold)); - } - - // Setup sets for the remeshing processor - bool bUseClippingGeometry = clippingGeometrySet->GetItemCount() > 0 ? true : false; - - RemeshingProcessor->GetRemeshingSettings()->SetUseClippingGeometry(bUseClippingGeometry); - RemeshingProcessor->GetRemeshingSettings()->SetProcessSelectionSetName("RemeshingProcessingSet"); - - if (bUseClippingGeometry) - { - RemeshingProcessor->GetRemeshingSettings()->SetClippingGeometrySelectionSetName("ClippingObjectSet"); - RemeshingProcessor->GetRemeshingSettings()->SetUseClippingGeometryEmptySpaceOverride(false); - } - - RemeshingProcessor->SetScene(Scene); - - FSimplygonMaterialLODSettings MaterialLODSettings(InProxySettings.MaterialSettings); - MaterialLODSettings.bActive = true; - - // Process data - SimplygonProcessLOD(RemeshingProcessor, InData, InputMaterials, MaterialLODSettings, OutMaterial); - - //Collect the proxy mesh - const uint32 ChildNodeCount = Scene->GetRootNode()->GetChildCount(); - - SimplygonSDK::spSceneMesh ProxyMesh = nullptr; - for (uint32 ChildNodeIndex = 0; ChildNodeIndex < ChildNodeCount; ++ChildNodeIndex) - { - auto ChildNode = Scene->GetRootNode()->GetChild(ChildNodeIndex); - SimplygonSDK::spSceneMesh Mesh = SimplygonSDK::SafeCast(ChildNode); - if (Mesh != NULL) - { - ProxyMesh = Mesh; - } - } - - if (ProxyMesh == nullptr) - { - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate a proxy mesh")); - } - else - { -#ifdef DEBUG_PROXY_MESH - SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter(); - objexp->SetExportFilePath("d:/AfterProxyMesh.obj"); - objexp->SetSingleGeometry(ProxyMesh->GetGeometry()); - objexp->RunExport(); -#endif - - //Convert geometry data to raw mesh data - SimplygonSDK::spGeometryData outGeom = ProxyMesh->GetGeometry(); - CreateRawMeshFromGeometry(OutProxyMesh, ProxyMesh->GetGeometry(), WINDING_Keep); - - // Default smoothing - TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - - for (const FEdgeID& EdgeID : OutProxyMesh.Edges().GetElementIDs()) - { - EdgeHardnesses[EdgeID] = false; - } - - if (OutProxyMesh.Polygons().Num() == 0) - { - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate a valid proxy mesh")); - } - CompleteDelegate.ExecuteIfBound(OutProxyMesh, OutMaterial, InJobGUID); - } - - EventHandler.Task = nullptr; - } - -private: - SimplygonSDK::ISimplygonSDK* SDK; - FDefaultErrorHandler ErrorHandler; - FDefaultEventHandler EventHandler; - FString VersionString; - - explicit FSimplygonMeshReduction(SimplygonSDK::ISimplygonSDK* InSDK) - : SDK(InSDK) - { - check(SDK); - SDK->SetErrorHandler(&ErrorHandler); - SDK->SetGlobalSetting("DefaultTBNType", SimplygonSDK::SG_TANGENTSPACEMETHOD_ORTHONORMAL_LEFTHANDED); - SDK->SetGlobalSetting("AllowDirectX", true); - - const TCHAR* LibraryVersion = ANSI_TO_TCHAR(InSDK->GetVersion()); - const TCHAR* UnrealVersionGuid = TEXT("18f808c3cf724e5a994f57de5c83cc4b"); - VersionString = FString::Printf(TEXT("%s.%s_%s"), LibraryVersion, SG_UE_INTEGRATION_REV,UnrealVersionGuid); - UE_LOG(LogSimplygon, Display, TEXT("Initialized with Simplygon %s"), *VersionString); - } - - SimplygonSDK::spGeometryData CreateGeometryFromRawMesh(const FMeshDescription& RawMesh) - { - TVertexAttributesConstRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesConstRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesConstRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesConstRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesConstRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesConstRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesConstRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesConstRef VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - int32 NumVertices = RawMesh.Vertices().Num(); - int32 NumWedges = RawMesh.VertexInstances().Num(); - - if (NumWedges == 0) - { - return NULL; - } - - int32 NumTris = 0; - for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs()) - { - NumTris += RawMesh.GetPolygon(PolygonID).Triangles.Num(); - } - - TArray FaceSmoothingMasks; - FaceSmoothingMasks.AddZeroed(NumTris); - FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(RawMesh, FaceSmoothingMasks); - - SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData(); - GeometryData->SetVertexCount(NumVertices); - GeometryData->SetTriangleCount(NumTris); - - TMap VertexIDToDstVertexIndex; - VertexIDToDstVertexIndex.Reserve(NumVertices); - int32 VertexCount = 0; - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - for (const FVertexID& VertexID : RawMesh.Vertices().GetElementIDs()) - { - FVector TempPos = VertexPositions[VertexID]; - TempPos = GetConversionMatrix().TransformPosition(TempPos); - Positions->SetTuple(VertexID.GetValue(), (float*)&TempPos); - VertexIDToDstVertexIndex.Add(VertexID, VertexCount); - VertexCount++; - } - - //Prepare the tex coord - TArray TexCoordsArray; - for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex) - { - GeometryData->AddTexCoords(TexCoordIndex); - SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex); - check(TexCoords->GetTupleSize() == 2); - TexCoordsArray.Add(TexCoords); - } - - //Prepare the vertex color - GeometryData->AddColors(0); - SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0); - check(LinearColors); - check(LinearColors->GetTupleSize() == 4); - - //Prepare the tangent space - GeometryData->AddTangents(0); - GeometryData->AddNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - - GeometryData->AddMaterialIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - - uint32 DstVertexIndex = 0; - uint32 DstTriangleIndex = 0; - for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs()) - { - for (const FMeshTriangle& Triangle : RawMesh.GetPolygon(PolygonID).Triangles) - { - for (int32 Corner = 0; Corner < 3; ++Corner) - { - const FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(Corner); - //Add all the per indice data - Indices->SetItem(DstVertexIndex, VertexIDToDstVertexIndex[RawMesh.GetVertexInstanceVertex(VertexInstanceID)]); - - //UVs - for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex) - { - FVector2D Vector2D = VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex); - TexCoordsArray[TexCoordIndex]->SetTuple(DstVertexIndex, (float*)&Vector2D); - } - - //Colors - LinearColors->SetTuple(DstVertexIndex, (float*)&VertexInstanceColors[VertexInstanceID]); - - //Tangents - FVector TempTangent = VertexInstanceTangents[VertexInstanceID]; - TempTangent = GetConversionMatrix().TransformVector(TempTangent); - Tangents->SetTuple(DstVertexIndex, (float*)&TempTangent); - - FVector TempBitangent = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; - TempBitangent = GetConversionMatrix().TransformVector(TempBitangent); - Bitangents->SetTuple(DstVertexIndex, (float*)&TempBitangent); - - FVector TempNormal = VertexInstanceNormals[VertexInstanceID]; - TempNormal = GetConversionMatrix().TransformVector(TempNormal); - Normals->SetTuple(DstVertexIndex, (float*)&TempNormal); - - //Increment the indice index - DstVertexIndex++; - } - - // Per-triangle data. - - //Materials - const FPolygonGroupID& PolygonGroupID = RawMesh.GetPolygonPolygonGroup(PolygonID); - MaterialIndices->SetItem(DstTriangleIndex, PolygonGroupID.GetValue()); - - //Smooth group - GeometryData->AddGroupIds(); - SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds(); - GroupIds->SetItem(DstTriangleIndex, FaceSmoothingMasks[DstTriangleIndex]); - - DstTriangleIndex++; - } - } - - return GeometryData; - } - - // This is a copy of CreateGeometryFromRawMesh with additional features for material LOD. - SimplygonSDK::spGeometryData CreateGeometryFromRawMesh(const FMeshDescription& RawMesh, const TArray& TextureBounds, const TArray& InTexCoords) - { - TVertexAttributesConstRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesConstRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesConstRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesConstRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesConstRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesConstRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesConstRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesConstRef VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - int32 NumVertices = RawMesh.Vertices().Num(); - int32 NumWedges = RawMesh.VertexInstances().Num(); - - if (NumWedges == 0) - { - return NULL; - } - - int32 NumTris = 0; - for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs()) - { - NumTris += RawMesh.GetPolygon(PolygonID).Triangles.Num(); - } - - TArray FaceSmoothingMasks; - FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(RawMesh, FaceSmoothingMasks); - - SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData(); - GeometryData->SetVertexCount(NumVertices); - GeometryData->SetTriangleCount(NumTris); - - TMap VertexIDToDstVertexIndex; - VertexIDToDstVertexIndex.Reserve(NumVertices); - int32 VertexCount = 0; - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - for (const FVertexID& VertexID : RawMesh.Vertices().GetElementIDs()) - { - FVector TempPos = VertexPositions[VertexID]; - TempPos = GetConversionMatrix().TransformPosition(TempPos); - Positions->SetTuple(VertexID.GetValue(), (float*)&TempPos); - VertexIDToDstVertexIndex.Add(VertexID, VertexCount); - VertexCount++; - } - - //Prepare the tex coord - TArray TexCoordsArray; - for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex) - { - GeometryData->AddTexCoords(TexCoordIndex); - SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex); - check(TexCoords->GetTupleSize() == 2); - TexCoordsArray.Add(TexCoords); - } - - //Prepare the vertex color - GeometryData->AddColors(0); - SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0); - check(LinearColors); - check(LinearColors->GetTupleSize() == 4); - - //Prepare the tangent space - GeometryData->AddTangents(0); - GeometryData->AddNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - - GeometryData->AddMaterialIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - - uint32 DstVertexIndex = 0; - uint32 DstTriangleIndex = 0; - for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs()) - { - for (const FMeshTriangle& Triangle : RawMesh.GetPolygon(PolygonID).Triangles) - { - //Materials - const FPolygonGroupID& PolygonGroupID = RawMesh.GetPolygonPolygonGroup(PolygonID); - MaterialIndices->SetItem(DstTriangleIndex, PolygonGroupID.GetValue()); - int32 MaterialIndex = PolygonGroupID.GetValue(); - - for (int32 Corner = 0; Corner < 3; ++Corner) - { - const FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(Corner); - //Add all the per indice data - Indices->SetItem(DstVertexIndex, VertexIDToDstVertexIndex[RawMesh.GetVertexInstanceVertex(VertexInstanceID)]); - - //UVs - for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex) - { - // Compute texture bounds for current material. - float MinU = 0, ScaleU = 1; - float MinV = 0, ScaleV = 1; - - if (TextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 && InTexCoords.Num() == 0) - { - const FBox2D& Bounds = TextureBounds[MaterialIndex]; - if (Bounds.GetArea() > 0) - { - MinU = Bounds.Min.X; - MinV = Bounds.Min.Y; - ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X); - ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y); - } - } - - const FVector2D& TexCoord = VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex); - float UV[2]; - UV[0] = (TexCoord.X - MinU) * ScaleU; - UV[1] = (TexCoord.Y - MinV) * ScaleV; - TexCoordsArray[TexCoordIndex]->SetTuple(DstVertexIndex, UV); - } - - //Colors - LinearColors->SetTuple(DstVertexIndex, (float*)&VertexInstanceColors[VertexInstanceID]); - - //Tangents - FVector TempTangent = VertexInstanceTangents[VertexInstanceID]; - TempTangent = GetConversionMatrix().TransformVector(TempTangent); - Tangents->SetTuple(DstVertexIndex, (float*)&TempTangent); - - FVector TempBitangent = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; - TempBitangent = GetConversionMatrix().TransformVector(TempBitangent); - Bitangents->SetTuple(DstVertexIndex, (float*)&TempBitangent); - - FVector TempNormal = VertexInstanceNormals[VertexInstanceID]; - TempNormal = GetConversionMatrix().TransformVector(TempNormal); - Normals->SetTuple(DstVertexIndex, (float*)&TempNormal); - - //Increment the indice index - DstVertexIndex++; - } - - // Per-triangle data. - - //Smooth group - GeometryData->AddGroupIds(); - SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds(); - GroupIds->SetItem(DstTriangleIndex, FaceSmoothingMasks[DstTriangleIndex]); - - DstTriangleIndex++; - } - } - return GeometryData; - } - - void CreateRawMeshFromGeometry(FMeshDescription& OutRawMesh, const SimplygonSDK::spGeometryData& GeometryData, EWindingMode WindingMode) - { - check(GeometryData); - - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds(); - SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - - check(Positions); - check(Indices); - - FMeshDescription& RawMesh = OutRawMesh; - - TVertexAttributesRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesRef VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - const bool bReverseWinding = (WindingMode == WINDING_Reverse); - int32 NumTris = GeometryData->GetTriangleCount(); - int32 NumWedges = NumTris * 3; - int32 NumVertices = GeometryData->GetVertexCount(); - - RawMesh.Empty(); - RawMesh.ReserveNewVertices(NumVertices); - RawMesh.ReserveNewPolygons(NumTris); - RawMesh.ReserveNewVertexInstances(NumWedges); - RawMesh.ReserveNewEdges(NumWedges); - - TMap GeoToRawVertexID; - SimplygonSDK::spRealData sgTuple = SDK->CreateRealData(); - for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) - { - Positions->GetTuple(VertexIndex, sgTuple); - SimplygonSDK::real* vertexPos = sgTuple->GetData(); - const FVertexID& VertexID = RawMesh.CreateVertex(); - VertexPositions[VertexID] = GetConversionMatrix().TransformPosition(FVector(vertexPos[0], vertexPos[1], vertexPos[2])); - GeoToRawVertexID.Add(VertexIndex, VertexID); - } - - //Prepare the tex coord - int32 TexCoordNum = 0; - TArray TexCoordsArray; - for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS_MD; ++TexCoordIndex, ++TexCoordNum) - { - SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex); - if (TexCoords == nullptr) - { - break; - } - check(TexCoords->GetTupleSize() == 2); - TexCoordsArray.Add(TexCoords); - } - VertexInstanceUVs.SetNumIndices(FMath::Max(TexCoordNum, 1)); - - //Prepare the polygongroup - TMap GeoToRawMaterial; - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - int32 MaterialIndex = MaterialIndices != nullptr ? MaterialIndices->GetItem(TriIndex) : 0; - if (!GeoToRawMaterial.Contains(MaterialIndex)) - { - const FPolygonGroupID PolygonGroupID(MaterialIndex); - RawMesh.CreatePolygonGroupWithID(PolygonGroupID); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString(TEXT("SimplygonMeshReduction_") + FString::FromInt(PolygonGroupID.GetValue()))); - GeoToRawMaterial.Add(MaterialIndex, PolygonGroupID); - } - } - - bool bHasZeroTangent = false; - bool bHasZeroNormal = false; - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - FVertexID VertexIndexes[3]; - FVertexInstanceID VertexInstanceIDs[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - const uint32 SrcIndex = (TriIndex * 3) + (bReverseWinding ? (2 - CornerIndex) : CornerIndex); - const FVertexID VertexID = GeoToRawVertexID[Indices->GetItem(SrcIndex)]; - const FVertexInstanceID VertexInstanceID = RawMesh.CreateVertexInstance(VertexID); - VertexIndexes[CornerIndex] = VertexID; - VertexInstanceIDs[CornerIndex] = VertexInstanceID; - //Texture coordinnates - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNum; ++TexCoordIndex) - { - TexCoordsArray[TexCoordIndex]->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgTextCoors = sgTuple->GetData(); - VertexInstanceUVs.Set(VertexInstanceID, TexCoordIndex, FVector2D(sgTextCoors[0], sgTextCoors[1])); - } - - //Vertex Color - if (LinearColors) - { - LinearColors->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgVertexColor = sgTuple->GetData(); - VertexInstanceColors[VertexInstanceID] = FVector4(sgVertexColor[0], sgVertexColor[1], sgVertexColor[2], sgVertexColor[3]); - } - - //Vertex Tangents - FVector Tangent(0.0f); - FVector BiTangent(0.0f); - FVector Normal(0.0f); - - if (Normals) - { - Normals->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgNormal = sgTuple->GetData(); - Normal = GetConversionMatrix().TransformVector(FVector(sgNormal[0], sgNormal[1], sgNormal[2])); - } - bHasZeroNormal |= Normal.IsNearlyZero(); - - if (Tangents && Bitangents) - { - Tangents->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgTangents = sgTuple->GetData(); - Tangent = GetConversionMatrix().TransformVector(FVector(sgTangents[0], sgTangents[1], sgTangents[2])); - - Bitangents->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgBiTangents = sgTuple->GetData(); - BiTangent = GetConversionMatrix().TransformVector(FVector(sgBiTangents[0], sgBiTangents[1], sgBiTangents[2])); - } - bHasZeroTangent |= Tangent.IsNearlyZero() || BiTangent.IsNearlyZero(); - - VertexInstanceTangents[VertexInstanceID] = Tangent; - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(Tangent.GetSafeNormal(), BiTangent.GetSafeNormal(), Normal.GetSafeNormal()); - VertexInstanceNormals[VertexInstanceID] = Normal; - } - //Create a polygon from this triangle - TArray Contours; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - int32 ContourPointIndex = Contours.AddDefaulted(); - FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex]; - //Find the matching edge ID - uint32 CornerIndices[2]; - CornerIndices[0] = (Corner + 0) % 3; - CornerIndices[1] = (Corner + 1) % 3; - - FVertexID EdgeVertexIDs[2]; - EdgeVertexIDs[0] = VertexIndexes[CornerIndices[0]]; - EdgeVertexIDs[1] = VertexIndexes[CornerIndices[1]]; - - FEdgeID MatchEdgeId = RawMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - if (MatchEdgeId == FEdgeID::Invalid) - { - MatchEdgeId = RawMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - EdgeHardnesses[MatchEdgeId] = false; - EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f; - } - ContourPoint.EdgeID = MatchEdgeId; - ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]]; - } - // Insert a polygon into the mesh - int32 MaterialIndex = MaterialIndices != nullptr ? MaterialIndices->GetItem(TriIndex) : 0; - const FPolygonID NewPolygonID = RawMesh.CreatePolygon(GeoToRawMaterial[MaterialIndex], Contours); - //Triangulate the polygon - FMeshPolygon& Polygon = RawMesh.GetPolygon(NewPolygonID); - RawMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles); - } - - TArray FaceSmoothingMasks; - FaceSmoothingMasks.AddZeroed(NumTris); - if (GroupIds) - { - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - FaceSmoothingMasks[TriIndex] = GroupIds->GetItem(TriIndex); - } - } - FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, RawMesh); - - if (bHasZeroTangent | bHasZeroNormal) - { - FMeshDescriptionOperations::ETangentOptions TangentOption = (FMeshDescriptionOperations::ETangentOptions)(FMeshDescriptionOperations::ETangentOptions::BlendOverlappingNormals | FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles); - if (bHasZeroNormal) - { - FMeshDescriptionOperations::CreateNormals(RawMesh, TangentOption, false); - } - FMeshDescriptionOperations::CreateMikktTangents(RawMesh, TangentOption); - } - } - - void SetReductionSettings(SimplygonSDK::spReductionSettings ReductionSettings, const FMeshReductionSettings& Settings, int32 NumTris) - { - float MaxDeviation = Settings.MaxDeviation > 0.0f ? Settings.MaxDeviation : SimplygonSDK::REAL_MAX; - float MinReductionRatio = FMath::Max(1.0f / NumTris, 0.05f); - float MaxReductionRatio = (Settings.MaxDeviation > 0.0f && Settings.PercentTriangles == 1.0f) ? MinReductionRatio : 1.0f; - float ReductionRatio = FMath::Clamp(Settings.PercentTriangles, MinReductionRatio, MaxReductionRatio); - - const float ImportanceTable[] = - { - 0.0f, // OFF - 0.125f, // Lowest - 0.35f, // Low, - 1.0f, // Normal - 2.8f, // High - 8.0f, // Highest - }; - - static_assert(ARRAY_COUNT(ImportanceTable) == (EMeshFeatureImportance::Highest + 1), "Importance table size mismatch."); // -1 because of TEMP_BROKEN(?) - check(Settings.SilhouetteImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.TextureImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.ShadingImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.VertexColorImportance < EMeshFeatureImportance::Highest + 1); // -1 because of TEMP_BROKEN(?) - - ReductionSettings->SetStopCondition(SimplygonSDK::SG_STOPCONDITION_ANY); - ReductionSettings->SetReductionTargets(SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION); - ReductionSettings->SetMaxDeviation(MaxDeviation); - ReductionSettings->SetTriangleRatio(ReductionRatio); - ReductionSettings->SetGeometryImportance(ImportanceTable[Settings.SilhouetteImportance]); - ReductionSettings->SetTextureImportance(ImportanceTable[Settings.TextureImportance]); - ReductionSettings->SetMaterialImportance(ImportanceTable[Settings.TextureImportance]); - ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance]); - ReductionSettings->SetVertexColorImportance(ImportanceTable[Settings.VertexColorImportance]); - ////ReductionSettings->SetAllowDirectX(true); - - //Automatic Symmetry Detection - ReductionSettings->SetKeepSymmetry(Settings.bKeepSymmetry); - ReductionSettings->SetUseAutomaticSymmetryDetection(Settings.bKeepSymmetry); - - //Set reposition vertices to be enabled by default - ReductionSettings->SetDataCreationPreferences(2); //2 = reposition vertices enabled - ReductionSettings->SetGenerateGeomorphData(true); - } - - void SetNormalSettings(SimplygonSDK::spNormalCalculationSettings NormalSettings, const FMeshReductionSettings& Settings) - { - NormalSettings->SetReplaceNormals(Settings.bRecalculateNormals); - NormalSettings->SetScaleByArea(false); - NormalSettings->SetScaleByAngle(false); - NormalSettings->SetHardEdgeAngleInRadians( FMath::DegreesToRadians(Settings.HardAngleThreshold) ); - } - - /** - * Creates a Simplygon scene representation of the skeletal hierarchy. - * @param InScene The Simplygon scene. - * @param InSkeleton The skeletal hierarchy from which to create the Simplygon representation. - * @param OutBoneTableIDs A mapping of Bone IDs from RefSkeleton to Simplygon Bone Table IDs - */ - void CreateSkeletalHierarchy( SimplygonSDK::spScene& InScene, const FReferenceSkeleton& InSkeleton, TArray& OutBoneTableIDs ) - { - TArray BoneArray; - - //Create Simplygon scene nodes for each bone in our Skeletal Hierarchy - for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetRawBoneNum(); ++BoneIndex) - { - SimplygonSDK::spSceneBone SceneBone = SDK->CreateSceneBone(); - BoneArray.Add(SceneBone); - } - - SimplygonSDK::spSceneBoneTable BoneTable = InScene->GetBoneTable(); - for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetRawBoneNum(); ++BoneIndex) - { - int32 ParentIndex = InSkeleton.GetParentIndex(BoneIndex); - SimplygonSDK::spSceneBone CurrentBone = BoneArray[BoneIndex]; - - /*if(InBoneTree[BoneIndex].bLockBone) - { - CurrentBone->SetLockFromBoneLOD(true); - } - - if(InBoneTree[BoneIndex].bRemoveBone) - { - CurrentBone->SetForceBoneRemoval(true); - }*/ - - - //We add the bone to the scene's bone table. This returns a uid that we must apply to the geometry data. - SimplygonSDK::rid BoneID = BoneTable->AddBone(CurrentBone); - OutBoneTableIDs.Add(BoneID); - - //Handle root bone. Add to Scene root. - if(BoneIndex == 0) - { - InScene->GetRootNode()->AddChild(CurrentBone); - } - else - { - SimplygonSDK::spSceneBone ParentBone = BoneArray[ParentIndex]; - ParentBone->AddChild(CurrentBone); - } - } - } - - /** - * Creates a Simplygon geometry representation from a skeletal mesh LOD model. - * @param Scene The Simplygon scene. - * @param LODModel The skeletal mesh LOD model from which to create the geometry data. - * @param BoneIDs A maps of Bone IDs from RefSkeleton to Simplygon BoneTable IDs - * @returns a Simplygon geometry data representation of the skeletal mesh LOD. - */ - SimplygonSDK::spGeometryData CreateGeometryFromSkeletalLODModel( SimplygonSDK::spScene& Scene, const FSkeletalMeshLODModel& LODModel, const TArray& BoneIDs, const TArray& BoneMatrices, const int32 LODIndex) - { - TArray Vertices; - LODModel.GetVertices( Vertices ); - const uint32 SectionCount = (uint32)LODModel.Sections.Num(); - auto SkipSection = [&LODModel, LODIndex](int32 SectionIndex) - { - if (LODModel.Sections[SectionIndex].bDisabled) - { - return true; - } - int32 MaxLODIndex = LODModel.Sections[SectionIndex].GenerateUpToLodIndex; - return (MaxLODIndex != -1 && MaxLODIndex < LODIndex); - }; - - uint32 VertexCount = 0; - uint32 TriCount = 0; - for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex) - { - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - - VertexCount += Section.SoftVertices.Num(); - //Skipped section - if (!SkipSection(SectionIndex)) - { - TriCount += LODModel.Sections[SectionIndex].NumTriangles; - } - } - const uint32 TexCoordCount = LODModel.NumTexCoords; - check( TexCoordCount <= MAX_TEXCOORDS ); - - SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData(); - GeometryData->SetVertexCount( VertexCount ); - GeometryData->SetTriangleCount( TriCount ); - - // Per-vertex data. - GeometryData->AddBoneIds( MAX_TOTAL_INFLUENCES ); - GeometryData->AddBoneWeights( MAX_TOTAL_INFLUENCES ); - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds(); - SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights(); - - // Per-corner per-triangle data. - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS]; - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - GeometryData->AddTexCoords( TexCoordIndex ); - TexCoords[TexCoordIndex] = GeometryData->GetTexCoords( TexCoordIndex ); - check( TexCoords[TexCoordIndex]->GetTupleSize() == 2 ); - } - GeometryData->AddNormals(); - GeometryData->AddTangents(0); - GeometryData->AddBitangents(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - - GeometryData->AddBaseTypeUserTriangleVertexField( SimplygonSDK::TYPES_ID_UCHAR, SIMPLYGON_COLOR_CHANNEL, 4 ); - SimplygonSDK::spValueArray ColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL ); - check( ColorValues ); - SimplygonSDK::spUnsignedCharArray Colors = SimplygonSDK::IUnsignedCharArray::SafeCast( ColorValues ); - check( Colors ); - - // Per-triangle data. - GeometryData->AddMaterialIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - - // Add per-vertex data. This data needs to be added per-section so that we can properly map bones. - uint32 FirstVertex = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num(); - for ( uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex ) - { - FSoftSkinVertex& Vertex = Vertices[ VertexIndex ]; - SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES]; - SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES]; - // get blended matrix - FMatrix BlendedMatrix = FMatrix::Identity; - uint32 TotalInfluence = 0; - for ( uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex ) - { - int32 BoneIndex = Vertex.InfluenceBones[InfluenceIndex]; - const uint8 BoneInfluence = Vertex.InfluenceWeights[InfluenceIndex]; - TotalInfluence += BoneInfluence; - - if ( BoneInfluence > 0 ) - { - check( BoneIndex < Section.BoneMap.Num() ); - uint32 BoneID = BoneIDs[Section.BoneMap[ BoneIndex ] ]; - VertexBoneIds[InfluenceIndex] = BoneID; - VertexBoneWeights[InfluenceIndex] = BoneInfluence / 255.0f; - - if (BoneMatrices.IsValidIndex(Section.BoneMap[BoneIndex])) - { - const FMatrix Matrix = BoneMatrices[Section.BoneMap[BoneIndex]]; - // calculate blended matrix - if (InfluenceIndex == 0) - { - BlendedMatrix = (Matrix * VertexBoneWeights[InfluenceIndex]); - } - else - { - BlendedMatrix += (Matrix * VertexBoneWeights[InfluenceIndex]); - } - } - } - else - { - //Set BoneID and BoneWeight to -1 and 0 respectively as required by simplygon. - VertexBoneIds[InfluenceIndex] = -1; - VertexBoneWeights[InfluenceIndex] = 0; - } - } - - // transform position - FVector WeightedVertex = BlendedMatrix.TransformPosition(Vertex.Position); - FVector WeightedTangentX = BlendedMatrix.TransformVector(Vertex.TangentX); - FVector WeightedTangentY = BlendedMatrix.TransformVector(Vertex.TangentY); - FVector WeightedTangentZ = BlendedMatrix.TransformVector(Vertex.TangentZ); - - check( TotalInfluence == 255 ); - Vertex.TangentX = WeightedTangentX.GetSafeNormal(); - Vertex.TangentY = WeightedTangentY.GetSafeNormal(); - uint8 WComponent = Vertex.TangentZ.W; - Vertex.TangentZ = WeightedTangentZ.GetSafeNormal(); - Vertex.TangentZ.W = WComponent; - FVector FinalVert = GetConversionMatrix().TransformPosition(WeightedVertex); - - //Vertex.Position.Z = -Vertex.Position.Z; - Positions->SetTuple( VertexIndex, (float*)&FinalVert); - BoneIds->SetTuple( VertexIndex, VertexBoneIds ); - BoneWeights->SetTuple( VertexIndex, VertexBoneWeights ); - } - FirstVertex = LastVertex; - } - - // Add per-vertex per-triangle data. - uint32 IndexCount = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - //Skipped section - if (SkipSection(SectionIndex)) - { - continue; - } - - const FSkelMeshSection& Section = LODModel.Sections[ SectionIndex ]; - const uint32 FirstIndex = Section.BaseIndex; - const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3; - - for ( uint32 Index = FirstIndex; Index < LastIndex; ++Index ) - { - uint32 VertexIndex = LODModel.IndexBuffer[ Index ]; - FSoftSkinVertex& Vertex = Vertices[ VertexIndex ]; - - FVector Normal = Vertex.TangentZ; - Normal = GetConversionMatrix().TransformPosition(Normal); - - FVector Tangent = Vertex.TangentX; - Tangent = GetConversionMatrix().TransformVector(Tangent); - - FVector Bitangent = Vertex.TangentY; - Bitangent = GetConversionMatrix().TransformVector(Bitangent); - - - Indices->SetItem(IndexCount, VertexIndex ); - Normals->SetTuple(IndexCount, (float*)&Normal ); - Tangents->SetTuple(IndexCount, (float*)&Tangent ); - Bitangents->SetTuple(IndexCount, (float*)&Bitangent ); - - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - FVector2D TexCoord = Vertex.UVs[TexCoordIndex]; - TexCoord.X = FMath::Clamp(TexCoord.X, -1024.0f, 1024.0f); - TexCoord.Y = FMath::Clamp(TexCoord.Y, -1024.0f, 1024.0f); - TexCoords[TexCoordIndex]->SetTuple(IndexCount, (float*)&TexCoord ); - } - - Colors->SetTuple(IndexCount, (UCHAR*)&Vertex.Color ); - IndexCount++; - } - } - - // Add per-triangle data. - uint32 TriangleCount = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - //Skipped section - if (SkipSection(SectionIndex)) - { - continue; - } - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - const uint32 FirstTriIndex = Section.BaseIndex / 3; - const uint32 LastTriIndex = FirstTriIndex + Section.NumTriangles; - const uint16 MaterialIndex = Section.MaterialIndex; - for ( uint32 TriIndex = FirstTriIndex; TriIndex < LastTriIndex; ++TriIndex ) - { - MaterialIndices->SetItem(TriangleCount, MaterialIndex ); - check( MaterialIndices->GetItem(TriangleCount) == MaterialIndex ); - TriangleCount++; - } - } - - // Create a new simplygon scene mesh object - SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh(); - - // Assign the geometry data to the mesh - Mesh->SetGeometry( GeometryData ); - - // Add Mesh to the scene - Scene->GetRootNode()->AddChild( Mesh ); - - return GeometryData; - } - - /** - * Holds data needed to create skeletal mesh skinning streams. - */ - struct FSkeletalMeshData - { - TArray Influences; - TArray Wedges; - TArray Faces; - TArray Points; - uint32 TexCoordCount; - }; - - /** - * Extracts mesh data from the Simplygon geometry representation in to a set of data usable by skeletal meshes. - * @param GeometryData the Simplygon geometry. - * @param MeshData the skeletal mesh output data. - */ - void ExtractSkeletalDataFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, FSkeletalMeshData& MeshData ) - { - TArray PointNormals; - TArray PointList; - TArray PointInfluenceMap; - - check( GeometryData ); - - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - SimplygonSDK::spValueArray VertexColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL ); - SimplygonSDK::spUnsignedCharArray VertexColors = SimplygonSDK::IUnsignedCharArray::SafeCast( VertexColorValues ); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds(); - SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights(); - SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS]; - uint32 TexCoordCount = 0; - for( uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; ++TexCoordIndex ) - { - TexCoords[TexCoordIndex] = GeometryData->GetTexCoords(TexCoordIndex); - if ( TexCoords[TexCoordIndex] == NULL ) - { - break; - } - TexCoordCount++; - } - MeshData.TexCoordCount = TexCoordCount; - - check( Positions ); - check( Indices ); - -// check( MaterialIndices ); - - check( Normals ); - check( Tangents ); - check( Bitangents ); - check( BoneIds ); - check( BoneWeights ); - - const bool bHaveColors = (VertexColors != NULL); - const uint32 VertexCount = GeometryData->GetVertexCount(); - const uint32 TriCount = GeometryData->GetTriangleCount(); - - // Initialize the lists of points, wedges, and faces. - MeshData.Points.AddZeroed( VertexCount ); - PointNormals.AddZeroed( VertexCount ); - PointList.Reserve( VertexCount ); - PointInfluenceMap.Reserve( VertexCount ); - for ( uint32 PointIndex = 0; PointIndex < VertexCount; ++PointIndex ) - { - PointList.Add( INDEX_NONE ); - PointInfluenceMap.Add( INDEX_NONE ); - } - MeshData.Wedges.AddZeroed( TriCount * 3 ); - MeshData.Faces.AddZeroed( TriCount ); - - //The number of influences may have changed if we have specified a max number of bones per vertex. - uint32 NumOfInfluences = FMath::Min( BoneIds->GetTupleSize() , MAX_TOTAL_INFLUENCES ); - - SimplygonSDK::spRealData sgPositionData = SDK->CreateRealData(); - SimplygonSDK::spRidData sgBoneIdsData = SDK->CreateRidData(); - SimplygonSDK::spRealData sgBoneWeightsData = SDK->CreateRealData(); - SimplygonSDK::spRealData sgTangentData = SDK->CreateRealData(); - SimplygonSDK::spRealData sgTexCoordData = SDK->CreateRealData(); - SimplygonSDK::spUnsignedCharData sgVertexColorData = SDK->CreateUnsignedCharData(); - - // Per-vertex data. - for ( uint32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex ) - { - FVector& Point = MeshData.Points[ VertexIndex ]; - //SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES]; - //SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES]; - - //Positions->GetTuple( VertexIndex, (float*)&Point ); - Positions->GetTuple( VertexIndex, sgPositionData ); - SimplygonSDK::real* sgPosition = sgPositionData->GetData(); - Point = FVector(sgPosition[0], sgPosition[1], sgPosition[2]); - - - //BoneIds->GetTuple( VertexIndex, VertexBoneIds ); - BoneIds->GetTuple(VertexIndex, sgBoneIdsData); - SimplygonSDK::rid* sgBoneId = sgBoneIdsData->GetData(); - - - //BoneWeights->GetTuple( VertexIndex, VertexBoneWeights ); - BoneWeights->GetTuple(VertexIndex, sgBoneWeightsData); - SimplygonSDK::real* sgBoneWeights = sgBoneWeightsData->GetData(); - - PointInfluenceMap[ VertexIndex ] = (uint32)MeshData.Influences.Num(); - for ( uint32 InfluenceIndex = 0; InfluenceIndex < NumOfInfluences; ++InfluenceIndex ) - { - const uint16 BoneIndex = sgBoneId[InfluenceIndex]; - const float BoneWeight = sgBoneWeights[InfluenceIndex]; - if ( InfluenceIndex == 0 || BoneWeight > 0.0f ) - { - SkeletalMeshImportData::FVertInfluence* VertInfluence = new(MeshData.Influences) SkeletalMeshImportData::FVertInfluence; - VertInfluence->BoneIndex = BoneIndex; - VertInfluence->Weight = BoneWeight; - VertInfluence->VertIndex = VertexIndex; - } - } - } - - // Per-triangle and per-corner data. - for ( uint32 TriIndex = 0; TriIndex < TriCount; ++TriIndex ) - { - // Per-triangle data. - SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[ TriIndex ]; - - Face.MeshMaterialIndex = MaterialIndices ? MaterialIndices->GetItem( TriIndex ) : 0; - - - // Per-corner data. - for( uint32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex ) - { - const uint32 WedgeIndex = TriIndex * 3 + CornerIndex; - const uint32 BasePointIndex = (uint32)Indices->GetItem( WedgeIndex ); - uint32 PointIndex = BasePointIndex; - - check( BasePointIndex < (uint32)PointList.Num() ); - - // Duplicate points where needed to create hard edges. - FVector WedgeNormal; - //Normals->GetTuple( WedgeIndex, (float*)&WedgeNormal ); - Normals->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgNormal = sgTangentData->GetData(); - WedgeNormal = FVector(sgNormal[0], sgNormal[1], sgNormal[2]); - WedgeNormal.Normalize(); - - FVector WedgeTangent, WedgeBitangent; - //Tangents->GetTuple( WedgeIndex, (float*)&WedgeTangent ); - Tangents->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgTangent = sgTangentData->GetData(); - WedgeTangent = FVector(sgTangent[0], sgTangent[1], sgTangent[2]); - - //Bitangents->GetTuple( WedgeIndex, (float*)&WedgeBitangent ); - Bitangents->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgBitangent = sgTangentData->GetData(); - WedgeBitangent = FVector(sgBitangent[0], sgBitangent[1], sgBitangent[2]); - - Face.TangentX[CornerIndex] = WedgeTangent; - Face.TangentY[CornerIndex] = WedgeBitangent; - Face.TangentZ[CornerIndex] = WedgeNormal; - - - FVector PointNormal = PointNormals[ PointIndex ]; - if ( PointNormal.SizeSquared() < KINDA_SMALL_NUMBER ) - { - PointNormals[ PointIndex ] = WedgeNormal; - } - else - { - while ( (PointNormal | WedgeNormal) - 1.0f < -KINDA_SMALL_NUMBER ) - { - PointIndex = PointList[ PointIndex ]; - if ( PointIndex == INDEX_NONE ) - { - break; - } - check( PointIndex < (uint32)PointList.Num() ); - PointNormal = PointNormals[ PointIndex ]; - } - - if ( PointIndex == INDEX_NONE ) - { - FVector Point = MeshData.Points[ BasePointIndex ]; - PointIndex = MeshData.Points.Add( Point ); - check( PointIndex == (uint32)PointList.Num() ); - check( PointIndex == (uint32)PointInfluenceMap.Num() ); - check( PointIndex == (uint32)PointNormals.Num() ); - PointNormals.Add( WedgeNormal ); - uint32 NextPointIndex = PointList[ BasePointIndex ]; - check( NextPointIndex < (uint32)PointList.Num() || NextPointIndex == INDEX_NONE ); - PointList[ BasePointIndex ] = PointIndex; - PointList.Add( NextPointIndex ); - PointInfluenceMap.Add( (uint32)MeshData.Influences.Num() ); - - int32 InfluenceIndex = PointInfluenceMap[ BasePointIndex ]; - while ( MeshData.Influences[ InfluenceIndex ].VertIndex == BasePointIndex ) - { - SkeletalMeshImportData::FVertInfluence* NewVertInfluence = new( MeshData.Influences ) SkeletalMeshImportData::FVertInfluence; - NewVertInfluence->BoneIndex = MeshData.Influences[ InfluenceIndex ].BoneIndex; - NewVertInfluence->Weight = MeshData.Influences[ InfluenceIndex ].Weight; - NewVertInfluence->VertIndex = PointIndex; - InfluenceIndex++; - } - - check( PointNormals.Num() == MeshData.Points.Num() ); - check( PointList.Num() == MeshData.Points.Num() ); - check( PointInfluenceMap.Num() == MeshData.Points.Num() ); - } - } - - check( PointIndex != INDEX_NONE ); - check( ( MeshData.Points[ PointIndex ] - MeshData.Points[ BasePointIndex ] ).SizeSquared() == 0.0f ); - - SkeletalMeshImportData::FMeshWedge& Wedge = MeshData.Wedges[ WedgeIndex ]; - Wedge.iVertex = PointIndex; - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - //TexCoords[TexCoordIndex]->GetTuple( WedgeIndex, (float*)&Wedge.UVs[TexCoordIndex] ); - TexCoords[TexCoordIndex]->GetTuple(WedgeIndex, sgTexCoordData); - SimplygonSDK::real* sgTexCoord = sgTexCoordData->GetData(); - Wedge.UVs[TexCoordIndex] = FVector2D(sgTexCoordData[0], sgTexCoordData[1]); - } - - if( bHaveColors ) - { - //VertexColors->GetTuple( WedgeIndex, (uint8 *)&Wedge.Color ); - VertexColors->GetTuple(WedgeIndex, sgVertexColorData); - uint8* sgColors = sgVertexColorData->GetData(); - Wedge.Color = FColor(sgColors[2], sgColors[1], sgColors[0], sgColors[3]); - } - else - { - Wedge.Color = FColor( 255, 255, 255, 255 ); - } - - Face.iWedge[CornerIndex] = WedgeIndex; - } - } - } - - /** - * Creates a skeletal mesh LOD model from the Simplygon geometry representation. - * @param GeometryData the Simplygon geometry representation from which to create a skeletal mesh LOD. - * @param SkeletalMesh the skeletal mesh in to which the LOD model will be added. - * @param NewModel the LOD model in to which the geometry will be stored. - */ - void CreateSkeletalLODModelFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, const FReferenceSkeleton& RefSkeleton, FSkeletalMeshLODModel* NewModel ) - { - check( GeometryData ); - - FSkeletalMeshData MeshData; - ExtractSkeletalDataFromGeometry( GeometryData, MeshData ); - - - TArray& Vertices = MeshData.Points; - for (int32 VertexIndex = 0; VertexIndex < MeshData.Points.Num(); ++VertexIndex) - { - Vertices[VertexIndex] = GetConversionMatrix().InverseTransformPosition(Vertices[VertexIndex]); - } - - for (int32 FaceIndex = 0; FaceIndex < MeshData.Faces.Num(); ++FaceIndex) - { - SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[FaceIndex]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - Face.TangentX[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentX[CornerIndex]); - Face.TangentY[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentY[CornerIndex]); - Face.TangentZ[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentZ[CornerIndex]); - } - } - - // Create dummy map of 'point to original' - TArray DummyMap; - DummyMap.AddUninitialized(MeshData.Points.Num()); - for(int32 PointIdx = 0; PointIdx("MeshUtilities"); - // Create skinning streams for NewModel. - MeshUtilities.BuildSkeletalMesh( - *NewModel, - RefSkeleton, - MeshData.Influences, - MeshData.Wedges, - MeshData.Faces, - MeshData.Points, - DummyMap, - Options - ); - - // Set texture coordinate count on the new model. - NewModel->NumTexCoords = MeshData.TexCoordCount; - } - - /** - * Sets reduction settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param ReductionSettings The reduction settings to set for Simplygon. - */ - void SetReductionSettings( const FSkeletalMeshOptimizationSettings& Settings, float BoundsRadius, int32 SourceTriCount, SimplygonSDK::spReductionSettings ReductionSettings ) - { - // Compute max deviation from quality. - float MaxDeviation = (Settings.ReductionMethod != SMOT_NumOfTriangles)? Settings.MaxDeviationPercentage * BoundsRadius : SimplygonSDK::REAL_MAX; - - // Set the reduction ratio such that at least 1 triangle or 1% of the original triangles remain, whichever is larger. - float MinReductionRatio = FMath::Max(1.0f / SourceTriCount, 0.01f); - float ReductionRatio = MinReductionRatio; - - // if reduction is not deviate, we set the percentage - if (Settings.ReductionMethod != SMOT_MaxDeviation) - { - ReductionRatio = FMath::Clamp(Settings.NumOfTrianglesPercentage, MinReductionRatio, 1.f); - } - - unsigned int ReductionTarget = 0; - unsigned int StopCondition = SimplygonSDK::SG_STOPCONDITION_ANY; - switch (Settings.ReductionMethod) - { - case SMOT_MaxDeviation: - // we still give triangle ratio because otherwise you won't get any triangle back and crash render resource - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION; - break; - case SMOT_NumOfTriangles: - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO; - break; - case SMOT_TriangleOrDeviation: - default: - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION; - break; - } - - const float ImportanceTable[] = - { - 0.0f, // OFF - 0.125f, // Lowest - 0.35f, // Low, - 1.0f, // Normal - 2.8f, // High - 8.0f, // Highest - }; - - static_assert(ARRAY_COUNT(ImportanceTable) == SMOI_MAX, "Bad importance table size."); - check( Settings.SilhouetteImportance < SMOI_MAX ); - check( Settings.TextureImportance < SMOI_MAX ); - check( Settings.ShadingImportance < SMOI_MAX ); - check( Settings.SkinningImportance < SMOI_MAX ); - - ReductionSettings->SetStopCondition(StopCondition); - ReductionSettings->SetReductionTargets(ReductionTarget); - ReductionSettings->SetMaxDeviation( MaxDeviation ); - ReductionSettings->SetTriangleRatio( ReductionRatio ); - ReductionSettings->SetGeometryImportance( ImportanceTable[Settings.SilhouetteImportance] ); - ReductionSettings->SetTextureImportance( ImportanceTable[Settings.TextureImportance] ); - ReductionSettings->SetMaterialImportance( ImportanceTable[Settings.TextureImportance] ); - ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance] ); - ReductionSettings->SetSkinningImportance( ImportanceTable[Settings.SkinningImportance] ); - - ReductionSettings->SetDataCreationPreferences(2); //2 = reposition vertices enabled - ReductionSettings->SetGenerateGeomorphData(true); - } - - /** - * Sets vertex normal settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param NormalSettings The normal settings to set for Simplygon. - */ - void SetNormalSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spNormalCalculationSettings NormalSettings ) - { - NormalSettings->SetReplaceNormals( Settings.bRecalcNormals ); - NormalSettings->SetScaleByArea( false ); - NormalSettings->SetScaleByAngle( false ); - NormalSettings->SetHardEdgeAngleInRadians(FMath::DegreesToRadians(Settings.NormalsThreshold)); - } - - /** - * Sets Bone Lod settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param NormalSettings The Bone LOD to set for Simplygon. - */ - void SetBoneSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spBoneSettings BoneSettings) - { - BoneSettings->SetBoneReductionTargets( SimplygonSDK::SG_BONEREDUCTIONTARGET_BONERATIO ); - BoneSettings->SetBoneRatio ( 1.f ); - BoneSettings->SetMaxBonePerVertex( Settings.MaxBonesPerVertex ); - BoneSettings->SetLimitBonesPerVertex(true); - BoneSettings->SetUseBoneReducer(true); - } - - /** - * ProxyLOD Related Methods - */ - void SetMaterialChannelData( - const TArray& InSamples, - FIntPoint InTextureSize, - SimplygonSDK::spMaterial& InSGMaterial, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - const char* SGMaterialChannelName) - { - if (InSamples.Num() > 1) - { - int32 NumTexels = InTextureSize.X * InTextureSize.Y; - SimplygonSDK::spImageData ImageData = SDK->CreateImageData(); - ImageData->AddColors(SimplygonSDK::TYPES_ID_UCHAR, SimplygonSDK::SG_IMAGEDATA_FORMAT_RGBA); - ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y); - SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast(ImageData->GetColors()); - - //Set the texture data to simplygon color data texel per texel - ImageColors->SetTupleCount(NumTexels); - for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++) - { - // BGRA -> RGBA - uint8 Texel[4]; - Texel[0] = InSamples[TexelIndex].R; - Texel[1] = InSamples[TexelIndex].G; - Texel[2] = InSamples[TexelIndex].B; - - if (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE || - SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR) - { - Texel[3] = InSamples[TexelIndex].A; - } - else - { - Texel[3] = FColor::White.A; - } - - ImageColors->SetTuple(TexelIndex, Texel); - } - - //@third party BEGIN SIMPLYGON - // Need to add a texture to the texture table in order to be able - // to reference it in the shading network. - FGuid TextureGuid = FGuid::NewGuid(); - SimplygonSDK::spTexture Texture = SDK->CreateTexture(); - Texture->SetImageData(ImageData); - Texture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureTable->AddTexture(Texture); - - //Create texture node - SimplygonSDK::spShadingTextureNode TextureNode = SDK->CreateShadingTextureNode(); - TextureNode->SetTextureLevel(0); // UV Coordinate to use - TextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureNode->SetUseSRGB(false); - //hook node into desired channel. - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, TextureNode); - //@third party END SIMPLYGON - - - - } - else if (InSamples.Num() == 1) - { - //@third party BEGIN SIMPLYGON - // Setup a color node - SimplygonSDK::spShadingColorNode ColorNode = SDK->CreateShadingColorNode(); - ColorNode->SetColor(InSamples[0].R / 255.0f, InSamples[0].G / 255.0f, InSamples[0].B / 255.0f, 1.0f); - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, ColorNode); - //@third party END SIMPLYGON - - } - else - { - //@third party BEGIN SIMPLYGON - SimplygonSDK::spShadingColorNode ColorNode = SDK->CreateShadingColorNode(); - ColorNode->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, ColorNode); - //@third party END SIMPLYGON - - } - } - - //Material conversions - //@third party BEGIN SIMPLYGON - void GetMaterialChannelData(const SimplygonSDK::spMaterial& InSGMaterial, const SimplygonSDK::spTextureTable& TextureTable, const char* SGMaterialChannelName, TArray& OutSamples, FIntPoint& OutTextureSize) - //@third party END SIMPLYGON - - { - //@third party BEGIN SIMPLYGON - SimplygonSDK::spShadingNode ExitNode = InSGMaterial->GetShadingNetwork(SGMaterialChannelName); - if (ExitNode == nullptr) - { - //Current channel has not been baked - return; - } - - SimplygonSDK::spShadingTextureNode TextureNode = SimplygonSDK::Cast(ExitNode); - SimplygonSDK::rid TextureId = TextureTable->FindTextureId(TextureNode->GetTextureName()); - - if (TextureId < 0) - { - UE_LOG(LogSimplygon, Warning, TEXT("Could not retrieve texture for texture table for channel %s"), ANSI_TO_TCHAR(SGMaterialChannelName)); - return; - } - - SimplygonSDK::spTexture Texture = TextureTable->GetTexture(TextureId); - SimplygonSDK::spImageData SGChannelData = Texture->GetImageData(); - //@third party END SIMPLYGON - - - if (SGChannelData) - { - SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast(SGChannelData->GetColors()); - - OutTextureSize.X = SGChannelData->GetXSize(); - OutTextureSize.Y = SGChannelData->GetYSize(); - - int32 TexelsCount = OutTextureSize.X*OutTextureSize.Y; - OutSamples.Empty(TexelsCount); - OutSamples.AddUninitialized(TexelsCount); - - SimplygonSDK::spUnsignedCharData sgImageCharData = SDK->CreateUnsignedCharData(); - for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex) - { - //uint8 ColorData[4]; - //ImageColors->GetTuple(TexelIndex, (unsigned char*)&ColorData); - - ImageColors->GetTuple(TexelIndex, sgImageCharData); - uint8* ColorData = sgImageCharData->GetData(); - - OutSamples[TexelIndex].R = ColorData[0]; - OutSamples[TexelIndex].G = ColorData[1]; - OutSamples[TexelIndex].B = ColorData[2]; - if (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE || - SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR) - { - OutSamples[TexelIndex].A = ColorData[3]; - } - else - { - OutSamples[TexelIndex].A = FColor::White.A; - } - } - } - } - - void CreateFlattenMaterialFromSGMaterial( - SimplygonSDK::spMaterialTable& InSGMaterialTable, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - FFlattenMaterial& OutMaterial) - { - FIntPoint Size; - SimplygonSDK::spMaterial SGMaterial = InSGMaterialTable->GetMaterial(0); - - // Diffuse - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - - // Normal - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - - // Metallic - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_METALLIC, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - - // Roughness - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_ROUGHNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - - // Specular - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_SPECULAR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - } - - void CreateFlattenMaterialFromSGMaterial( - SimplygonSDK::spMaterial& SGMaterial, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - FFlattenMaterial& OutMaterial) - { - FIntPoint Size; - - // Diffuse - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - - // Normal - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - - // Metallic - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - - // Roughness - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - - // Specular - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - - // Opacity - Size = FIntPoint::ZeroValue; -#if USE_USER_OPACITY_CHANNEL - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); -#else - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); -#endif - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, Size); - - // Opacity mask - Size = FIntPoint::ZeroValue; -#if USE_USER_OPACITY_CHANNEL - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_OPACITY_MASK, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); -#else - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); -#endif - OutMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, Size); - - // Emissive - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, Size); - - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::SubSurface, Size); - - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::AmbientOcclusion, Size); - } - - bool CreateSGMaterialFromFlattenMaterial( - const TArray& InputMaterials, - const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMaterialTable& OutSGMaterialTable, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& OutTextureTable, - //@third party END SIMPLYGON - - bool bReleaseInputMaterials) - { - if (InputMaterials.Num() == 0) - { - //If there are no materials, feed Simplygon with a default material instead. - UE_LOG(LogSimplygon, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated.")); - return false; - } - - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - SimplygonSDK::spMaterial SGMaterial = SDK->CreateMaterial(); - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - -#if USE_USER_OPACITY_CHANNEL - if (!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY)) - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY); - - if (!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK)) - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK); -#endif - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - - SGMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("Material%d"), MaterialIndex))); - - // Does current material have BaseColor? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse)) - { - //@third party BEGIN SIMPLYGON - //NOTE: Currently bBakeVertexData is marked as deprecated in FMaterialProxySetting. We can add the support before 4.15 lockdown - - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR); - } - - // Does current material have Metallic? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS); - } - - // Does current material have Specular? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR); - } - - // Does current material have Roughness? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS); - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT); - } - - //Does current material have a normalmap? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS); - } - - // Does current material have Opacity? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity)) - { -#if USE_USER_OPACITY_CHANNEL - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_OPACITY); -#else - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY); -#endif - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask)) - { -#if USE_USER_OPACITY_CHANNEL - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_OPACITY_MASK); -#else - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY); -#endif - } - - if (FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num()) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE); - } - else - { - TArray BlackEmissive; - BlackEmissive.AddZeroed(1); - //@third party BEGIN SIMPLYGON - SetMaterialChannelData(BlackEmissive, FIntPoint(1,1), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE); - //@third party END SIMPLYGON - - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::SubSurface)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::SubSurface), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - } - - OutSGMaterialTable->AddMaterial(SGMaterial); - - if (bReleaseInputMaterials) - { - // Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here - // and above the call chain. - const_cast(&FlattenMaterial)->ReleaseData(); - } - } - - return true; - } - - /** - * The method converts ESimplygonTextureSamplingQuality - * @param InSamplingQuality The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @return - */ - uint8 GetSamples(ESimplygonTextureSamplingQuality::Type InSamplingQuality) - { - switch (InSamplingQuality) - { - case ESimplygonTextureSamplingQuality::Poor: - return 1; - case ESimplygonTextureSamplingQuality::Low: - return 2; - case ESimplygonTextureSamplingQuality::Medium: - return 6; - case ESimplygonTextureSamplingQuality::High: - return 8; - } - return 1; - } - - uint8 ConvertColorChannelToInt(ESimplygonColorChannels::Type InSamplingQuality) - { - switch (InSamplingQuality) - { - case ESimplygonColorChannels::RGBA: - return 4; - case ESimplygonColorChannels::RGB: - return 3; - case ESimplygonColorChannels::L: - return 1; - } - - return 3; - } - - /** - * Use Simplygon Color Caster to Cast a Color Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastColorChannel(const FSimplygonChannelCastingSettings& InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData) - { - SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster(); - - cast->SetColorType( GetSimplygonMaterialChannel(InCasterSettings.MaterialChannel) ); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( InCasterSettings.BitsPerChannel ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetOutputImage(OutColorData); - cast->SetBakeOpacityInAlpha(false); - //cast->SetBakeVertexColors(InCasterSettings.bBakeVertexColors); - //@third party BEGIN SIMPLYGON - cast->SetOutputSRGB(InCasterSettings.bUseSRGB); - //@third party END SIMPLYGON - - cast->CastMaterials(); // Fetch! - } - - /** - * Use Simplygon Normal Caster to Cast a Normals Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastNormalsChannel(FSimplygonChannelCastingSettings InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData, - int32 DestinationMaterialIndex = -1) - { - SimplygonSDK::spNormalCaster cast = SDK->CreateNormalCaster(); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetFlipBackfacingNormals(InCasterSettings.bFlipBackfacingNormals); - cast->SetGenerateTangentSpaceNormals(InCasterSettings.bUseTangentSpaceNormals); - cast->SetFlipGreen(false); - //cast->SetNormalMapTextureLevel(); - cast->SetDestMaterialId(DestinationMaterialIndex); - cast->SetOutputImage(OutColorData); - cast->CastMaterials(); // Fetch! - } - - /** - * Use Simplygon Opacity Caster to Cast an Opacity Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastOpacityChannel(FSimplygonChannelCastingSettings InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData) - { - SimplygonSDK::spOpacityCaster cast = SDK->CreateOpacityCaster(); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetOutputImage(OutColorData); - cast->CastMaterials(); // Fetch! - } - - /** - * Sets Mapping Image Setting for Simplygon. - * @param InMaterialLODSettings The MaterialLOD Settings used for setting up Simplygon MappingImageSetting. - * @param InMappingImageSettings The Simplygon MappingImage Settings that is being setup. - */ - void SetupMappingImage(const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMappingImageSettings InMappingImageSettings, - bool InAggregateProcess, - bool InRemoveUVs) - { - if (!InMaterialLODSettings.bActive) - { - return; - } - - int32 NumInputMaterials = 1; - int32 NumOutputMaterials = 1; - - //if (InMaterialLODSettings.bReuseExistingCharts || InAggregateProcess) - we're always using UVs from the mesh because new UVs are already generated with GenerateUniqueUVs() call - if (InAggregateProcess || NumOutputMaterials > 1) - { - InMappingImageSettings->SetTexCoordGeneratorType(SimplygonSDK::SG_TEXCOORDGENERATORTYPE_CHARTAGGREGATOR); - //InMappingImageSettings->SetChartAggregatorMode(SimplygonSDK::SG_CHARTAGGREGATORMODE_SURFACEAREA); - } - else - { - InMappingImageSettings->SetTexCoordGeneratorType(SimplygonSDK::SG_TEXCOORDGENERATORTYPE_PARAMETERIZER); - } - - InMappingImageSettings->SetGenerateMappingImage(true); - InMappingImageSettings->SetTexCoordLevel(0); - if (NumOutputMaterials > 1) //-V547 - { - InMappingImageSettings->SetGenerateTexCoords(true); //! check if it is possible to avoid this - } - - - for (int32 MaterialIndex = 0; MaterialIndex < NumOutputMaterials; MaterialIndex++) - { - InMappingImageSettings->SetGutterSpace(MaterialIndex, InMaterialLODSettings.GutterSpace); - InMappingImageSettings->SetMultisamplingLevel(MaterialIndex, GetSamples(InMaterialLODSettings.SamplingQuality)); - } - - if (InRemoveUVs) - { - InMappingImageSettings->SetUseFullRetexturing(true); - } - - InMappingImageSettings->SetGenerateTangents(false); - - bool bAutomaticSizes = InMaterialLODSettings.bUseAutomaticSizes; - InMappingImageSettings->SetUseAutomaticTextureSize(bAutomaticSizes); - - if (!bAutomaticSizes) - { - for (int32 MaterialIndex = 0; MaterialIndex < NumOutputMaterials; MaterialIndex++) - { - InMappingImageSettings->SetWidth(MaterialIndex, InMaterialLODSettings.GetTextureResolutionFromEnum(InMaterialLODSettings.TextureWidth)); - InMappingImageSettings->SetHeight(MaterialIndex, InMaterialLODSettings.GetTextureResolutionFromEnum(InMaterialLODSettings.TextureHeight)); - } - } - else - { - InMappingImageSettings->SetForcePower2Texture(true); - } - } - - SimplygonSDK::spMaterial RebakeMaterials(const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InSGMaterialTable, - //@third party BEGIN SIMPLYGON - const SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - int32 DestinationMaterialIndex = -1) - { - SimplygonSDK::spMaterial OutMaterial = SDK->CreateMaterial(); -#if USE_USER_OPACITY_CHANNEL - if(!OutMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY)) - { - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY); - } - - if (!OutMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK)) - { - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK); - } -#endif - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - - for(int ChannelIndex=0; ChannelIndex < InMaterialLODSettings.ChannelsToCast.Num(); ChannelIndex++) - { - FSimplygonChannelCastingSettings CasterSetting = InMaterialLODSettings.ChannelsToCast[ChannelIndex]; - - if(CasterSetting.bActive) - { - SimplygonSDK::spImageData OutColorData = SDK->CreateImageData(); - - switch(CasterSetting.Caster) - { - case ESimplygonCasterType::Color: - CastColorChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData); - break; - case ESimplygonCasterType::Normals: - CastNormalsChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData, DestinationMaterialIndex); - break; - case ESimplygonCasterType::Opacity: -#if USE_USER_OPACITY_CHANNEL - CastColorChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData); -#else - CastOpacityChannel(CasterSetting,InMappingImage,InSGMaterialTable,OutColorData, TextureTable); -#endif - break; - default: - break; - } - - //@third party BEGIN SIMPLYGON - FGuid TextureGuid = FGuid::NewGuid(); - SimplygonSDK::spTexture BakedTexture = SDK->CreateTexture(); - BakedTexture->SetImageData(OutColorData); - BakedTexture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureTable->AddTexture(BakedTexture); - - SimplygonSDK::spShadingTextureNode BakedTextureNode = SDK->CreateShadingTextureNode(); - BakedTextureNode->SetTextureLevel(0); - BakedTextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - OutMaterial->SetShadingNetwork(GetSimplygonMaterialChannel(CasterSetting.MaterialChannel), BakedTextureNode); - //@third party END SIMPLYGON - - } - } - - return OutMaterial; - } - - /* - * (1, 0, 0) - * (0, 0, 1) - * (0, 1, 0) - */ - const FMatrix& GetConversionMatrix() - { - static FMatrix m; - static bool bInitialized = false; - if (!bInitialized) - { - m.SetIdentity(); - m.M[1][1] = 0.0f; - m.M[2][1] = 1.0f; - - m.M[1][2] = 1.0f; - m.M[2][2] = 0.0f; - bInitialized = true; - } - - return m; - } - - void ValidateGeometry(SimplygonSDK::spGeometryValidator& GeometryValidator, SimplygonSDK::spGeometryData& GeometryData) - { - bool bGeometryValid = GeometryValidator->ValidateGeometry(GeometryData); - if (!bGeometryValid) - { - uint32 error_val = GeometryValidator->GetErrorValue(); - if ((error_val & SimplygonSDK::SG_VALIDATIONERROR_ZERO_LENGTH_NORMAL) != 0) - { - SimplygonSDK::spNormalRepairer rep = SDK->CreateNormalRepairer(); - rep->SetRepairOnlyInvalidNormals(true); - rep->SetGeometry(GeometryData); - rep->RunProcessing(); - } - else - { - FString ErrorMessage = ANSI_TO_TCHAR(GeometryValidator->GetErrorString()); - - // Downgrade geometry validation errors to info - ErrorMessage = ErrorMessage.Replace(TEXT("Error:"), TEXT("Info:")); - - UE_LOG(LogSimplygon, Log, TEXT("Invalid mesh data: %s."), *ErrorMessage); - } - } - } -}; - -TUniquePtr GSimplygonMeshReduction; - - -void FSimplygonMeshReductionModule::StartupModule() -{ - GSimplygonMeshReduction.Reset(FSimplygonMeshReduction::Create()); - IModularFeatures::Get().RegisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -void FSimplygonMeshReductionModule::ShutdownModule() -{ - FSimplygonMeshReduction::Destroy(); - GSimplygonMeshReduction = nullptr; - IModularFeatures::Get().UnregisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -#define USE_SIMPLYGON_SWARM 0 - -IMeshReduction* FSimplygonMeshReductionModule::GetStaticMeshReductionInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -IMeshReduction* FSimplygonMeshReductionModule::GetSkeletalMeshReductionInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -IMeshMerging* FSimplygonMeshReductionModule::GetMeshMergingInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -class IMeshMerging* FSimplygonMeshReductionModule::GetDistributedMeshMergingInterface() -{ - return nullptr; -} - -FString FSimplygonMeshReductionModule::GetName() -{ - return FString("SimplygonMeshReduction"); -} - -#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h b/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h deleted file mode 100644 index 3f065031114e..000000000000 --- a/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Containers/EnumAsByte.h" -#include "Engine/MaterialMerging.h" -#include "SimplygonSDK.h" - -#include "SimplygonTypes.generated.h" - -//@third party code BEGIN SIMPLYGON -#define USE_USER_OPACITY_CHANNEL 1 -#if USE_USER_OPACITY_CHANNEL -static const char* USER_MATERIAL_CHANNEL_OPACITY = "UserOpacity"; -static const char* USER_MATERIAL_CHANNEL_OPACITY_MASK = "UserOpacityMask"; -#endif -//@third party code END SIMPLYGON - -// User defined material channel used for baking out sub surface colours -static const char* USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR = "UserSubSurfaceColor"; - -UENUM() -namespace ESimplygonMaterialChannel -{ - enum Type - { - SG_MATERIAL_CHANNEL_AMBIENT UMETA(DisplayName = "Ambient", DisplayValue = "Ambient"), - SG_MATERIAL_CHANNEL_DIFFUSE UMETA(DisplayName = "Diffuse", DisplayValue = "Diffuse"), - SG_MATERIAL_CHANNEL_SPECULAR UMETA(DisplayName = "Specular", DisplayValue = "Specular"), - SG_MATERIAL_CHANNEL_OPACITY UMETA(DisplayName = "Opacity", DisplayValue = "Opacity"), - SG_MATERIAL_CHANNEL_OPACITYMASK UMETA(DisplayName = "OpacityMask", DisplayValue = "OpacityMask"), - SG_MATERIAL_CHANNEL_EMISSIVE UMETA(DisplayName = "Emissive", DisplayValue = "Emissive"), - SG_MATERIAL_CHANNEL_NORMALS UMETA(DisplayName = "Normals", DisplayValue = "Normals"), - SG_MATERIAL_CHANNEL_DISPLACEMENT UMETA(DisplayName = "Displacement", DisplayValue = "Displacement"), - SG_MATERIAL_CHANNEL_BASECOLOR UMETA(DisplayName = "Basecolor", DisplayValue = "Basecolor"), - SG_MATERIAL_CHANNEL_ROUGHNESS UMETA(DisplayName = "Roughness", DisplayValue = "Roughness"), - SG_MATERIAL_CHANNEL_METALLIC UMETA(DisplayName = "Metallic", DisplayValue = "Metallic"), - SG_MATERIAL_CHANNEL_SUBSURFACE UMETA(DisplayName = "SubsurfaceColor", DisplayValue = "SubsurfaceColor") - }; - -} - -static const char* GetSimplygonMaterialChannel(ESimplygonMaterialChannel::Type channel) -{ - if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR) - return SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR) - return SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS) - return SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC) - return SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS) - return SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITY) -#if USE_USER_OPACITY_CHANNEL - return USER_MATERIAL_CHANNEL_OPACITY; -#else - return SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY; -#endif - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITYMASK) -#if USE_USER_OPACITY_CHANNEL - return USER_MATERIAL_CHANNEL_OPACITY_MASK; -#else - return SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY; -#endif - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_EMISSIVE) - return SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SUBSURFACE) - return USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_AMBIENT) - return SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT; - else - { - check(0); - return 0; - } - -} - -UENUM() -namespace ESimplygonLODType -{ - enum Type - { - Reduction, - Remeshing - }; -} - -/* -* Material LOD Type -*/ -UENUM() -namespace EMaterialLODType -{ - enum Type - { - Off, //no material lod - BakeTexture, //combine materials and cast new textures - BakeVertexColor, //combine materials and cast textures into vertex color field instead of baking new textures - Replace - }; -} - -/* -* Texture Stretch -*/ -UENUM() -namespace ESimplygonTextureStrech -{ - enum Type - { - None, - VerySmall, - Small, - Medium, - Large, - VeryLarge - }; -} - -/* -* Type of caster to use Stretch -*/ -UENUM() -namespace ESimplygonCasterType -{ - enum Type - { - None, - Color, //use Color caster - Normals, //use Normals caster - Opacity, //use Opacity caster - }; -} - -/* -* Texture Sampling Quality -*/ -UENUM() -namespace ESimplygonTextureSamplingQuality -{ - enum Type - { - Poor, - Low, - Medium, - High - }; -} - -UENUM() -namespace ESimplygonColorChannels -{ - enum Type - { - RGBA, - RGB, - L - }; -} - - -UENUM() -namespace ESimplygonTextureResolution -{ - enum Type - { - TextureResolution_64 UMETA(DisplayName = "64"), - TextureResolution_128 UMETA(DisplayName = "128"), - TextureResolution_256 UMETA(DisplayName = "256"), - TextureResolution_512 UMETA(DisplayName = "512"), - TextureResolution_1024 UMETA(DisplayName = "1024"), - TextureResolution_2048 UMETA(DisplayName = "2048"), - TextureResolution_4096 UMETA(DisplayName = "4096"), - TextureResolution_8192 UMETA(DisplayName = "8192") - }; -} - -/* -* Desc : The following class stores settings for the simplygon caster. -*/ -USTRUCT() -struct FSimplygonChannelCastingSettings -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte MaterialChannel; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte Caster; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - bool bActive; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte ColorChannels; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - int32 BitsPerChannel; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseSRGB; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bBakeVertexColors; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bFlipBackfacingNormals; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseTangentSpaceNormals; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bFlipGreenChannel; - - FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::Type channel, ESimplygonCasterType::Type caster, ESimplygonColorChannels::Type colorChannels) - : MaterialChannel(channel) - , Caster(caster) - , bActive(false) - , ColorChannels(colorChannels) - , BitsPerChannel(8) - , bUseSRGB(true) - , bBakeVertexColors(false) - , bFlipBackfacingNormals(false) - , bUseTangentSpaceNormals(true) - , bFlipGreenChannel(false) - { - } - - FSimplygonChannelCastingSettings() - : MaterialChannel(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR) - , Caster(ESimplygonCasterType::Color) - , bActive(false) - , ColorChannels(ESimplygonColorChannels::RGB) - , BitsPerChannel(8) - , bUseSRGB(true) - , bBakeVertexColors(false) - , bFlipBackfacingNormals(false) - , bUseTangentSpaceNormals(true) - , bFlipGreenChannel(false) - { - } - - FSimplygonChannelCastingSettings(const FSimplygonChannelCastingSettings& Other) - : MaterialChannel(Other.MaterialChannel) - , Caster(Other.Caster) - , bActive(Other.bActive) - , ColorChannels(Other.ColorChannels) - , BitsPerChannel(Other.BitsPerChannel) - , bUseSRGB(Other.bUseSRGB) - , bBakeVertexColors(Other.bBakeVertexColors) - , bFlipBackfacingNormals(Other.bFlipBackfacingNormals) - , bUseTangentSpaceNormals(Other.bUseTangentSpaceNormals) - , bFlipGreenChannel(Other.bFlipGreenChannel) - { - } - - bool operator==(const FSimplygonChannelCastingSettings& Other) const - { - if (bActive == false && Other.bActive == false) - { - // Ignore other fields when both objects are inactive - return true; - } - - return MaterialChannel == Other.MaterialChannel - && Caster == Other.Caster - && bActive == Other.bActive - && ColorChannels == Other.ColorChannels - && BitsPerChannel == Other.BitsPerChannel - && bUseSRGB == Other.bUseSRGB - && bBakeVertexColors == Other.bBakeVertexColors - && bFlipBackfacingNormals == Other.bFlipBackfacingNormals - && bUseTangentSpaceNormals == Other.bUseTangentSpaceNormals - && bFlipGreenChannel == Other.bFlipGreenChannel; - } - - bool operator!=(const FSimplygonChannelCastingSettings& Other) const - { - return !(*this == Other); - } -}; - - -static const ESimplygonTextureResolution::Type GetResolutionEnum(const int32 InSize) -{ - switch (InSize) - { - case 64: - { - return ESimplygonTextureResolution::TextureResolution_64; - } - - case 128: - { - return ESimplygonTextureResolution::TextureResolution_128; - } - - case 256: - { - return ESimplygonTextureResolution::TextureResolution_256; - } - - case 512: - { - return ESimplygonTextureResolution::TextureResolution_512; - - } - case 1024: - { - return ESimplygonTextureResolution::TextureResolution_1024; - } - case 2048: - { - return ESimplygonTextureResolution::TextureResolution_2048; - } - case 4096: - { - return ESimplygonTextureResolution::TextureResolution_4096; - } - case 8192: - { - return ESimplygonTextureResolution::TextureResolution_8192; - } - - default: - { - check(false); - return ESimplygonTextureResolution::TextureResolution_64; - } - } - - return ESimplygonTextureResolution::TextureResolution_64; -} - -/* -* Desc : The following class stores settings for the simplygon material LOD. Specifically the mapping image -*/ -USTRUCT() -struct FSimplygonMaterialLODSettings -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bActive; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte MaterialLODType; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseAutomaticSizes; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureWidth; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureHeight; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte SamplingQuality; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - int32 GutterSpace; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureStrech; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bReuseExistingCharts; - - UPROPERTY() - TArray ChannelsToCast; - - FSimplygonMaterialLODSettings() - : bActive(false) - , MaterialLODType(EMaterialLODType::BakeTexture) - , bUseAutomaticSizes(false) - , TextureWidth(ESimplygonTextureResolution::Type::TextureResolution_512) - , TextureHeight(ESimplygonTextureResolution::Type::TextureResolution_512) - , SamplingQuality(ESimplygonTextureSamplingQuality::Low) - , GutterSpace(4) - , TextureStrech(ESimplygonTextureStrech::Medium) - , bReuseExistingCharts(false) - { - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS, ESimplygonCasterType::Normals, ESimplygonColorChannels::RGB)); - } - - FSimplygonMaterialLODSettings(const FSimplygonMaterialLODSettings& Other) - : bActive(Other.bActive) - , MaterialLODType(Other.MaterialLODType) - , bUseAutomaticSizes(Other.bUseAutomaticSizes) - , TextureWidth(Other.TextureWidth) - , TextureHeight(Other.TextureHeight) - , SamplingQuality(Other.SamplingQuality) - , GutterSpace(Other.GutterSpace) - , TextureStrech(Other.TextureStrech) - , bReuseExistingCharts(Other.bReuseExistingCharts) - { - ChannelsToCast.Empty(); - for (int ItemIndex = 0; ItemIndex < Other.ChannelsToCast.Num(); ItemIndex++) - { - ChannelsToCast.Add(Other.ChannelsToCast[ItemIndex]); - - } - } - - - - FSimplygonMaterialLODSettings(const FMaterialProxySettings& Settings) - : bActive(true) - , MaterialLODType(EMaterialLODType::BakeTexture) - , bUseAutomaticSizes( Settings.TextureSizingType == TextureSizingType_UseSimplygonAutomaticSizing ) - , TextureWidth(GetResolutionEnum(Settings.TextureSize.X)) - , TextureHeight(GetResolutionEnum(Settings.TextureSize.Y)) - , SamplingQuality(ESimplygonTextureSamplingQuality::High) - , GutterSpace( Settings.GutterSpace ) - , TextureStrech(ESimplygonTextureStrech::Medium) - , bReuseExistingCharts(false) - { - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = true; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bSpecularMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bRoughnessMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bMetallicMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS, ESimplygonCasterType::Normals, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bNormalMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_EMISSIVE, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bEmissiveMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITY, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bOpacityMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITYMASK, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bOpacityMaskMap; - - // TODO, properly render out sub surface data/values - //ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SUBSURFACE, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - //ChannelsToCast.Last().bUseSRGB = false; - //ChannelsToCast.Last().bActive = true; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_AMBIENT, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bAmbientOcclusionMap; - } - - static int32 GetTextureResolutionFromEnum(ESimplygonTextureResolution::Type InResolution) - { - switch (InResolution) - { - case ESimplygonTextureResolution::TextureResolution_64: - return 64; - case ESimplygonTextureResolution::TextureResolution_128: - return 128; - case ESimplygonTextureResolution::TextureResolution_256: - return 256; - case ESimplygonTextureResolution::TextureResolution_512: - return 512; - case ESimplygonTextureResolution::TextureResolution_1024: - return 1024; - case ESimplygonTextureResolution::TextureResolution_2048: - return 2048; - case ESimplygonTextureResolution::TextureResolution_4096: - return 4096; - case ESimplygonTextureResolution::TextureResolution_8192: - return 8192; - - } - return 64; - } - - bool operator==(const FSimplygonMaterialLODSettings& Other) const - { - if (bActive == false && Other.bActive == false) - { - // Ignore other fields when both objects are inactive - return true; - } - - bool areAllElementsEqual = ChannelsToCast.Num() == Other.ChannelsToCast.Num() ? true : false; - - if (areAllElementsEqual) - { - for (int ItemIndex = 0; ItemIndex < ChannelsToCast.Num(); ItemIndex++) - { - if (ChannelsToCast[ItemIndex] != Other.ChannelsToCast[ItemIndex]) - { - areAllElementsEqual = false; - break; - } - if (!areAllElementsEqual) - break; - } - } - - return bActive == Other.bActive - && MaterialLODType == Other.MaterialLODType - && bUseAutomaticSizes == Other.bUseAutomaticSizes - && TextureWidth == Other.TextureWidth - && TextureHeight == Other.TextureHeight - && SamplingQuality == Other.SamplingQuality - && GutterSpace == Other.GutterSpace - && TextureStrech == Other.TextureStrech - && bReuseExistingCharts == Other.bReuseExistingCharts - && areAllElementsEqual == true; - } - - bool operator!=(const FSimplygonMaterialLODSettings& Other) const - { - return !(*this == Other); - } -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs b/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs deleted file mode 100644 index b75099c95846..000000000000 --- a/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using System.IO; -using UnrealBuildTool; - -public class SimplygonMeshReduction : ModuleRules -{ - public SimplygonMeshReduction(ReadOnlyTargetRules Target) : base(Target) - { - PublicIncludePaths.Add("Developer/SimplygonMeshReduction/Public"); - - PrivateDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "MeshDescription", - "MeshDescriptionOperations", - "MaterialUtilities", - "MeshBoneReduction", - "RHI", - "AnimationModifiers", - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "MeshUtilities", - "MeshReductionInterface", - } - ); - - AddEngineThirdPartyPrivateStaticDependencies(Target, "Simplygon"); - - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/Inc/SimplygonSDK.h"; - if (Target.Platform == UnrealTargetPlatform.Win64 && File.Exists(SimplygonPath)) - { - PrecompileForTargets = PrecompileTargetsType.Editor; - } - else - { - PrecompileForTargets = PrecompileTargetsType.None; - } - } -} diff --git a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp b/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp deleted file mode 100644 index 7c203cae4d19..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp +++ /dev/null @@ -1,1341 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "SimplygonRESTClient.h" -#include "HAL/RunnableThread.h" -#include "Editor/EditorPerProjectUserSettings.h" -#include "Dom/JsonObject.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonSerializer.h" -#include "HttpModule.h" -#include "HttpManager.h" - -#define HOSTNAME "http://127.0.0.1" -#define PORT ":55002" - -DEFINE_LOG_CATEGORY_STATIC(LogSimplygonRESTClient, Verbose, All); - -const TCHAR* SIMPLYGON_SWARM_REQUEST_DEBUG_TEMPALTE = TEXT("Error Processing Request %s"); - -FSimplygonSwarmTask::FSimplygonSwarmTask(const FSwarmTaskkData& InTaskData) - : - TaskData(InTaskData), - State(SRS_UNKNOWN), - bProcessingStarted(false), - TimeSinceProcessStart(0.0f) -{ - bMultiPartUploadInitialized = false; - TaskData.TaskUploadComplete = false; - DebugHttpRequestCounter.Set(0); - IsCompleted.AtomicSet(false); - APIKey = TEXT("LOCAL"); - TaskData.JobName = TaskData.JobName.IsEmpty() ? TEXT("UE4_SWARM") : TaskData.JobName; - bEnableDebugLogging = true; -} - -FSimplygonSwarmTask::~FSimplygonSwarmTask() -{ - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Log, TEXT("Destroying Task With Job Id %s"), *JobId); - - TArray> OutPendingRequests; - if (PendingRequests.GetKeys(OutPendingRequests) > 0) - { - for (int32 ItemIndex = 0; ItemIndex < OutPendingRequests.Num(); ++ItemIndex) - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Warning, TEXT("Cancelling pending Request for task with status %d"), (int32)OutPendingRequests[ItemIndex]->GetStatus()); - OutPendingRequests[ItemIndex]->CancelRequest(); - } - } - //check(DebugHttpRequestCounter.GetValue() == 0); - UploadParts.Empty(); - - //unbind the delegates - OnAssetDownloaded().Unbind(); - OnAssetUploaded().Unbind(); - OnSwarmTaskFailed().Unbind(); - - delete TaskData.StateLock; -} - -void FSimplygonSwarmTask::CreateUploadParts(const int32 MaxUploadPartSize) -{ - //create upload parts - UploadParts.Empty(); - TArray fileBlob; - if (FFileHelper::LoadFileToArray(fileBlob, *TaskData.ZipFilePath)) - { - UploadSize = fileBlob.Num(); - if (fileBlob.Num() > MaxUploadPartSize) - { - int32 NumberOfPartsRequried = fileBlob.Num() / MaxUploadPartSize; - int32 RemainingBytes = fileBlob.Num() % MaxUploadPartSize; - - for (int32 PartIndex = 0; PartIndex < NumberOfPartsRequried; ++PartIndex) - { - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - int32 Offset = PartIndex * MaxUploadPartSize * sizeof(uint8); - UploadPartData->Data.AddUninitialized(MaxUploadPartSize); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData() + Offset, MaxUploadPartSize * sizeof(uint8)); - UploadPartData->PartNumber = PartIndex + 1; - UploadPartData->PartUploaded = false; - } - - if (RemainingBytes > 0) - { - //NOTE: need to set Offset before doing a new on UploadParts - int32 Offset = UploadParts.Num() * MaxUploadPartSize; - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - UploadPartData->Data.AddUninitialized(RemainingBytes); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData() + Offset, RemainingBytes * sizeof(uint8)); - UploadPartData->PartNumber = NumberOfPartsRequried + 1; - UploadPartData->PartUploaded = false; - } - } - else - { - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - UploadPartData->Data.AddUninitialized(fileBlob.Num()); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData(), fileBlob.Num() * sizeof(uint8)); - UploadPartData->PartNumber = 1; - UploadPartData->PartUploaded = false; - } - - TotalParts = UploadParts.Num(); - RemainingPartsToUpload.Add(TotalParts); - } -} - -bool FSimplygonSwarmTask::NeedsMultiPartUpload() -{ - return UploadParts.Num() > 1; -} - -SimplygonRESTState FSimplygonSwarmTask::GetState() const -{ - FScopeLock Lock(TaskData.StateLock); - return State; -} - -void FSimplygonSwarmTask::SetHost(FString InHostAddress) -{ - HostName = InHostAddress; -} - -void FSimplygonSwarmTask::EnableDebugLogging() -{ - bEnableDebugLogging = true; -} - -void FSimplygonSwarmTask::SetState(SimplygonRESTState InState) -{ - if (this != nullptr && TaskData.StateLock) - { - FScopeLock Lock(TaskData.StateLock); - - //do not update the state if either of these set is already set - if (InState != State - && (State != SRS_ASSETDOWNLOADED && State != SRS_FAILED)) - { - State = InState; - } - else if ((InState != State) && (State != SRS_FAILED) && (InState == SRS_ASSETDOWNLOADED)) - { - State = SRS_ASSETDOWNLOADED; - this->IsCompleted.AtomicSet(true); - } - else if (InState != State && InState == SRS_FAILED) - { - State = SRS_ASSETDOWNLOADED; - } - - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Error, TEXT("Object in invalid state can not acquire state lock")); - } - -} - -bool FSimplygonSwarmTask::IsFinished() const -{ - FScopeLock Lock(TaskData.StateLock); - return IsCompleted; -} - -bool FSimplygonSwarmTask::ParseJsonMessage(FString InJsonMessage, FSwarmJsonResponse& OutResponseData) -{ - bool sucess = false; - - TSharedPtr JsonParsed; - TSharedRef> JsonReader = TJsonReaderFactory::Create(InJsonMessage); - if (FJsonSerializer::Deserialize(JsonReader, JsonParsed)) - { - FString Status; - if (JsonParsed->HasField("JobId")) - { - JsonParsed->TryGetStringField("JobId", OutResponseData.JobId); - } - - if (JsonParsed->HasField("Status")) - { - OutResponseData.Status = JsonParsed->GetStringField("Status"); - } - - if (JsonParsed->HasField("OutputAssetId")) - { - JsonParsed->TryGetStringField("OutputAssetId", OutResponseData.OutputAssetId); - } - - if (JsonParsed->HasField("AssetId")) - { - JsonParsed->TryGetStringField("AssetId", OutResponseData.AssetId); - } - - if (JsonParsed->HasField("ProgressPercentage")) - { - JsonParsed->TryGetNumberField("ProgressPercentage", OutResponseData.Progress); - } - - if (JsonParsed->HasField("ProgressPercentage")) - { - JsonParsed->TryGetNumberField("ProgressPercentage", OutResponseData.Progress); - } - - if (JsonParsed->HasField("UploadId")) - { - JsonParsed->TryGetStringField("UploadId", OutResponseData.UploadId); - } - - sucess = true; - } - - - return sucess; -} - - -// ~ HTTP Request method to communicate with Simplygon REST Interface - - -void FSimplygonSwarmTask::AccountInfo() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - AddAuthenticationHeader(request); - request->SetURL(FString::Printf(TEXT("%s/2.3/account?apikey=%s"), *HostName, *APIKey)); - request->SetVerb(TEXT("GET")); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::AccountInfo_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::AccountInfo_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Invalid %s"), *Request->GetURL()); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Message %s"), *msg); - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - - -void FSimplygonSwarmTask::CreateJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/create?apikey=%s&job_name=%s&asset_id=%s"), *HostName, *APIKey, *TaskData.JobName, *InputAssetId); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::CreateJob_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - SetState(SRS_JOBCREATED_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::CreateJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Invalid %s"), *Request->GetURL()); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.JobId.IsEmpty()) - { - JobId = Data.JobId; - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Created JobId: %s"), *Data.JobId); - SetState(SRS_JOBCREATED); - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Warning, TEXT("Empty JobId for task")); - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to parse message %s for Request %s"), *msg, *Request->GetURL()); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::UploadJobSettings() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s/uploadsettings?apikey=%s"), *HostName, *JobId, *APIKey); - - TArray data; - FFileHelper::LoadFileToArray(data, *TaskData.SplFilePath); - - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->SetContent(data); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::UploadJobSettings_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - SetState(SRS_JOBSETTINGSUPLOADED_PENDING); - } -} - -void FSimplygonSwarmTask::UploadJobSettings_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - if (!OnAssetUploaded().IsBound()) - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("OnAssetUploaded delegate not bound to any object")); - } - else - { - SetState(SRS_JOBSETTINGSUPLOADED); - OnAssetUploaded().Execute(*this); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::ProcessJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s/Process?apikey=%s"), *HostName, *JobId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("PUT")); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::ProcessJob_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s Response code %d"), *request->GetURL()); - } - else - { - SetState(SRS_JOBPROCESSING_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } - - if ( !bProcessingStarted) - { - TimeSinceProcessStart = 0.0f; - bProcessingStarted = true; - } - else - { - TimeSinceProcessStart += 2.5f; - - if (TimeSinceProcessStart > 900.0f) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process time seconds since processing started %f"), TimeSinceProcessStart); - } - } -} - -void FSimplygonSwarmTask::ProcessJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - SetState(SRS_JOBPROCESSING); - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::GetJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s?apikey=%s"), *HostName, *JobId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::GetJob_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::GetJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - // Do not need to process jobs which already either failed or completed - if (State != SRS_JOBPROCESSING) - { - return; - } - - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - else if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (!msg.IsEmpty() && ParseJsonMessage(msg, Data)) - { - - if (!Data.Status.IsEmpty()) - { - if (Data.Status == "Processed") - { - if (!Data.OutputAssetId.IsEmpty()) - { - OutputAssetId = Data.OutputAssetId; - SetState(SRS_JOBPROCESSED); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Job with Id %s processed"), *Data.JobId); - } - else - { - SetState(SRS_FAILED); - } - } - else if (Data.Status == "Failed") - { - SetState(SRS_FAILED); - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Job with id %s failed with error: %s"), *Data.JobId, *Data.ErrorMessage); - } - else if (Data.Status == "Processing") - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Process update job with Id %s (%f)"), *Data.JobId, Data.Progress); - } - else if (Data.Status == "Created") - { - SetState(SRS_JOBCREATED); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Job state was created while expecting processing %s"), *Data.JobId); - } - } - } - else - { - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::UploadAsset() -{ - //if multipart upload is need then use multi part request else use older request to upload data - if (NeedsMultiPartUpload()) - { - int32 PartsToUpload = RemainingPartsToUpload.GetValue(); - int32 PartIndex = TotalParts - PartsToUpload; - if (!bMultiPartUploadInitialized && PartsToUpload > 0) - { - MultiPartUploadBegin(); - } - else if (PartsToUpload > 0) - { - MultiPartUploadPart(UploadParts[PartIndex].PartNumber); - } - else if (PartsToUpload == 0) - { - if (!TaskData.TaskUploadComplete) - { - MultiPartUploadEnd(); - } - else - { - MultiPartUploadGet(); - } - } - } - else - { - //bail if part has already been uploaded - if (UploadParts[0].PartUploaded) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Skip Already Uploaded Asset.")); - return; - } - - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/upload?apikey=%s&asset_name=%s"), *HostName, *APIKey, *TaskData.JobName); - - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->SetContent(UploadParts[0].Data); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::UploadAsset_Response); - - const uint32 bufferSize = UploadParts[0].Data.Num(); - - FHttpModule::Get().SetMaxReadBufferSize(bufferSize); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - SetState(SRS_ASSETUPLOADED_PENDING); - } - } -} - -void FSimplygonSwarmTask::UploadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.AssetId.IsEmpty()) - { - InputAssetId = Data.AssetId; - UploadParts[0].PartUploaded = true; - SetState(SRS_ASSETUPLOADED); - } - else - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Could not parse Input asset Id for job: %s"), *Data.JobId); - SetState(SRS_FAILED); - } - - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - -void FSimplygonSwarmTask::DownloadAsset() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - if (OutputAssetId.IsEmpty()) - { - SetState(SRS_FAILED); - } - - FString url = FString::Printf(TEXT("%s/2.3/asset/%s/download?apikey=%s"), *HostName, *OutputAssetId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - FHttpModule::Get(). - - FHttpModule::Get().SetHttpTimeout(300); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::DownloadAsset_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - SetState(SRS_ASSETDOWNLOADED_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Downloading Job with Id %s"), *JobId); - } -} - -void FSimplygonSwarmTask::DownloadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Invalid response for job %s"), *TaskData.JobName); - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - if (this == nullptr || JobId.IsEmpty()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Object has already been destoryed or job id was empty")); - return; - } - TArray data = Response->GetContent(); - /*FString msg = Response->GetContentAsString(); - FSwarmJsonResponse Data;*/ - - if (data.Num() > 0) - { - if (!TaskData.OutputZipFilePath.IsEmpty() && !FFileHelper::SaveArrayToFile(data, *TaskData.OutputZipFilePath)) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Unable to save file %s"), *TaskData.OutputZipFilePath); - SetState(SRS_FAILED); - - } - else - { - if (!this->IsCompleted && OnAssetDownloaded().IsBound()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Asset downloaded")); - OnAssetDownloaded().Execute(*this); - SetState(SRS_ASSETDOWNLOADED); - } - else - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("OnAssetDownloaded delegate not bound to any objects")); - } - } - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadBegin() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart?apikey=%s&asset_name=%s"), *HostName, *APIKey, *TaskData.JobName); - - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadBegin_Response); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - - SetState(SRS_MULTIPARTASSETUPLOAD_PENDING); - } -} - -void FSimplygonSwarmTask::MultiPartUploadBegin_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - FSwarmJsonResponse Data; - - if (ParseJsonMessage(msg, Data)) - { - if (!Data.UploadId.IsEmpty()) - { - UploadId = Data.UploadId; - bMultiPartUploadInitialized = true; - } - else - { - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - -void FSimplygonSwarmTask::MultiPartUploadPart(const uint32 InPartNumber) -{ - //should bailout if part has already been uploaded - int32 PartIndex = InPartNumber - 1; - if (UploadParts[PartIndex].PartUploaded) - return; - - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s/upload?apikey=%s&part_number=%d"), *HostName, *UploadId, *APIKey, UploadParts[PartIndex].PartNumber); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("PUT")); - request->SetContent(UploadParts[PartIndex].Data); - FHttpModule::Get().SetMaxReadBufferSize(UploadParts[PartIndex].Data.Num()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadPart_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } - -} - -void FSimplygonSwarmTask::MultiPartUploadPart_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - //get part_number from query string - FString PartNumStr = Request->GetURLParameter("part_number"); - if (!PartNumStr.IsEmpty()) - { - int32 PartNum = FCString::Atoi(*PartNumStr); - PartNum -= 1; - if (!UploadParts[PartNum].PartUploaded) - { - RemainingPartsToUpload.Decrement(); - UploadParts[PartNum].PartUploaded = true; - } - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } - } -} - -void FSimplygonSwarmTask::MultiPartUploadEnd() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s/Complete?apikey=%s&part_count=%d&upload_size=%d"), *HostName, *UploadId, *APIKey, TotalParts, UploadSize); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadEnd_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadEnd_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.UploadId.IsEmpty()) - { - this->TaskData.TaskUploadComplete = true; - } - } - else - { - SetState(SRS_FAILED); - } - } - else - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - - } -} - -void FSimplygonSwarmTask::MultiPartUploadGet() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s?apikey=%s"), *HostName, *UploadId, *APIKey); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadGet_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadGet_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("%s: %i"), __FUNCTION__, Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - - if (ParseJsonMessage(msg, Data)) - { - if (!Data.AssetId.IsEmpty()) - { - InputAssetId = Data.AssetId; - SetState(SRS_ASSETUPLOADED); - UploadParts.Empty(); - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to parse JSON")); - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - - -void FSimplygonSwarmTask::AddAuthenticationHeader(TSharedRef request) -{ - //Basic User:User - request->SetHeader("Authorization", "Basic dXNlcjp1c2Vy"); -} - -FSimplygonRESTClient* FSimplygonRESTClient::Runnable = nullptr; - -FSimplygonRESTClient::FSimplygonRESTClient() - : - Thread(nullptr), - HostName(TEXT(HOSTNAME)), - APIKey(TEXT("LOCAL")), - bEnableDebugging(false) -{ - FString IP = GetDefault()->SimplygonServerIP; - bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - if (IP != "") - { - if (!IP.Contains("http://")) - { - HostName = "http://" + IP; - } - else - { - HostName = IP; - } - } - else - { - HostName = TEXT(HOSTNAME); - } - - HostName += TEXT(PORT); - JobLimit = GetDefault()->SwarmNumOfConcurrentJobs; - - - DelayBetweenRuns = GetDefault()->SimplygonSwarmDelay / 1000; - DelayBetweenRuns = DelayBetweenRuns <= 5 ? 5 : DelayBetweenRuns; - - Thread = FRunnableThread::Create(this, TEXT("SimplygonRESTClient")); -} - -FSimplygonRESTClient::~FSimplygonRESTClient() -{ - if (Thread != nullptr) - { - delete Thread; - Thread = nullptr; - } -} - - -bool FSimplygonRESTClient::Init() -{ - return true; -} - - -void FSimplygonRESTClient::Wait(const float InSeconds, const float InSleepTime /* = 0.1f */) -{ - // Instead of waiting the given amount of seconds doing nothing - // check periodically if there's been any Stop requests. - for (float TimeToWait = InSeconds; TimeToWait > 0.0f && ShouldStop() == false; TimeToWait -= InSleepTime) - { - FPlatformProcess::Sleep(FMath::Min(InSleepTime, TimeToWait)); - } -} - -uint32 FSimplygonRESTClient::Run() -{ - Wait(5); - //float SleepDelay = DelayBetweenRuns; - do - { - MoveItemsToBoundedArray(); - - UpdateTaskStates(); - - if (IsRunningCommandlet()) - { - FHttpModule::Get().GetHttpManager().Tick(FApp::GetDeltaTime()); - } - - Wait(DelayBetweenRuns); - - } while (ShouldStop() == false); - - - return 0; -} - -void FSimplygonRESTClient::UpdateTaskStates() -{ - TArray> TasksMarkedForRemoval; - //TasksMarkedForRemoval.AddUninitialized(JobLimit); - - FScopeLock Lock(&CriticalSectionData); - - for (int32 Index = 0; Index < JobsBuffer.Num(); Index++) - { - // - TSharedPtr& SwarmTask = JobsBuffer[Index]; - switch (SwarmTask->GetState()) - { - case SRS_UNKNOWN: - case SRS_MULTIPARTASSETUPLOAD_PENDING: - SwarmTask->UploadAsset(); - break; - case SRS_FAILED: - TasksMarkedForRemoval.Add(SwarmTask); - break; - case SRS_ASSETUPLOADED: - SwarmTask->CreateJob(); - break; - case SRS_JOBCREATED: - SwarmTask->UploadJobSettings(); - break; - case SRS_JOBSETTINGSUPLOADED: - SwarmTask->ProcessJob(); - break; - case SRS_JOBPROCESSING: - SwarmTask->GetJob(); - break; - case SRS_JOBPROCESSED: - SwarmTask->DownloadAsset(); - break; - case SRS_ASSETDOWNLOADED: - TasksMarkedForRemoval.Add(SwarmTask); - break; - } - } - - for (int32 Index = 0; Index < TasksMarkedForRemoval.Num(); Index++) - { - if (TasksMarkedForRemoval[Index]->GetState() == SRS_FAILED) - { - TasksMarkedForRemoval[Index]->OnSwarmTaskFailed().ExecuteIfBound(*TasksMarkedForRemoval[Index]); - } - JobsBuffer.Remove(TasksMarkedForRemoval[Index]); - } - - TasksMarkedForRemoval.Empty(); -} - -void FSimplygonRESTClient::MoveItemsToBoundedArray() -{ - if (!PendingJobs.IsEmpty()) - { - int32 ItemsToInsert = 0; - if (JobsBuffer.Num() >= JobLimit) - { - ItemsToInsert = JobLimit; - } - else if (JobsBuffer.Num() < JobLimit) - { - JobsBuffer.Shrink(); - ItemsToInsert = JobLimit - JobsBuffer.Num(); - - FScopeLock Lock(&CriticalSectionData); - do - { - TSharedPtr OutItem; - if (PendingJobs.Dequeue(OutItem)) - { - OutItem->CreateUploadParts(MaxUploadSizeInBytes); - JobsBuffer.Add(OutItem); - } - - } while (!PendingJobs.IsEmpty() && JobsBuffer.Num() < JobLimit); - } - } -} - -FSimplygonRESTClient* FSimplygonRESTClient::Get() -{ - if (!Runnable && FPlatformProcess::SupportsMultithreading()) - { - Runnable = new FSimplygonRESTClient(); - } - return Runnable; -} - -void FSimplygonRESTClient::Shutdown() -{ - if (Runnable) - { - Runnable->EnusureCompletion(); - delete Runnable; - Runnable = nullptr; - } -} - -void FSimplygonRESTClient::Stop() -{ - StopTaskCounter.Increment(); -} - -void FSimplygonRESTClient::EnusureCompletion() -{ - Stop(); - Thread->WaitForCompletion(); -} - -void FSimplygonRESTClient::AddSwarmTask(TSharedPtr& InTask) -{ - InTask->SetHost(HostName); - PendingJobs.Enqueue(InTask); -} - -void FSimplygonRESTClient::SetMaxUploadSizeInBytes(int32 InMaxUploadSizeInBytes) -{ - MaxUploadSizeInBytes = InMaxUploadSizeInBytes; -} - -void FSimplygonRESTClient::Exit() -{ - -} - diff --git a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp b/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp deleted file mode 100644 index 0236ff7764d5..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp +++ /dev/null @@ -1,1759 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "SimplygonSwarmCommon.h" -#include "SimplygonSwarmHelpers.h" -#include "SimplygonRESTClient.h" -#include "Modules/ModuleManager.h" -#include "UObject/Object.h" -#include "Misc/Paths.h" -#include "Misc/ScopedSlowTask.h" -#include "IImageWrapper.h" -#include "IImageWrapperModule.h" -#include "Editor/EditorPerProjectUserSettings.h" -#include "Misc/EngineVersion.h" -#include "Misc/MonitoredProcess.h" -#include "Templates/UniquePtr.h" -THIRD_PARTY_INCLUDES_START -#include -THIRD_PARTY_INCLUDES_END - -#define LOCTEXT_NAMESPACE "SimplygonSwarm" - -#include "IMeshReductionInterfaces.h" - -#include "MeshMergeData.h" -#include "Features/IModularFeatures.h" - -#include "MeshDescription.h" -#include "MeshAttributes.h" -#include "MeshAttributeArray.h" -#include "MeshDescriptionOperations.h" - -// Standard Simplygon channels have some issues with extracting color data back from simplification, -// so we use this workaround with user channels -static const char* USER_MATERIAL_CHANNEL_METALLIC = "UserMetallic"; -static const char* USER_MATERIAL_CHANNEL_ROUGHNESS = "UserRoughness"; -static const char* USER_MATERIAL_CHANNEL_SPECULAR = "UserSpecular"; - -static const TCHAR* BASECOLOR_CHANNEL = TEXT("Basecolor"); -static const TCHAR* METALLIC_CHANNEL = TEXT("Metallic"); -static const TCHAR* SPECULAR_CHANNEL = TEXT("Specular"); -static const TCHAR* ROUGHNESS_CHANNEL = TEXT("Roughness"); -static const TCHAR* NORMAL_CHANNEL = TEXT("Normals"); -static const TCHAR* OPACITY_CHANNEL = TEXT("Opacity"); -static const TCHAR* EMISSIVE_CHANNEL = TEXT("Emissive"); -static const TCHAR* OPACITY_MASK_CHANNEL = TEXT("OpacityMask"); -static const TCHAR* AO_CHANNEL = TEXT("AmbientOcclusion"); -static const TCHAR* MATERIAL_MASK_CHANNEL = TEXT("MaterialMask"); -static const TCHAR* OUTPUT_LOD = TEXT("outputlod_0"); -static const TCHAR* SSF_FILE_TYPE = TEXT("ssf"); -static const TCHAR* REMESHING_PROCESSING_SETNAME = TEXT("RemeshingProcessingSet"); -static const TCHAR* CLIPPING_GEOMETRY_SETNAME = TEXT("ClippingObjectSet"); - - -#define SIMPLYGON_COLOR_CHANNEL "VertexColors" - -#define KEEP_SIMPLYGON_SWARM_TEMPFILES - -static const TCHAR* SG_UE_INTEGRATION_REV = TEXT("#SG_UE_INTEGRATION_REV"); - -#ifdef __clang__ - // SimplygonSDK.h uses 'deprecated' pragma which Clang does not recognize - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown pragma ignored [-Wunknown-pragmas] -#endif - -#ifdef __clang__ - #pragma clang diagnostic pop -#endif - -#define MAX_UPLOAD_PART_SIZE_MB 1024 -#define MAX_UPLOAD_PART_SIZE_BYTES ( MAX_UPLOAD_PART_SIZE_MB * 1024 * 1024 ) - -static const TCHAR SHADING_NETWORK_TEMPLATE[] = TEXT("\n\t\n\t\t\n\t\t\t1 1 1 1\n\t\t\n\t\t%s\n\t\t%s\n\t\t%d\n\t\t1.000000\n\t\t1.000000\n\t\n"); - -ssf::pssfMeshData CreateSSFMeshDataFromRawMesh(const FMeshDescription& InRawMesh, TArray InTextureBounds, TArray InTexCoords); - -class FSimplygonSwarmModule : public IMeshReductionModule -{ -public: - // IModuleInterface interface. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IMeshReductionModule interface. - virtual class IMeshReduction* GetStaticMeshReductionInterface() override; - virtual class IMeshReduction* GetSkeletalMeshReductionInterface() override; - virtual class IMeshMerging* GetMeshMergingInterface() override; - virtual class IMeshMerging* GetDistributedMeshMergingInterface() override; - virtual FString GetName() override; - -private: -}; - -DEFINE_LOG_CATEGORY_STATIC(LogSimplygonSwarm, Log, All); -IMPLEMENT_MODULE(FSimplygonSwarmModule, SimplygonSwarm); - -class FSimplygonSwarm - : public IMeshMerging -{ -public: - virtual ~FSimplygonSwarm() - { - } - - static FSimplygonSwarm* Create() - { - return new FSimplygonSwarm(); - } - - virtual FString GetName() override - { - return FString("SimplygonSwarm"); - } - - struct FMaterialCastingProperties - { - bool bCastMaterials; - bool bCastNormals; - bool bCastMetallic; - bool bCastRoughness; - bool bCastSpecular; - - FMaterialCastingProperties() - : bCastMaterials(false) - , bCastNormals(false) - , bCastMetallic(false) - , bCastRoughness(false) - , bCastSpecular(false) - { - } - }; - - /** - * Method used to generate ProxyLOD either using Remeshing or Aggregation - * @param InData Mesh Merge Data - * @param InProxySettings Settings to use for proxy generation - * @param InputMaterials Flattened materials - * @param InJobGUID Job GUID - */ - virtual void ProxyLOD(const TArray& InData, - const struct FMeshProxySettings& InProxySettings, - const TArray& InputMaterials, - const FGuid InJobGUID) - { - FScopedSlowTask SlowTask(3.f, (LOCTEXT("SimplygonSwarm_ProxyLOD", "Generating Proxy Mesh using Simplygon Swarm"))); - SlowTask.MakeDialog(); - - FMeshDescription OutProxyMesh; - FFlattenMaterial OutMaterial; - - //setup path variables - FString JobPath = FGuid::NewGuid().ToString(); - FString JobDirectory = FString::Printf(TEXT("%s%s"), *GetMutableDefault()->SwarmIntermediateFolder, *JobPath); - FString InputFolderPath = FString::Printf(TEXT("%s/Input"), *JobDirectory); - - FString ZipFileName = FString::Printf(TEXT("%s/%s.zip"), *JobDirectory, *JobPath); - FString OutputZipFileName = FString::Printf(TEXT("%s/%s_output.zip"), *JobDirectory, *JobPath); - FString SPLFileOutputFullPath = FString::Printf(TEXT("%s/input.spl"), *InputFolderPath); - FString SPLSettingsText; - - EBlendMode OutputMaterialBlendMode = BLEND_Opaque; - bool bHasMaked = false; - bool bHasOpacity = false; - - for (int MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - if (InputMaterials[MaterialIndex].BlendMode == BLEND_Translucent) - { - bHasOpacity = true; - } - - if (InputMaterials[MaterialIndex].BlendMode == BLEND_Masked) - { - bHasMaked = true; - } - } - - if ( (bHasMaked && bHasOpacity) || bHasOpacity) - { - OutputMaterialBlendMode = BLEND_Translucent; - } - else if (bHasMaked && !bHasOpacity) - { - OutputMaterialBlendMode = BLEND_Masked; - } - - //scan for clipping geometry - bool bHasClippingGeometry = false; - if (InData.FindByPredicate([](const FMeshMergeData InMeshData) {return InMeshData.bIsClippingMesh == true; })) - { - bHasClippingGeometry = true; - } - - SPL::SPL* spl = new SPL::SPL(); - spl->Header.ClientName = TCHAR_TO_ANSI(TEXT("UE4")); - spl->Header.ClientVersion = TCHAR_TO_ANSI(*FEngineVersion::Current().ToString()); - spl->Header.SimplygonVersion = TCHAR_TO_ANSI(TEXT("8.0")); - SPL::ProcessNode* splProcessNode = new SPL::ProcessNode(); - spl->ProcessGraph = splProcessNode; - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_CreateSPL", "Generating Simplygon Processing Settings")); - - CreateRemeshingProcess(InProxySettings, *splProcessNode, OutputMaterialBlendMode, bHasClippingGeometry); - - ssf::pssfScene SsfScene; - - TArray InputMeshes; - - for (auto Data : InData) - { - InputMeshes.Push(Data.RawMesh); - } - - bool bDiscardEmissive = true; - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - bDiscardEmissive &= ((!FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black))); - } - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_GenerateData", "Generating Simplygon Processing Data")); - - //converts UE entities to ssf, Textures will be exported to file - ConvertMeshMergeDataToSsfScene(InData, InputMaterials, InProxySettings, InputFolderPath, SsfScene); - - SsfScene->CoordinateSystem->Value = 1; - SsfScene->WorldOrientation->Value = 3; - - FString SsfOuputPath = FString::Printf(TEXT("%s/input.ssf"), *InputFolderPath); - - //save out ssf file. - WriteSsfFile(SsfScene, SsfOuputPath); - - spl->Save(TCHAR_TO_ANSI(*SPLFileOutputFullPath)); - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_UploadData", "Uploading Processing Data to Simplygon Swarm Server")); - //zip contents and spawn a task - if (ZipContentsForUpload(InputFolderPath, ZipFileName)) - { - //validate if patch exist - if (!FPaths::FileExists(*FPaths::ConvertRelativePathToFull(ZipFileName))) - { - UE_LOG(LogSimplygonSwarm, Error , TEXT("Could not find zip file for uploading %s"), *ZipFileName); - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Could not find zip file for uploading")); - return; //-V773 - } - - FSwarmTaskkData TaskData; - TaskData.ZipFilePath = ZipFileName; - TaskData.SplFilePath = SPLFileOutputFullPath; - TaskData.OutputZipFilePath = OutputZipFileName; - TaskData.JobDirectory = JobDirectory; - TaskData.StateLock = new FCriticalSection(); - TaskData.ProcessorJobID = InJobGUID; - TaskData.bDitheredTransition = (InputMaterials.Num() > 0) ? InputMaterials[0].bDitheredLODTransition : false; - TaskData.bEmissive = !bDiscardEmissive; - TaskData.JobName = InData[0].DebugJobName; - - int32 MaxUploadSizeInBytes = GetMutableDefault()->SwarmMaxUploadChunkSizeInMB * 1024 * 1024; - FSimplygonRESTClient::Get()->SetMaxUploadSizeInBytes(MaxUploadSizeInBytes); - TSharedPtr SwarmTask = MakeShareable(new FSimplygonSwarmTask(TaskData)); - SwarmTask->OnAssetDownloaded().BindRaw(this, &FSimplygonSwarm::ImportFile); - SwarmTask->OnAssetUploaded().BindRaw(this, &FSimplygonSwarm::Cleanup); - SwarmTask->OnSwarmTaskFailed().BindRaw(this, &FSimplygonSwarm::OnSimplygonSwarmTaskFailed); - FSimplygonRESTClient::Get()->AddSwarmTask(SwarmTask); - } - - delete spl; - } - - /** - * The following method is called when a swarm task fails. This forwards the call to external module - * @param InSwarmTask The completed swarm task - */ - void OnSimplygonSwarmTaskFailed(const FSimplygonSwarmTask& InSwarmTask) - { - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Simplygon Swarm Proxy Generation failed.")); - } - - /** - * Method to clean up temporary files after uploading the job to Simplygon Grid Server - * @param InSwarmTask The completed swarm task - */ - void Cleanup(const FSimplygonSwarmTask& InSwarmTask) - { - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - - if(!bDebuggingEnabled) - { - FString InputFolderPath = FPaths::ConvertRelativePathToFull(FString::Printf(TEXT("%s/Input"), *InSwarmTask.TaskData.JobDirectory)); - //remove folder folder - if (FPaths::DirectoryExists(InputFolderPath)) - { - if (!IFileManager::Get().DeleteDirectory(*InputFolderPath, true, true)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to remove simplygon swarm task temp directory %s"), *InputFolderPath); - } - } - FString FullZipPath = FPaths::ConvertRelativePathToFull(*InSwarmTask.TaskData.ZipFilePath); - //remove uploaded zip file - if (FPaths::FileExists(FullZipPath)) - { - if (!IFileManager::Get().Delete(*FullZipPath)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to remove Simplygon Swarm Task temp file %s"), *InSwarmTask.TaskData.ZipFilePath); - } - } - } - } - - - /** - * Fired when the Server returns the completed job to the client. Called from RESTClient - * @param InSwarmTask The completed swarm task - */ - void ImportFile(const FSimplygonSwarmTask& InSwarmTask) - { - - FMeshDescription OutProxyMesh; - FFlattenMaterial OutMaterial; - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - FString OutputFolderPath = FString::Printf(TEXT("%s/Output"), *InSwarmTask.TaskData.JobDirectory); - FString ParentDirForOutputSsf = FString::Printf(TEXT("%s/outputlod_0"), *OutputFolderPath); - - //for import the file back in uncomment - FString ZipFileFullPath = FPaths::ConvertRelativePathToFull(InSwarmTask.TaskData.OutputZipFilePath); - FString UnzipOutputFullPath = FPaths::ConvertRelativePathToFull(OutputFolderPath); - if (UnzipDownloadedContent(ZipFileFullPath, UnzipOutputFullPath)) - { - FString InOuputSsfPath = FString::Printf(TEXT("%s/output.ssf"), *ParentDirForOutputSsf); - ssf::pssfScene OutSsfScene = new ssf::ssfScene(); - FString SsfFullPath = FPaths::ConvertRelativePathToFull(InOuputSsfPath); - - if (!FPaths::FileExists(SsfFullPath)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Ssf file not found %s"), *SsfFullPath); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Ssf file not found")); - return; - } - - ReadSsfFile(SsfFullPath, OutSsfScene); - ConvertFromSsfSceneToRawMesh(OutSsfScene, OutProxyMesh, OutMaterial, ParentDirForOutputSsf); - OutMaterial.bDitheredLODTransition = InSwarmTask.TaskData.bDitheredTransition; - - if (!InSwarmTask.TaskData.bEmissive) - { - OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Empty(); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, FIntPoint(0,0)); - } - - if (!OutProxyMesh.VertexInstances().Num()) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("RawMesh is invalid.")); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Invalid FMeshDescription data")); - } - - - //do cleanup work - if (!bDebuggingEnabled) - { - FString FullOutputFolderPath = FPaths::ConvertRelativePathToFull(*OutputFolderPath); - if (!IFileManager::Get().DeleteDirectory(*FullOutputFolderPath, true, true)) - UE_LOG(LogSimplygonSwarm, Error, TEXT("Failed to remove simplygon swarm task temp directory %s"), *FullOutputFolderPath); - - FString FullOutputFileName = FPaths::ConvertRelativePathToFull(*InSwarmTask.TaskData.OutputZipFilePath); - //remove uploaded zip file - if (!IFileManager::Get().Delete(*FullOutputFileName, true, true, false)) - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("Failed to remove Simplygon Swarm Task temp file %s"), *FullOutputFileName); - } - } - - //if is bound then execute - if (CompleteDelegate.IsBound()) - { - CompleteDelegate.Execute(OutProxyMesh, OutMaterial, InSwarmTask.TaskData.ProcessorJobID); - } - else - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("No valid complete delegate is currently bounded. ")); - } - - } - else - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to unzip downloaded content %s"), *ZipFileFullPath); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Invalid FMeshDescription data")); - } - } - -private: - FString VersionString; - FSimplygonRESTClient* SgRESTInterface; - //FRunnableThread *Thread; - uint8 ToolMajorVersion; - uint8 ToolMinorVersion; - uint16 ToolBuildVersion; - - explicit FSimplygonSwarm() - { - VersionString = FString::Printf(TEXT("%s"), SG_UE_INTEGRATION_REV); - ToolMajorVersion = FEngineVersion::Current().GetMajor(); - ToolMinorVersion = FEngineVersion::Current().GetMinor(); - ToolBuildVersion = FEngineVersion::Current().GetPatch(); - } - - /** - * Read in ssf file from disk - * @param InSsfFilePath Ssf file to read in - * @param SsfScene SsfScene that the ssf file is read into - */ - void ReadSsfFile(FString InSsfFilePath, ssf::pssfScene& SsfScene) - { - ssf::ssfString ToolName = FSimplygonSSFHelper::TCHARToSSFString(TEXT("UE4")); - - ssf::ssfBinaryInputStream InputStream; - InputStream.OpenFile(FSimplygonSSFHelper::TCHARToSSFString(*InSsfFilePath)); - SsfScene->ReadFile(&InputStream, ToolName, ToolMajorVersion, ToolMinorVersion, ToolBuildVersion); - } - - /** - * Write out ssf scene to disk - * @param SsfScene SsfScene to write out - * @param InSsfFilePath Path to ssf file - */ - void WriteSsfFile(ssf::pssfScene SsfScene, FString InSsfFilePath) - { - ssf::ssfString ToolName = FSimplygonSSFHelper::TCHARToSSFString(TEXT("UE4")); - ssf::ssfBinaryOutputStream theOutputStream; - theOutputStream.OpenFile(FSimplygonSSFHelper::TCHARToSSFString(*InSsfFilePath)); - SsfScene->WriteFile(&theOutputStream, ToolName, ToolMajorVersion, ToolMinorVersion, ToolBuildVersion); - theOutputStream.CloseFile(); - } - - /** - * Setup spl mapping image object used for material baking - * @param InMaterialProxySettings Proxy Settings to use for setting up remeshing process node - * @param InMappingImageSettings Mapping image setting object - */ - void SetupSplMappingImage(const struct FMaterialProxySettings& InMaterialProxySettings, SPL::MappingImageSettings& InMappingImageSettings) - { - FIntPoint ImageSizes = ComputeMappingImageSize(InMaterialProxySettings); - bool bAutomaticTextureSize = InMaterialProxySettings.TextureSizingType == TextureSizingType_UseSimplygonAutomaticSizing; - - InMappingImageSettings.GenerateMappingImage = true; - InMappingImageSettings.GutterSpace = InMaterialProxySettings.GutterSpace; - InMappingImageSettings.UseAutomaticTextureSize = bAutomaticTextureSize; - InMappingImageSettings.Height = ImageSizes.X; - InMappingImageSettings.Width = ImageSizes.Y; - InMappingImageSettings.UseFullRetexturing = true; - InMappingImageSettings.GenerateTangents = true; - InMappingImageSettings.GenerateTexCoords = true; - InMappingImageSettings.TexCoordLevel = 255; - InMappingImageSettings.MultisamplingLevel = 3; - InMappingImageSettings.TexCoordGeneratorType = SPL::TexCoordGeneratorType::SG_TEXCOORDGENERATORTYPE_PARAMETERIZER; - InMappingImageSettings.Enabled = true; - - } - - /** - * Create Spl Process node for Remeshing - * @param InProxySettings Proxy Settings to use for setting up remeshing process node - * @param InProcessNodeSpl SplProcessNode object - * @param InOutputMaterialBlendMode Output Material Blend mode - * @param InHasClippingGeometry Weather or the scene being processed has clipping geometry - */ - void CreateRemeshingProcess(const struct FMeshProxySettings& InProxySettings, SPL::ProcessNode& InProcessNodeSpl, EBlendMode InOutputMaterialBlendMode = BLEND_Opaque, bool InHasClippingGeometry = false) - { - SPL::RemeshingProcessor* processor = new SPL::RemeshingProcessor(); - processor->RemeshingSettings = new SPL::RemeshingSettings(); - - processor->RemeshingSettings->OnScreenSize = InProxySettings.ScreenSize; - processor->RemeshingSettings->SurfaceTransferMode = SPL::SurfaceTransferMode::SG_SURFACETRANSFER_ACCURATE; - processor->RemeshingSettings->ProcessSelectionSetName = TCHAR_TO_ANSI(REMESHING_PROCESSING_SETNAME); - - if (InHasClippingGeometry) - { - processor->RemeshingSettings->UseClippingGeometryEmptySpaceOverride = false; - processor->RemeshingSettings->UseClippingGeometry = InHasClippingGeometry; - processor->RemeshingSettings->ClippingGeometrySelectionSetName = TCHAR_TO_ANSI(CLIPPING_GEOMETRY_SETNAME); - } - - if (InProxySettings.bRecalculateNormals) - processor->RemeshingSettings->HardEdgeAngleInRadians = FMath::DegreesToRadians(InProxySettings.HardAngleThreshold); - - processor->RemeshingSettings->MergeDistance = InProxySettings.MergeDistance; - processor->RemeshingSettings->Enabled = true; - - FIntPoint ImageSizes = ComputeMappingImageSize(InProxySettings.MaterialSettings); - - //mapping image settings - processor->MappingImageSettings = new SPL::MappingImageSettings(); - SetupSplMappingImage(InProxySettings.MaterialSettings, *processor->MappingImageSettings); - - SetupSplMaterialCasters(InProxySettings.MaterialSettings, InProcessNodeSpl, InOutputMaterialBlendMode); - - InProcessNodeSpl.Processor = processor; - InProcessNodeSpl.DefaultTBNType = SPL::SG_TANGENTSPACEMETHOD_ORTHONORMAL_LEFTHANDED; - - SPL::WriteNode* splWriteNode = new SPL::WriteNode(); - splWriteNode->Format = TCHAR_TO_ANSI(SSF_FILE_TYPE); - splWriteNode->Name = TCHAR_TO_ANSI(OUTPUT_LOD); - - - InProcessNodeSpl.Children.push_back(splWriteNode); - } - - /** - * Create Spl Process node for Remeshing - * @param InSplText Save SPL Text - * @param InOutputFilePath SplProcessNode object - */ - void SaveSPL(FString InSplText, FString InOutputFilePath) - { - FArchive* SPLFile = IFileManager::Get().CreateFileWriter(*InOutputFilePath); - SPLFile->Logf(TEXT("%s"), *InSplText); - SPLFile->Close(); - } - - /** - * Convert collection of FMeshMergeData to SsfScene - * @param InMeshMergeData Meshes to merge - * @param InputMaterials Flattened Materials - * @param InProxySettings Proxy Settings - * @param InputFolderPath Input Folder Path - * @param OutSsfScene Out SsfScene - */ - void ConvertMeshMergeDataToSsfScene(const TArray& InMeshMergeData, - const TArray& InputMaterials, - const struct FMeshProxySettings& InProxySettings, FString InputFolderPath, ssf::pssfScene& OutSsfScene) - { - //create the ssf scene - OutSsfScene = new ssf::ssfScene(); - - OutSsfScene->CoordinateSystem.Set(1); - OutSsfScene->WorldOrientation.Set(2); - OutSsfScene->TextureTable->TexturesDirectory.Set(FSimplygonSSFHelper::TCHARToSSFString(TEXT("/Textures"))); - - //set processing and clipping geometry sets - - //processing set - ssf::ssfNamedIdList ProcessingObjectsSet; - ssf::ssfNamedIdList ClippingGeometrySet; - - ProcessingObjectsSet.Name = FSimplygonSSFHelper::TCHARToSSFString(REMESHING_PROCESSING_SETNAME); - ProcessingObjectsSet.ID = FSimplygonSSFHelper::SSFNewGuid(); - ClippingGeometrySet.Name = FSimplygonSSFHelper::TCHARToSSFString(CLIPPING_GEOMETRY_SETNAME); - ClippingGeometrySet.ID = FSimplygonSSFHelper::SSFNewGuid(); - - - TMap MaterialMap; - - CreateSSFMaterialFromFlattenMaterial(InputMaterials, InProxySettings.MaterialSettings, OutSsfScene->MaterialTable, OutSsfScene->TextureTable, InputFolderPath, true, MaterialMap); - - //create the root node - ssf::pssfNode SsfRootNode = new ssf::ssfNode(); - SsfRootNode->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfRootNode->ParentId.Set(FSimplygonSSFHelper::SFFEmptyGuid()); - - //add root node to scene - OutSsfScene->NodeTable->NodeList.push_back(SsfRootNode); - - int32 Count = 0; - for (FMeshMergeData MergeData : InMeshMergeData) - { - //create a the node that will contain the mesh - ssf::pssfNode SsfNode = new ssf::ssfNode(); - SsfNode->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfNode->ParentId.Set(SsfRootNode->Id.Get()); - FString NodeName = FString::Printf(TEXT("Node%i"), Count); - - SsfNode->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*NodeName)); - ssf::ssfMatrix4x4 IdenMatrix; - IdenMatrix.M[0][0] = IdenMatrix.M[1][1] = IdenMatrix.M[2][2] = IdenMatrix.M[3][3] = 1; - SsfNode->LocalTransform.Set(IdenMatrix); - - //create the mesh object - ssf::pssfMesh SsfMesh = new ssf::ssfMesh(); - SsfMesh->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - FString MeshName = FString::Printf(TEXT("Mesh%i"), Count); - SsfMesh->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*MeshName)); - - Count++; - - //setup mesh data - ssf::pssfMeshData SsfMeshData = CreateSSFMeshDataFromRawMesh(*MergeData.RawMesh, MergeData.TexCoordBounds, MergeData.NewUVs); - SsfMesh->MeshDataList.push_back(SsfMeshData); - - //setup mesh material information - SsfMesh->MaterialIds.Create(); - TArray UniqueMaterialIds; - UniqueMaterialIds.Reserve(InputMaterials.Num()); - - //get unqiue material ids - GetUniqueMaterialIndices(*(MergeData.RawMesh), UniqueMaterialIds); - - SsfMesh->MaterialIds->Items.reserve(UniqueMaterialIds.Num()); - - TMap GlobalToLocal; - //map ssfmesh local materials - for (int32 GlobalMaterialId : UniqueMaterialIds) - { - SsfMesh->MaterialIds->Items.push_back(FSimplygonSSFHelper::TCHARToSSFString(*MaterialMap[GlobalMaterialId])); - int32 localIndex = SsfMesh->MaterialIds->Items.size() - 1; - //replace - GlobalToLocal.Add(GlobalMaterialId, localIndex); - } - - for (ssf::pssfMeshData MeshData : SsfMesh->MeshDataList) - { - for (int Index = 0; Index < MeshData->MaterialIndices.Get().Items.size(); Index++) - { - MeshData->MaterialIndices.Get().Items[Index] = GlobalToLocal[MeshData->MaterialIndices.Get().Items[Index]]; - } - } - - //link mesh to node - SsfNode->MeshId.Set(SsfMesh->Id.Get().Value); - - //add mesh and node to their respective tables - OutSsfScene->NodeTable->NodeList.push_back(SsfNode); - OutSsfScene->MeshTable->MeshList.push_back(SsfMesh); - - if (MergeData.bIsClippingMesh) - { - ClippingGeometrySet.Items.push_back(SsfNode->Id->ToCharString()); - } - else - { - ProcessingObjectsSet.Items.push_back(SsfNode->Id->ToCharString()); - } - - - } - - if(ClippingGeometrySet.Items.size() > 0) - OutSsfScene->SelectionGroupSetsList.push_back(ClippingGeometrySet); - - if (ProcessingObjectsSet.Items.size() > 0) - OutSsfScene->SelectionGroupSetsList.push_back(ProcessingObjectsSet); - - } - - /** - * Convert SsfScnee to RawMesh. Currently assumes that only a single mesh will be present in the SsfScene - * @param SsfScene SsfScene - * @param OutProxyMesh Converted SsfMeshData to RawMesh - * @param OutMaterial Converted SsfMaterial to Flattened Material - * @param BaseTexturesPath Base Path for textures - */ - void ConvertFromSsfSceneToRawMesh(ssf::pssfScene SsfScene, FMeshDescription& OutProxyMesh, FFlattenMaterial& OutMaterial, const FString BaseTexturesPath) - { - TVertexAttributesRef VertexPositions = OutProxyMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesRef EdgeCreaseSharpnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesRef VertexInstanceNormals = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesRef VertexInstanceTangents = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesRef VertexInstanceColors = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesRef VertexInstanceUVs = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - bool bReverseWinding = true; - - for (ssf::pssfMesh Mesh : SsfScene->MeshTable->MeshList) - { - //extract geometry data - for (ssf::pssfMeshData MeshData : Mesh->MeshDataList) - { - int32 TotalVertices = MeshData->GetVerticesCount(); - int32 TotalCorners = MeshData->GetCornersCount(); - int32 TotalTriangles = MeshData->GetTrianglesCount(); - - //Assuming only one mesh - OutProxyMesh.Empty(); - OutProxyMesh.ReserveNewVertices(TotalVertices); - OutProxyMesh.ReserveNewPolygons(TotalTriangles); - OutProxyMesh.ReserveNewVertexInstances(TotalCorners); - OutProxyMesh.ReserveNewEdges(TotalCorners); - - TMap SsfToMeshDescriptionVertexID; - SsfToMeshDescriptionVertexID.Reserve(TotalVertices); - int32 VertexIndex = 0; - for (ssf::ssfVector3 VertexCoord : MeshData->Coordinates.Get().Items) - { - const FVertexID VertexID = OutProxyMesh.CreateVertex(); - VertexPositions[VertexID] = GetConversionMatrixYUP().InverseTransformPosition(FVector(VertexCoord.V[0], VertexCoord.V[1], VertexCoord.V[2])); - SsfToMeshDescriptionVertexID.Add(VertexIndex, VertexID); - VertexIndex++; - } - - //Prepare the tex coord - int32 TexCoordIndex = 0; - ssf::ssfNamedList BakedMaterialUVs = FSimplygonSSFHelper::GetBakedMaterialUVs(MeshData->TextureCoordinatesList); - VertexInstanceUVs.SetNumIndices(1); - - //Is buffer has some data? - bool Normals = !MeshData->Normals.IsEmpty() && MeshData->Normals.Get().Items.size() > 0; - bool Tangents = !MeshData->Tangents.IsEmpty() && MeshData->Tangents.Get().Items.size() > 0; - bool Bitangents = !MeshData->Bitangents.IsEmpty() && MeshData->Bitangents.Get().Items.size() > 0; - bool MaterialIndices = !MeshData->MaterialIndices.IsEmpty() && MeshData->MaterialIndices.Get().Items.size() > 0; - bool GroupIds = !MeshData->SmoothingGroup.IsEmpty() && MeshData->SmoothingGroup.Get().Items.size() > 0; - - //Setup PolygonGroup - //Prepare the polygongroup - TMap SsfToRawMaterial; - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - int32 MaterialIndex = MaterialIndices ? (int32)MeshData->MaterialIndices.Get().Items[TriIndex].Value : 0; - if (!SsfToRawMaterial.Contains(MaterialIndex)) - { - const FPolygonGroupID PolygonGroupID(MaterialIndex); - OutProxyMesh.CreatePolygonGroupWithID(PolygonGroupID); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString(TEXT("SimplygonSwarm_") + FString::FromInt(PolygonGroupID.GetValue()))); - SsfToRawMaterial.Add(MaterialIndex, PolygonGroupID); - } - } - - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - FVertexID VertexIndexes[3]; - FVertexInstanceID VertexInstanceIDs[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - int32 SrcCornerIndex = bReverseWinding ? 2 - CornerIndex : CornerIndex; - int32 SrcIndex = (3 * TriIndex) + SrcCornerIndex; - VertexIndexes[CornerIndex] = SsfToMeshDescriptionVertexID[MeshData->TriangleIndices.Get().Items[TriIndex].V[SrcCornerIndex]]; - VertexInstanceIDs[CornerIndex] = OutProxyMesh.CreateVertexInstance(VertexIndexes[CornerIndex]); - - //Texture Coordinates, copy baked material UV's only discard the rest - VertexInstanceUVs.Get(VertexInstanceIDs[CornerIndex], 0) = FVector2D(BakedMaterialUVs.Items[SrcIndex].V[0], BakedMaterialUVs.Items[SrcIndex].V[1]); - - //Vertex Color, SSF can store multiple color channels. However UE only supports one color channel - for (ssf::ssfNamedList TexColorChannel : MeshData->ColorsList) - { - VertexInstanceColors[VertexInstanceIDs[CornerIndex]] = FVector4(TexColorChannel.Items[SrcIndex].V[0], TexColorChannel.Items[SrcIndex].V[1], TexColorChannel.Items[SrcIndex].V[2], TexColorChannel.Items[SrcIndex].V[3]); - break; //UE support only one - } - - //Tangents - if (Normals) - { - FVector NormalValue = FVector(MeshData->Normals.Get().Items[SrcIndex].V[0], MeshData->Normals.Get().Items[SrcIndex].V[1], MeshData->Normals.Get().Items[SrcIndex].V[2]); - NormalValue = GetConversionMatrixYUP().InverseTransformPosition(NormalValue); - VertexInstanceNormals[VertexInstanceIDs[CornerIndex]] = NormalValue; - if (Tangents && Bitangents) - { - FVector TangentValue = FVector(MeshData->Tangents.Get().Items[SrcIndex].V[0], MeshData->Tangents.Get().Items[SrcIndex].V[1], MeshData->Tangents.Get().Items[SrcIndex].V[2]); - TangentValue = GetConversionMatrixYUP().InverseTransformPosition(TangentValue); - VertexInstanceTangents[VertexInstanceIDs[CornerIndex]] = TangentValue; - - FVector BiTangentValue = FVector(MeshData->Bitangents.Get().Items[SrcIndex].V[0], MeshData->Bitangents.Get().Items[SrcIndex].V[1], MeshData->Bitangents.Get().Items[SrcIndex].V[2]); - BiTangentValue = GetConversionMatrixYUP().InverseTransformPosition(BiTangentValue); - VertexInstanceBinormalSigns[VertexInstanceIDs[CornerIndex]] = GetBasisDeterminantSign(TangentValue.GetSafeNormal(), BiTangentValue.GetSafeNormal(), NormalValue.GetSafeNormal()); - } - } - } - //Create a polygon from this triangle - TArray Contours; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - int32 ContourPointIndex = Contours.AddDefaulted(); - FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex]; - //Find the matching edge ID - uint32 CornerIndices[2]; - CornerIndices[0] = (Corner + 0) % 3; - CornerIndices[1] = (Corner + 1) % 3; - - FVertexID EdgeVertexIDs[2]; - EdgeVertexIDs[0] = VertexIndexes[CornerIndices[0]]; - EdgeVertexIDs[1] = VertexIndexes[CornerIndices[1]]; - - FEdgeID MatchEdgeId = OutProxyMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - if (MatchEdgeId == FEdgeID::Invalid) - { - MatchEdgeId = OutProxyMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - EdgeHardnesses[MatchEdgeId] = false; - EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f; - } - ContourPoint.EdgeID = MatchEdgeId; - ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]]; - } - // Insert a polygon into the mesh - const FPolygonID NewPolygonID = OutProxyMesh.CreatePolygon(SsfToRawMaterial[MeshData->MaterialIndices.Get().Items[TriIndex].Value], Contours); - //Triangulate the polygon - FMeshPolygon& Polygon = OutProxyMesh.GetPolygon(NewPolygonID); - OutProxyMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles); - } - - TArray FaceSmoothingMasks; - FaceSmoothingMasks.AddZeroed(TotalTriangles); - if (GroupIds) - { - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - FaceSmoothingMasks[TriIndex] = MeshData->SmoothingGroup.Get().Items[TriIndex].Value; - } - } - FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, OutProxyMesh); - } - - - - //since its a proxy will only contain one material on it - ssf::ssfString ProxyMaterialGuid = Mesh->MaterialIds.Get().Items[0].Value; - ssf::pssfMaterial ProxyMaterial = FSimplygonSSFHelper::FindMaterialById(SsfScene, ProxyMaterialGuid); - if (ProxyMaterial != nullptr) - { - SetupMaterial(SsfScene, ProxyMaterial, OutMaterial, BaseTexturesPath); - } - } - } - - /** - * Extracts texture from a material channel's textures. Currently only returns one Samples - * @param SsfMaterialChannel SsfMaterialChannel pointer - * @param BaseTexturesPath Base folder path where textures are located - * @param ChannelName Channel name - * @param OutSamples Out Pixel samples from the texture - * @param OutTextureSize Out TextureSizes - */ - void ExtractTextureDescriptors(ssf::pssfScene SceneGraph, - ssf::pssfMaterialChannel SsfMaterialChannel, - FString BaseTexturesPath, - FString ChannelName, - TArray& OutSamples, - FIntPoint& OutTextureSize) - { - for (ssf::pssfMaterialChannelTextureDescriptor TextureDescriptor : SsfMaterialChannel->MaterialChannelTextureDescriptorList) - { - ssf::pssfTexture Texture = FSimplygonSSFHelper::FindTextureById(SceneGraph, TextureDescriptor->TextureID.Get().Value); - - if (Texture != nullptr) - { - FString TextureFilePath = FString::Printf(TEXT("%s/%s"), *BaseTexturesPath, ANSI_TO_TCHAR(Texture->Path.Get().Value.c_str())); - CopyTextureData(OutSamples, OutTextureSize, ChannelName, TextureFilePath); - } - } - } - - /** - * Setup material will extract material information from SsfMaterial and create a flattened material from it. - * @param InSsfScene Base folder path where textures are located - * @param InSsfMaterial Channel name - * @param OutMaterial Out Pixel samples from the texture - * @param InBaseTexturesPath Out TextureSizes - */ - void SetupMaterial(ssf::pssfScene SceneGraph, ssf::pssfMaterial InSsfMaterial, FFlattenMaterial &OutMaterial, FString InBaseTexturesPath) - { - - bool bHasOpacityMask = false; - bool bHasOpacity = false; - for (ssf::pssfMaterialChannel Channel : InSsfMaterial->MaterialChannelList) - { - const FString ChannelName(ANSI_TO_TCHAR(Channel->ChannelName.Get().Value.c_str())); - - if (ChannelName.Compare(BASECOLOR_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - } - else if (ChannelName.Compare(NORMAL_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Normal); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - } - else if (ChannelName.Compare(SPECULAR_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Specular); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - } - else if (ChannelName.Compare(ROUGHNESS_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness),Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - } - else if (ChannelName.Compare(METALLIC_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - } - else if (ChannelName.Compare(OPACITY_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); - bHasOpacity = true; - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, Size); - } - else if (ChannelName.Compare(OPACITY_MASK_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); - bHasOpacityMask = true; - OutMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, Size); - }/**/ - else if (ChannelName.Compare(AO_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::AmbientOcclusion, Size); - } - else if (ChannelName.Compare(EMISSIVE_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, Size); - } - } - - if ( (bHasOpacity && bHasOpacityMask) || bHasOpacity) - { - OutMaterial.BlendMode = BLEND_Translucent; - } - else if (bHasOpacityMask) - { - OutMaterial.BlendMode = BLEND_Masked; - } - - //NOTE: this feature is provided in the advance integration. - // Simplygon can bake both worldspace and tangentspace normal maps. - // worldspace normal maps are better in certain cases. - // We will move the functionality in a separate CL. - - //OutMaterial.bTangentspaceNormalmap = InSsfMaterial->TangentSpaceNormals->Value; - } - - /** - * Wrapper method which calls UAT with ZipUtils to unzip files. - * @param ZipFileName Path to zip file to be extracted. - * @param OutputFolderPath Path to folder where the contents of the zip will be extracted. - */ - bool UnzipDownloadedContent(FString ZipFileName, FString OutputFolderPath) - { - if (!FPaths::FileExists(FPaths::ConvertRelativePathToFull(ZipFileName))) - { - return false; - } - - FString CmdExe = TEXT("cmd.exe"); - - bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - - FString CommandLine = FString::Printf(TEXT("ZipUtils -archive=\"%s\" -extract=\"%s\" -nocompile"), *ZipFileName, *OutputFolderPath); - UatTask(CommandLine); - - return true; - } - - /** - * Wrapper method which call UAT with the ZipUtils to zip files. - * @param InputDirectoryPath Directory to zip - * @param OutputFileName Output zipfile path - */ -bool ZipContentsForUpload(FString InputDirectoryPath, FString OutputFileName) -{ - bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - FString CmdExe = TEXT("cmd.exe"); - FString CommandLine = FString::Printf(TEXT("ZipUtils -archive=\"%s\" -add=\"%s\" -compression=0 -nocompile"), *FPaths::ConvertRelativePathToFull(OutputFileName), *FPaths::ConvertRelativePathToFull(InputDirectoryPath)); - UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("Uat command line %s"), *CommandLine); - - return UatTask(CommandLine); -} - - /** - * Takes in a UAT Command and executes it. Is based on MainFrameAction CreateUatTask. A very minimalistic version. - * @param CommandLine Commandline argument to run against RunUAT.bat - */ - bool UatTask(FString CommandLine) - { -#if PLATFORM_WINDOWS - FString RunUATScriptName = TEXT("RunUAT.bat"); - FString CmdExe = TEXT("cmd.exe"); -#elif PLATFORM_LINUX - FString RunUATScriptName = TEXT("RunUAT.sh"); - FString CmdExe = TEXT("/bin/bash"); -#else - FString RunUATScriptName = TEXT("RunUAT.command"); - FString CmdExe = TEXT("/bin/sh"); -#endif - const bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - - const FString UatPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles") / RunUATScriptName); - - if (!FPaths::FileExists(UatPath)) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("File"), FText::FromString(UatPath)); - FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RequiredFileNotFoundMessage", "A required file could not be found:\n{File}"), Arguments)); - - return false; - } - -#if PLATFORM_WINDOWS - FString FullCommandLine = FString::Printf(TEXT("/c \"\"%s\" %s\""), *UatPath, *CommandLine); -#else - FString FullCommandLine = FString::Printf(TEXT("\"%s\" %s"), *UatPath, *CommandLine); -#endif - while (FPlatformProcess::IsApplicationRunning(TEXT("AutomationTool.exe"))) - { - static const float SleepTime = 0.5f; - FPlatformProcess::Sleep(SleepTime); - UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("UAT already running sleeping for %f seconds"), SleepTime); - } - - TSharedPtr UatProcess = MakeShareable(new FMonitoredProcess(CmdExe, FullCommandLine, true)); - UatProcess->SetSleepInterval(0.1f); - - // create notification item - - const bool bLaunched = UatProcess->Launch(); - - UatProcess->OnOutput().BindLambda([&](FString Message) {UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("UatTask Output %s"), *Message); }); - - while (UatProcess->Update()) {} - - return bLaunched; - - } - - /** - * Get Unique Mateiral Inidices - * @param OriginalMaterialIds Original Material Indicies - * @param ChannelUniqueMaterialIds OutUniqueMaterialIds - */ - void GetUniqueMaterialIndices(const FMeshDescription& MeshDescription, TArray& UniqueMaterialIds) - { - int32 index = 0; - for (const FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs()) - { - UniqueMaterialIds.AddUnique(index); - index++; - } - } - - struct FSkeletalMeshData - { - TArray Influences; - TArray Wedges; - TArray Faces; - TArray Points; - uint32 TexCoordCount; - }; - - /** - * Method to setup a color caster spl object and attach it to the given process node. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Basecolor, Specular, Roughness) - */ - void SetupColorCaster(SPL::ProcessNode& InSplProcessNode, FString Channel) - { - SPL::ColorCaster* colorCaster = new SPL::ColorCaster(); - colorCaster->Dilation = 10; - colorCaster->OutputChannels = 4; - colorCaster->OutputSRGB = false; - colorCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_INTERPOLATE; - colorCaster->ColorType = TCHAR_TO_ANSI(*Channel); - colorCaster->Name = TCHAR_TO_ANSI(*Channel); - colorCaster->Channel = TCHAR_TO_ANSI(*Channel); - colorCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_FLOYDSTEINBERG; - //for spl we need to expliclity set the enabled flag. - colorCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(colorCaster); - } - - /** - * Method to setup a normal caster spl object and attach it to the given process node. - * Note : You can use this method to define custom normal channels as well. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Normal) - * @param bTangentspaceNormals Channel name to cast (i.e Normal) - */ - void SetupNormalCaster(SPL::ProcessNode& InSplProcessNode, FString Channel, bool bTangentspaceNormals = true) - { - SPL::NormalCaster* normalCaster = new SPL::NormalCaster(); - normalCaster->Name = TCHAR_TO_ANSI(*Channel); - normalCaster->Channel = TCHAR_TO_ANSI(*Channel); - normalCaster->GenerateTangentSpaceNormals = bTangentspaceNormals; - normalCaster->OutputChannels = 3; - normalCaster->Dilation = 10; - normalCaster->FlipGreen = false; - normalCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_NEARESTNEIGHBOR; - normalCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_NO_DITHER; - normalCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(normalCaster); - } - - /** - * Method to setup a normal caster spl object and attach it to the given process node. - * Note : You can use this method to define custom normal channels as well. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Normal) - * @param bTangentspaceNormals Channel name to cast (i.e Normal) - */ - void SetupOpacityCaster(SPL::ProcessNode& InSplProcessNode, FString Channel) - { - SPL::OpacityCaster* opacityCaster = new SPL::OpacityCaster(); - opacityCaster->Dilation = 10; - opacityCaster->OutputChannels = 4; - opacityCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_INTERPOLATE; - opacityCaster->ColorType = TCHAR_TO_ANSI(*Channel); - opacityCaster->Name = TCHAR_TO_ANSI(*Channel); - opacityCaster->Channel = TCHAR_TO_ANSI(*Channel); - opacityCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_FLOYDSTEINBERG; - - //for spl we need to expliclity set the enabled flag. - opacityCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(opacityCaster); - } - - /** - * Setup Material caster for a spl process node - * @param InMaterialProxySettings Material proxy settings - * @param InSplProcessNode SplProcess node to attach casters to - * @param InOutputMaterialBlendMode EBlendMode (Opaque, Translucent, Masked) are supported - * @returns The calculated view distance - */ - void SetupSplMaterialCasters(const FMaterialProxySettings& InMaterialProxySettings, SPL::ProcessNode& InSplProcessNode, EBlendMode InOutputMaterialBlendMode = BLEND_Opaque) - { - SetupColorCaster(InSplProcessNode, BASECOLOR_CHANNEL); - - if (InMaterialProxySettings.bRoughnessMap) - { - SetupColorCaster(InSplProcessNode, ROUGHNESS_CHANNEL); - } - if (InMaterialProxySettings.bSpecularMap) - { - SetupColorCaster(InSplProcessNode, SPECULAR_CHANNEL); - } - if (InMaterialProxySettings.bMetallicMap) - { - SetupColorCaster(InSplProcessNode, METALLIC_CHANNEL); - } - - if (InMaterialProxySettings.bNormalMap) - { - SetupNormalCaster(InSplProcessNode, NORMAL_CHANNEL, true/*InMaterialProxySettings.bUseTangentSpace*/); - } - - if (InMaterialProxySettings.bOpacityMap) - { - SetupOpacityCaster(InSplProcessNode, OPACITY_CHANNEL); - } - else if (InMaterialProxySettings.bOpacityMaskMap) - { - SetupColorCaster(InSplProcessNode, OPACITY_MASK_CHANNEL); - } - - //NOTE: Enable this block once AO feature is moved into vanilla integration. - if (InMaterialProxySettings.bAmbientOcclusionMap) - { - SetupColorCaster(InSplProcessNode, AO_CHANNEL); - } - - if (InMaterialProxySettings.bEmissiveMap) - { - SetupColorCaster(InSplProcessNode, EMISSIVE_CHANNEL); - } - } - - /** - * Calculates the view distance that a mesh should be displayed at. - * @param MaxDeviation - The maximum surface-deviation between the reduced geometry and the original. This value should be acquired from Simplygon - * @returns The calculated view distance - */ - float CalculateViewDistance( float MaxDeviation ) - { - // We want to solve for the depth in world space given the screen space distance between two pixels - // - // Assumptions: - // 1. There is no scaling in the view matrix. - // 2. The horizontal FOV is 90 degrees. - // 3. The backbuffer is 1920x1080. - // - // If we project two points at (X,Y,Z) and (X',Y,Z) from view space, we get their screen - // space positions: (X/Z, Y'/Z) and (X'/Z, Y'/Z) where Y' = Y * AspectRatio. - // - // The distance in screen space is then sqrt( (X'-X)^2/Z^2 + (Y'-Y')^2/Z^2 ) - // or (X'-X)/Z. This is in clip space, so PixelDist = 1280 * 0.5 * (X'-X)/Z. - // - // Solving for Z: ViewDist = (X'-X * 640) / PixelDist - - const float ViewDistance = (MaxDeviation * 960.0f); - return ViewDistance; - } - - /** - * Compute mapping image size from the given material proxy settings - * @param Settings Material Proxy Settings - */ - static FIntPoint ComputeMappingImageSize(const FMaterialProxySettings& Settings) - { - FIntPoint ImageSize = Settings.TextureSize; - - return ImageSize; - } - - /** - * Method to swap axis - * (1,0,0) - * (0,0,1) - * (0,1,0) - */ - const FMatrix& GetConversionMatrixYUP() - { - - static FMatrix m; - static bool bInitialized = false; - if (!bInitialized) - { - m.SetIdentity(); - - bInitialized = true; - } - return m; - } - - /** - * Method to create a SsfMeshData from FMeshDescription - * @param InRawMesh Rawmesh to create SsfMeshData from - * @param InTextureBounds Texture bounds - * @param InTexCoords Corrected texture coordinates generated after material flattening. - */ - ssf::pssfMeshData CreateSSFMeshDataFromRawMesh(const FMeshDescription& SrcRawMesh, TArray InTextureBounds, TArray InTexCoords) - { - TVertexAttributesConstRef VertexPositions = SrcRawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TVertexInstanceAttributesConstRef VertexInstanceNormals = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesConstRef VertexInstanceTangents = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesConstRef VertexInstanceColors = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesConstRef VertexInstanceUVs = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - int32 NumVertices = SrcRawMesh.Vertices().Num(); - int32 NumWedges = 0; - for (const FPolygonID& PolygonID : SrcRawMesh.Polygons().GetElementIDs()) - { - const FMeshPolygon& Polygon = SrcRawMesh.GetPolygon(PolygonID); - NumWedges += Polygon.Triangles.Num() * 3; - } - int32 NumTris = NumWedges / 3; - - if (NumWedges == 0) - { - return nullptr; - } - - //assuming everything is left-handed so no need to change winding order and handedness. SSF supports both - - ssf::pssfMeshData SgMeshData = new ssf::ssfMeshData(); - - //setup vertex coordinates - ssf::ssfList & SsfCoorinates = SgMeshData->Coordinates.Create(); - SsfCoorinates.Items.resize(NumVertices); - TMap MeshToSsfVertexID; - MeshToSsfVertexID.Reserve(NumVertices); - int32 VertexIndex = 0; - for(const FVertexID& VertexID : SrcRawMesh.Vertices().GetElementIDs()) - { - ssf::ssfVector3 CurrentVertex; - FVector4 Position = GetConversionMatrixYUP().TransformPosition(VertexPositions[VertexID]); - CurrentVertex.V[0] = double(Position.X); - CurrentVertex.V[1] = double(Position.Y); - CurrentVertex.V[2] = double(Position.Z); - SsfCoorinates.Items[VertexIndex] = CurrentVertex; - MeshToSsfVertexID.Add(VertexID, VertexIndex); - VertexIndex++; - } - - //setup triangle data - ssf::ssfList& SsfTriangleIndices = SgMeshData->TriangleIndices.Create(); - ssf::ssfList& SsfMaterialIndices = SgMeshData->MaterialIndices.Create(); - ssf::ssfList& SsfSmoothingGroups = SgMeshData->SmoothingGroup.Create(); - - SsfTriangleIndices.Items.resize(NumTris); - SsfMaterialIndices.Items.resize(NumTris); - SsfSmoothingGroups.Items.resize(NumTris); - - bool bHasNormals = VertexInstanceNormals.GetNumElements() == NumWedges; - bool bHasTangents = bHasNormals && (VertexInstanceTangents.GetNumElements() == NumWedges) && (VertexInstanceBinormalSigns.GetNumElements() == NumWedges); - - ssf::ssfList EmptyList; - ssf::ssfList& SsfTangents = bHasTangents ? SgMeshData->Tangents.Create() : EmptyList; - ssf::ssfList& SsfBitangents = bHasTangents ? SgMeshData->Bitangents.Create() : EmptyList; - ssf::ssfList& SsfNormals = bHasNormals ? SgMeshData->Normals.Create() : EmptyList; - if (bHasNormals) - { - if (bHasTangents) - { - SsfTangents.Items.resize(NumWedges); - SsfBitangents.Items.resize(NumWedges); - } - SsfNormals.Items.resize(NumWedges); - } - - const int32 TexCoordNumber = FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_MESH_TEXTURE_COORDS); - ssf::ssfNamedList SsfTextureCoordinates[MAX_MESH_TEXTURE_COORDS]; - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - //Since SSF uses Named Channels - SsfTextureCoordinates[TexCoordIndex].Name = FSimplygonSSFHelper::TCHARToSSFString(*FString::Printf(TEXT("TexCoord%d"), TexCoordIndex)); - SsfTextureCoordinates[TexCoordIndex].Items.resize(NumWedges); - } - - ssf::ssfNamedList SsfColorMap; - bool bHasVertexColor = VertexInstanceColors.GetNumElements() == NumWedges; - if (bHasVertexColor) - { - //setup the color named channel . Currently its se to index zero. If multiple colors channel are need then use an index instead of 0 - SsfColorMap.Name = FSimplygonSSFHelper::TCHARToSSFString(*FString::Printf(TEXT("Colors%d"), 0)); - SsfColorMap.Items.resize(NumWedges); - } - - //Smooth group - TArray FaceSmoothingMasks; - FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(SrcRawMesh, FaceSmoothingMasks); - - //Reverse winding switches - bool bReverseWinding = true; - - int32 TriangleIndex = 0; - for (const FPolygonID& PolygonID : SrcRawMesh.Polygons().GetElementIDs()) - { - const FMeshPolygon& Polygon = SrcRawMesh.GetPolygon(PolygonID); - - FPolygonGroupID PolygonGroupID = SrcRawMesh.GetPolygonPolygonGroup(PolygonID); - int32 MaterialIndex = PolygonGroupID.GetValue(); - - for (const FMeshTriangle& Triangle : Polygon.Triangles) - { - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - int32 DestCornerIndex = bReverseWinding ? 2 - CornerIndex : CornerIndex; - FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(CornerIndex); - SsfTriangleIndices.Items[TriangleIndex].V[DestCornerIndex] = MeshToSsfVertexID[SrcRawMesh.GetVertexInstanceVertex(VertexInstanceID)]; - - //NTBs - if (bHasNormals) - { - FVector Normal = VertexInstanceNormals[VertexInstanceID]; - if (bHasTangents) - { - FVector Tangent = VertexInstanceTangents[VertexInstanceID]; - FVector Bitangent = FVector::CrossProduct(Normal, Tangent).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; - - ssf::ssfVector3 SsfTangent; - FVector4 Tangent4 = GetConversionMatrixYUP().TransformPosition(Tangent); - SsfTangent.V[0] = double(Tangent4.X); - SsfTangent.V[1] = double(Tangent4.Y); - SsfTangent.V[2] = double(Tangent4.Z); - SsfTangents.Items[TriangleIndex * 3 + DestCornerIndex] = SsfTangent; - - ssf::ssfVector3 SsfBitangent; - FVector4 Bitangent4 = GetConversionMatrixYUP().TransformPosition(Bitangent); - SsfBitangent.V[0] = double(Bitangent4.X); - SsfBitangent.V[1] = double(Bitangent4.Y); - SsfBitangent.V[2] = double(Bitangent4.Z); - SsfBitangents.Items[TriangleIndex * 3 + DestCornerIndex] = SsfBitangent; - } - - ssf::ssfVector3 SsfNormal; - FVector4 Normal4 = GetConversionMatrixYUP().TransformPosition(Normal); - SsfNormal.V[0] = double(Normal4.X); - SsfNormal.V[1] = double(Normal4.Y); - SsfNormal.V[2] = double(Normal4.Z); - SsfNormals.Items[TriangleIndex * 3 + DestCornerIndex] = SsfNormal; - } - - //Vertex color - if (bHasVertexColor) - { - FLinearColor LinearColor(VertexInstanceColors[VertexInstanceID]); - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[0] = LinearColor.R; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[1] = LinearColor.G; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[2] = LinearColor.B; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[3] = LinearColor.A; - } - - - //Texcoords - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - bool bUseInTexCoord = (TexCoordIndex == 0 && InTexCoords.Num() == NumWedges); - int32 NumTexCoord = bUseInTexCoord ? InTexCoords.Num() : VertexInstanceUVs.GetNumElements(); - if (NumTexCoord == NumWedges) - { - // Compute texture bounds for current material. - float MinU = 0, ScaleU = 1; - float MinV = 0, ScaleV = 1; - if (InTextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 && InTexCoords.Num() == 0) - { - const FBox2D& Bounds = InTextureBounds[MaterialIndex]; - if (Bounds.GetArea() > 0) - { - MinU = Bounds.Min.X; - MinV = Bounds.Min.Y; - ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X); - ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y); - } - } - - const FVector2D& TexCoord = bUseInTexCoord ? InTexCoords[TriangleIndex * 3 + CornerIndex] : VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex); - ssf::ssfVector2 temp; - temp.V[0] = (TexCoord.X - MinU) * ScaleU; - temp.V[1] = (TexCoord.Y - MinV) * ScaleV; - SsfTextureCoordinates[TexCoordIndex].Items[TriangleIndex * 3 + DestCornerIndex] = temp; - } - } - - } - - //Material - SsfMaterialIndices.Items[TriangleIndex] = MaterialIndex; - - //Smooth group - SsfSmoothingGroups.Items[TriangleIndex] = FaceSmoothingMasks[TriangleIndex]; - - TriangleIndex++; - } - } - - SgMeshData->MaterialIndices.Create(); - - //Push back all the data... - - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - SgMeshData->TextureCoordinatesList.push_back(SsfTextureCoordinates[TexCoordIndex]); - } - if (bHasVertexColor) - { - SgMeshData->ColorsList.push_back(SsfColorMap); - } - - - return SgMeshData; - } - - /** - * Method to copy texture's pixel data into a FColor array - * @param OutSamples Out TArray where texture data is copied to. - * @param OutTextureSize Out Texture sizes - * @param TexturePath Path to Texture - * @param IsNormalMap Is this normalmap that we are reading - */ - void CopyTextureData( - TArray& OutSamples, - FIntPoint& OutTextureSize, - FString ChannelName, - FString TexturePath, - bool IsNormalMap = false - ) - { - IImageWrapperModule& ImageWrapperModule = FModuleManager::GetModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - TArray TextureData; - if (!FFileHelper::LoadFileToArray(TextureData, *FPaths::ConvertRelativePathToFull(TexturePath)) && TextureData.Num() > 0) - { - UE_LOG(LogSimplygonSwarm, Warning, TEXT("Unable to find Texture file %s"), *TexturePath); - } - else - { - const TArray* RawData = NULL; - - if (ImageWrapper->SetCompressed(TextureData.GetData(), TextureData.Num()) && ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawData)) - { - OutTextureSize.X = ImageWrapper->GetHeight(); - OutTextureSize.Y = ImageWrapper->GetWidth(); - int32 TexelsCount = ImageWrapper->GetHeight() * ImageWrapper->GetWidth(); - OutSamples.Empty(TexelsCount); - OutSamples.AddUninitialized(TexelsCount); - - for (int32 X = 0; X < ImageWrapper->GetHeight(); ++X) - { - for (int32 Y = 0; Y < ImageWrapper->GetWidth(); ++Y) - { - int32 PixelIndex = ImageWrapper->GetHeight() * X + Y; - - OutSamples[PixelIndex].B = (*RawData)[PixelIndex*sizeof(FColor) + 0]; - OutSamples[PixelIndex].G = (*RawData)[PixelIndex*sizeof(FColor) + 1]; - OutSamples[PixelIndex].R = (*RawData)[PixelIndex*sizeof(FColor) + 2]; - OutSamples[PixelIndex].A = (*RawData)[PixelIndex*sizeof(FColor) + 3]; - } - } - } - - } - } - - /** - * Method to create a SsfMaterialChannel object - * @param InSamples Color data to output to texture. - * @param InTextureSize Texture size - * @param SsfTextureTable SsfTexture Table - * @param TextureName Texture name - * @param BaseTexturePath Texture base folder to use - * @param IsSRGB Texture is SRGB based or not. - */ - ssf::pssfMaterialChannel CreateSsfMaterialChannel( - const TArray& InSamples, - FIntPoint InTextureSize, - ssf::pssfTextureTable SsfTextureTable, - FString ChannelName, FString TextureName, FString BaseTexturePath, bool IsSRGB = true) - { - - ssf::pssfMaterialChannel SsfMaterialChannel = new ssf::ssfMaterialChannel(); - SsfMaterialChannel->ChannelName.Set(FSimplygonSSFHelper::TCHARToSSFString(*ChannelName)); - - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - - if (InSamples.Num() >= 1) - { - - IImageWrapperModule& ImageWrapperModule = FModuleManager::GetModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - FString TextureOutputRelative = FString::Printf(TEXT("%s/%s.png"), ANSI_TO_TCHAR(SsfTextureTable->TexturesDirectory->Value.c_str()), *TextureName); - FString TextureOutputPath = FString::Printf(TEXT("%s%s"), *BaseTexturePath, *TextureOutputRelative); - - if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(&InSamples[0], InSamples.Num() * sizeof(FColor), InTextureSize.X, InTextureSize.Y, ERGBFormat::BGRA, 8)) - { - if (FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *TextureOutputPath)) - { - ssf::pssfTexture SsfTexture = new ssf::ssfTexture(); - ssf::pssfMaterialChannelTextureDescriptor SsfTextureDescriptor = new ssf::ssfMaterialChannelTextureDescriptor(); - SsfTexture->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfTexture->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*TextureName)); - SsfTexture->Path.Set(FSimplygonSSFHelper::TCHARToSSFString(*TextureOutputRelative)); - SsfTextureDescriptor->TextureID.Set(SsfTexture->Id.Get()); - - FString TexCoordText = TEXT("TexCoord0"); - SsfTextureDescriptor->TexCoordSet.Set(FSimplygonSSFHelper::TCHARToSSFString(*TexCoordText)); - - SsfMaterialChannel->MaterialChannelTextureDescriptorList.push_back(SsfTextureDescriptor); - FString ShadingNetwork = FString::Printf(SHADING_NETWORK_TEMPLATE, *TextureName, *TexCoordText, 0); - SsfMaterialChannel->ShadingNetwork.Set(FSimplygonSSFHelper::TCHARToSSFString(*ShadingNetwork)); - SsfTextureTable->TextureList.push_back(SsfTexture); - } - else - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("Could not save to file %s"), *TextureOutputPath); - } - - } - - } - else - { - SsfMaterialChannel->Color.Create(); - SsfMaterialChannel->Color->V[0] = 1.0f; - SsfMaterialChannel->Color->V[1] = 1.0f; - SsfMaterialChannel->Color->V[2] = 1.0f; - SsfMaterialChannel->Color->V[3] = 1.0f; - } - - return SsfMaterialChannel; - } - - /** - * Method to create a SsfMaterialChannel object - * @param InputMaterials List of flatten materials. - * @param InMaterialLODSettings Material Proxy Settings - * @param SsfMaterialTable Material Table - * @param SsfTextureTable Texture Table - * @param BaseTexturePath Base Texture Path - * @param bReleaseInputMaterials Wether or not release Flatten Material you are done. - * @param OutMaterialMapping Id to Guid mapping. - */ - bool CreateSSFMaterialFromFlattenMaterial( - const TArray& InputMaterials, - const FMaterialProxySettings& InMaterialLODSettings, - ssf::pssfMaterialTable SsfMaterialTable, - ssf::pssfTextureTable SsfTextureTable, - FString BaseTexturePath, - bool bReleaseInputMaterials, TMap& OutMaterialMapping) -{ - if (InputMaterials.Num() == 0) - { - //If there are no materials, feed Simplygon with a default material instead. - UE_LOG(LogSimplygonSwarm, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated.")); - return false; - } - - bool bFillEmptyEmissive = false; - bool bDiscardEmissive = true; - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - if (FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num() > 1 || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] != FColor::Black)) - { - bFillEmptyEmissive = true; - } - - bDiscardEmissive &= ((FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive)) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black)); - } - - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - FString MaterialGuidString = FGuid::NewGuid().ToString(); - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - FString MaterialName = FString::Printf(TEXT("Material%d"), MaterialIndex); - - ssf::pssfMaterial SsfMaterial = new ssf::ssfMaterial(); - SsfMaterial->Id.Set(FSimplygonSSFHelper::TCHARToSSFString(*MaterialGuidString)); - SsfMaterial->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*MaterialName)); - - OutMaterialMapping.Add(MaterialIndex, MaterialGuidString); - - // Does current material have BaseColor? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse)) - { - FString ChannelName(BASECOLOR_CHANNEL); - ssf::pssfMaterialChannel BaseColorChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - - SsfMaterial->MaterialChannelList.push_back(BaseColorChannel); - - //NOTE: use the commented setting once switching between tangentspace/worldspace is added into the vanilla version of the engine. - SsfMaterial->TangentSpaceNormals->Create(true /*InMaterialLODSettings.bUseTangentSpace*/); - } - - // Does current material have Metallic? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic)) - { - FString ChannelName(METALLIC_CHANNEL); - ssf::pssfMaterialChannel MetallicChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(MetallicChannel); - } - - // Does current material have Specular? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular)) - { - FString ChannelName(SPECULAR_CHANNEL); - ssf::pssfMaterialChannel SpecularChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(SpecularChannel); - } - - // Does current material have Roughness? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness)) - { - FString ChannelName(ROUGHNESS_CHANNEL); - ssf::pssfMaterialChannel RoughnessChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(RoughnessChannel); - } - - //Does current material have a normalmap? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal)) - { - FString ChannelName(NORMAL_CHANNEL); - SsfMaterial->TangentSpaceNormals.Create(); - SsfMaterial->TangentSpaceNormals.Set(true); - ssf::pssfMaterialChannel NormalChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath, false); - SsfMaterial->MaterialChannelList.push_back(NormalChannel); - } - - // Does current material have Opacity? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity)) - { - FString ChannelName(OPACITY_CHANNEL); - ssf::pssfMaterialChannel OpacityChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(OpacityChannel); - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask)) - { - FString ChannelName(OPACITY_MASK_CHANNEL); - ssf::pssfMaterialChannel OpacityChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(OpacityChannel); - } - - // Emissive could have been outputted by the shader/swarm due to various reasons, however we don't always need the data that was created so we discard it - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black)) - { - FString ChannelName(EMISSIVE_CHANNEL); - ssf::pssfMaterialChannel EmissiveChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(EmissiveChannel); - } - else if (bFillEmptyEmissive && !FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive)) - { - TArray Sample; - Sample.Add(FColor::Black); - FIntPoint Size(1, 1); - FString ChannelName(EMISSIVE_CHANNEL); - TArray BlackEmissive; - BlackEmissive.AddZeroed(1); - ssf::pssfMaterialChannel EmissiveChannel = CreateSsfMaterialChannel(Sample, Size, SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(EmissiveChannel); - } - - //NOTE: Enable this once AO baking functionality is moved into the engine. - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion)) - { - FString ChannelName(AO_CHANNEL); - ssf::pssfMaterialChannel AOChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(AOChannel); - } - - SsfMaterialTable->MaterialList.push_back(SsfMaterial); - - if (bReleaseInputMaterials) - { - // Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here - // and above the call chain. - const_cast(&FlattenMaterial)->ReleaseData(); - } - } - - return true; - } -}; - -TUniquePtr GSimplygonMeshReduction; - - -void FSimplygonSwarmModule::StartupModule() -{ - GSimplygonMeshReduction.Reset(FSimplygonSwarm::Create()); - FModuleManager::Get().LoadModule(FName("ImageWrapper")); - IModularFeatures::Get().RegisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -void FSimplygonSwarmModule::ShutdownModule() -{ - FSimplygonRESTClient::Shutdown(); - IModularFeatures::Get().UnregisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -IMeshReduction* FSimplygonSwarmModule::GetStaticMeshReductionInterface() -{ - return nullptr; -} - -IMeshReduction* FSimplygonSwarmModule::GetSkeletalMeshReductionInterface() -{ - return nullptr; -} - -IMeshMerging* FSimplygonSwarmModule::GetMeshMergingInterface() -{ - return nullptr; -} - -class IMeshMerging* FSimplygonSwarmModule::GetDistributedMeshMergingInterface() -{ - return GSimplygonMeshReduction.Get(); -} - -FString FSimplygonSwarmModule::GetName() -{ - return FString("SimplygonSwarm"); -} - -#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h deleted file mode 100644 index ba32fd557124..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "HAL/Runnable.h" -#include "HAL/ThreadSafeBool.h" -#include "HAL/FileManager.h" -#include "Misc/MessageDialog.h" -#include "Misc/SlowTask.h" -#include "Misc/FileHelper.h" -#include "Misc/ScopeLock.h" -#include "Serialization/ArrayWriter.h" -#include "Containers/Queue.h" -#include "SimplygonSwarmCommon.h" - -#include "MeshUtilities.h" - -#include "Runtime/Online/HTTP/Public/Interfaces/IHttpResponse.h" -#include "Runtime/Online/HTTP/Public/Interfaces/IHttpRequest.h" -#include "Runtime/Online/HTTP/Public/HttpModule.h" - -/** Enum representing state used by Simplygon Grid Server */ -enum SimplygonRESTState -{ - SRS_UNKNOWN, - SRS_FAILED, - SRS_ASSETUPLOADED_PENDING, - SRS_MULTIPARTASSETUPLOAD_PENDING, - SRS_ASSETUPLOADED, - SRS_JOBCREATED_PENDING, - SRS_JOBCREATED, - SRS_JOBSETTINGSUPLOADED_PENDING, - SRS_JOBSETTINGSUPLOADED, - SRS_JOBPROCESSING_PENDING, - SRS_JOBPROCESSING, - SRS_JOBPROCESSED, - SRS_ASSETDOWNLOADED_PENDING, - SRS_ASSETDOWNLOADED -}; - -/** Enum representing state used internally by REST Client to manage multi-part asset uploading to Simplygon Grid */ -enum EUploadPartState -{ - UPS_BEGIN, // start uploading (hand shake) - UPS_UPLOADING_PART, // upload part - UPS_END // upload transaction completed -}; - -/** -Intermediate struct to hold upload file chunks for multi-part upload -Note : Multi part upload are required as the Simplygon Grid Server has a 2GB file upload limitation -*/ -struct FSwarmUploadPart -{ - /** Upload part binary chunck */ - TArray Data; - - /** Part number */ - int32 PartNumber; - - /** Bool if part has been uploaded */ - FThreadSafeBool PartUploaded; - - ~FSwarmUploadPart() - { - Data.Empty(); - } - -}; - -/** Struct holding essential task data for task management. */ -struct FSwarmTaskkData -{ - /** Path to the zip file that needs to be uploaded */ - FString ZipFilePath; - - /** Path to spl file that needs to be uploaded */ - FString SplFilePath; - - /** Path to zip file containing resulting geometry */ - FString OutputZipFilePath; - - /** Swarm Job Directory */ - FString JobDirectory; - - /** Swarm Job Name - Can be used to track jobs using the Admin Utility*/ - FString JobName; - - /** Lock for synchornization between threads */ - FCriticalSection* StateLock; - - /** Unique Job Id */ - FGuid ProcessorJobID; - - /** Set if the task upload has been completed */ - FThreadSafeBool TaskUploadComplete; - - /** Supports Dithered Transition */ - bool bDitheredTransition; - // Whether or not emissive should be outputted - bool bEmissive; -}; - -/** Struct that hold intermediate data used to communicate next state to Simplygon Grid Server */ -struct FSwarmJsonResponse -{ - /** Unique JobId */ - FString JobId; - - /** Unique asset id returned from server. - Note : This can be used to check if asset already is available on the server to save network bandwidth - */ - FString AssetId; - - /** Supports Dithered Transition */ - FString ErrorMessage; - - /** Supports Dithered Transition */ - uint32 Progress; - - /** Supports Dithered Transition */ - FString Status; - - /** Supports Dithered Transition */ - FString OutputAssetId; - - /** Supports Dithered Transition */ - FString UploadId; -}; - -/** Simplygon Swarm Task. Responsible for communicating with the Grid Server */ -class FSimplygonSwarmTask -{ - -public: - DECLARE_DELEGATE_OneParam(FSimplygonSwarmTaskDelegate, const FSimplygonSwarmTask&); - - FSimplygonSwarmTask(const FSwarmTaskkData& InTaskData); - ~FSimplygonSwarmTask(); - - /* Method to get/set Task Sate */ - SimplygonRESTState GetState() const; - void SetState(SimplygonRESTState InState); - - bool IsFinished() const; - - /*~ Events */ - FSimplygonSwarmTaskDelegate& OnAssetDownloaded() { return OnAssetDownloadedDelegate; } - FSimplygonSwarmTaskDelegate& OnAssetUploaded() { return OnAssetUploadedDelegate; } - FSimplygonSwarmTaskDelegate& OnSwarmTaskFailed() { return OnTaskFailedDelegate; } - - void EnableDebugLogging(); - - /** - Method to setup the server the task should use. - Currently only Single host is supported. - */ - void SetHost(FString InHostAddress); - - /* - The following method is used to split the asset into multiple parts for a multi part upload. - */ - void CreateUploadParts(const int32 MaxUploadFileSize); - - /* - Tells if the current task needs to be uploaded as seperate part due to data size limits - */ - bool NeedsMultiPartUpload(); - - //~Being Rest methods - void AccountInfo(); - void CreateJob(); - void UploadJobSettings(); - void ProcessJob(); - void GetJob(); - void UploadAsset(); - void DownloadAsset(); - - /* - Note for multi part upload the following need to happen - Call upload being to setup the multi part upload - upload parts using MultiPartUplaodPart - Call MultiPartupload end - */ - void MultiPartUploadBegin(); - void MultiPartUploadPart(const uint32 partNo); - void MultiPartUploadEnd(); - void MultiPartUploadGet(); - - //~Being Response methods - void AccountInfo_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void CreateJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void UploadJobSettings_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void ProcessJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void GetJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void UploadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void DownloadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void AddAuthenticationHeader(TSharedRef request); - - //Multi part upload responses - void MultiPartUploadBegin_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadPart_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadEnd_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadGet_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - - //~End Rest methods - - /** Helper method use to deserialze json respone from SimplygonGRid and popupate the FSwarmJsonRepose Struct*/ - bool ParseJsonMessage(FString InJsonMessage, FSwarmJsonResponse& OutResponseData); - - /** Essential Task Data */ - FSwarmTaskkData TaskData; - -private: - - /** Task State */ - SimplygonRESTState State; - - /** Job Id */ - FString JobId; - - /** Asset Id returned from the server */ - FString InputAssetId; - - /** Output Asset id */ - FString OutputAssetId; - - /** Is Completed */ - FThreadSafeBool IsCompleted; - - /** Enable Debug Logging */ - bool bEnableDebugLogging; - - /** Parts left to upload (Multi-part Uploading) */ - FThreadSafeCounter RemainingPartsToUpload; - - /** Debug HttpRequestCounter - Note: This was added to track issues when two resposnes came for a completed job. - Since the job was completed before the object is partially destoreyd when a new response came in. - The import file method failed. This was added for debugging. The most likely cause if that the respose delegate is never cleaned up. - (This must be zero else, Sometime two responses for job completed arrive which caused issue) */ - FThreadSafeCounter DebugHttpRequestCounter; - - /** Multipart upload has been initalized*/ - bool bMultiPartUploadInitialized; - - /** Multi part upload data*/ - TIndirectArray UploadParts; - - /** Total number of parts to upload*/ - uint32 TotalParts; - - /** Simplygon Grid Server IP address */ - FString HostName; - - /** API Key used to communicate with the Grid Server */ - FString APIKey; - - /** Upload Id used for multipart upload */ - FString UploadId; - - /** Total upload size */ - int32 UploadSize; - - //~ Delegates Begin - FSimplygonSwarmTaskDelegate OnAssetDownloadedDelegate; - FSimplygonSwarmTaskDelegate OnAssetUploadedDelegate; - FSimplygonSwarmTaskDelegate OnProgressUpdated; - FSimplygonSwarmTaskDelegate OnTaskFailedDelegate; - //~ Delegates End - - /** Map that stores pending request. They need to be cleaned up when destroying the instance. Especially if job has completed*/ - TMap, FString> PendingRequests; - - bool bProcessingStarted; - float TimeSinceProcessStart; -}; - -/* -Simplygon REST Based Clinet. Responsible for managing/controlling task. Runs on its own thread. -*/ -class FSimplygonRESTClient : public FRunnable -{ -public: - - /* Add a swarm task to the Queue*/ - void AddSwarmTask(TSharedPtr& InTask); - - void SetMaxUploadSizeInBytes(int32 InMaxUploadSizeInBytes); - - /*Get pointer to the runnable*/ - static FSimplygonRESTClient* Get(); - - static void Shutdown(); - -private: - - - FSimplygonRESTClient(); - - ~FSimplygonRESTClient(); - - //~ Begin FRunnable Interface - virtual bool Init(); - - virtual uint32 Run(); - - /* Method that goes through all task and checks their status*/ - void UpdateTaskStates(); - - /* Moves jobs into the Jobs Buffer*/ - void MoveItemsToBoundedArray(); - - virtual void Stop(); - - virtual void Exit(); - //~ End FRunnable Interface - - - void Wait(const float InSeconds, const float InSleepTime = 0.1f); - - void EnusureCompletion(); - - /** Checks if there's been any Stop requests */ - FORCEINLINE bool ShouldStop() const - { - return StopTaskCounter.GetValue() > 0; - } - -private: - - /*Static instance*/ - static FSimplygonRESTClient* Runnable; - - /*Critical Section*/ - FCriticalSection CriticalSectionData; - - FThreadSafeCounter StopTaskCounter; - - /* a local buffer as to limit the number of concurrent jobs. */ - TArray> JobsBuffer; - - /* Pending Jobs Queue */ - TQueue, EQueueMode::Mpsc> PendingJobs; - - /* Thread*/ - FRunnableThread* Thread; - - /* Simplygon Grid Server IP Address*/ - FString HostName; - - /* API Key*/ - FString APIKey; - - /* Add a swarm task to the Queue*/ - bool bEnableDebugging; - - /* Sleep time between status updates*/ - float DelayBetweenRuns; - - /* Number of Simultaneous Jobs to Manage*/ - int32 JobLimit; - - /* Max Upload size in bytes . Should not be more than the 2GB data limit for the Grid Server*/ - int32 MaxUploadSizeInBytes; -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h deleted file mode 100644 index 6cb6ff051511..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#pragma once - -#include "CoreMinimal.h" -#include "RawMesh.h" -#include "MeshUtilities.h" -#include "MaterialUtilities.h" -#include "MeshBoneReduction.h" -#include "ComponentReregisterContext.h" -#include "ImageUtils.h" -THIRD_PARTY_INCLUDES_START -#include "ssf.h" -THIRD_PARTY_INCLUDES_END -#include "IImageWrapper.h" -#include "SPLInclude.h" - -namespace SPL = Simplygon::SPL::v80; - -#ifndef SPL_VERSION - #define SPL_VERSION 8 -#endif // !SPL_VERSION - -#define SPL_CURRENT_VERSION SPL_VERSION - -// You should place include statements to your module's private header files here. You only need to -// add includes for headers that are used in most of your module's source files though. diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h deleted file mode 100644 index 83c279ccf166..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#pragma once - -#include "SimplygonSwarmCommon.h" -#include - -struct FSimplygonSSFHelper -{ -public: - - /** - * Return a new GUID as SsfString - */ - static ssf::ssfString SSFNewGuid() - { - return TCHARToSSFString(*FGuid::NewGuid().ToString()); - } - - /** - * Return a empty GUID as SsfString - */ - static ssf::ssfString SFFEmptyGuid() - { - return TCHARToSSFString(*FGuid().ToString()); - } - - /** - * Convert TCHAR to SsfString - */ - static ssf::ssfString TCHARToSSFString(const TCHAR* str) - { - return ssf::ssfString(std::basic_string(str)); - } - - /** - * Compare two ssf strings - */ - static bool CompareSSFStr(ssf::ssfString lhs, ssf::ssfString rhs) - { - if (lhs.Value == rhs.Value) - return true; - - return false; - } - - /** - * Get TextureCoordinate from SsfTexcoordinateList based on TextureSetName - * @param TextureCoordsList List of TextureCoorniates - * @result a ssf::ssfNamedList object containing uv data nullptr otherwise - */ - static ssf::ssfNamedList FindByTextureSetName(std::list < ssf::ssfNamedList > TextureCoordsList, ssf::ssfString Name) - { - auto UVSet = std::find_if(TextureCoordsList.begin(), TextureCoordsList.end(), - [Name](const ssf::ssfNamedList InTextureSet) - { - if (FSimplygonSSFHelper::CompareSSFStr(Name, InTextureSet.Name)) - { - return true; - } - return false; - }); - - if (UVSet != std::end(TextureCoordsList)) - { - return *UVSet; - } - - return ssf::ssfNamedList < ssf::ssfVector2>(); - } - - /** - * Get SsfTexture by Guid - * @param SsfScene SsfScene - * @param TextureId Texture Guid String - * @result a ssf::pssfTexture object nullptr otherwise - */ - static ssf::pssfTexture FindTextureById(ssf::pssfScene SsfScene, ssf::ssfString TextureId) - { - auto Texture = std::find_if(SsfScene->TextureTable->TextureList.begin(), SsfScene->TextureTable->TextureList.end(), - [TextureId](const ssf::pssfTexture tex) - { - if (FSimplygonSSFHelper::CompareSSFStr(tex->Id.Get().Value, TextureId)) - { - return true; - } - return false; - }); - - - if (Texture != std::end(SsfScene->TextureTable->TextureList)) - { - if (!Texture->IsNull()) - { - return *Texture; - } - } - - return nullptr; - } - - /** - * Get TextureCoordinate from SsfTexcoordinateList based on TextureSetName - * @param SsfScene Input SsfScene - * @param MaterailId Input SsfScene - * @result a ssf::pssfMaterial object nullptr otherwise - */ - static ssf::pssfMaterial FindMaterialById(ssf::pssfScene SsfScene, ssf::ssfString MaterailId) - { - auto ProxyMaterial = std::find_if(SsfScene->MaterialTable->MaterialList.begin(), SsfScene->MaterialTable->MaterialList.end(), - [MaterailId](const ssf::pssfMaterial mat) - { - if (FSimplygonSSFHelper::CompareSSFStr(mat->Id.Get().Value, MaterailId)) - { - return true; - } - return false; - }); - - if (ProxyMaterial != std::end(SsfScene->MaterialTable->MaterialList)) - { - if (!ProxyMaterial->IsNull()) - { - return *ProxyMaterial; - } - } - - return nullptr; - } - - /** - * Get Relavant Set for Baked Materials from the SsfTexcoordsList. - * @param TextureCoordsList List of TextureCoorniates - * @result a ssf::ssfNamedList object containing uv data nullptr otherwise - */ - static ssf::ssfNamedList GetBakedMaterialUVs(std::list < ssf::ssfNamedList > TextureCoordsList) - { - return FindByTextureSetName(TextureCoordsList, ssf::ssfString("MaterialLOD")); - } - -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs b/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs deleted file mode 100644 index e1366d325f6b..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.IO; - -public class SimplygonSwarm : ModuleRules -{ - public SimplygonSwarm(ReadOnlyTargetRules Target) : base(Target) - { - PublicIncludePaths.Add("Developer/SimplygonSwarm/Public"); - PrivateIncludePaths.Add("Developer/SimplygonSwarm/Private"); - - PublicDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "InputCore", - "Json", - "RHI", - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "MeshDescription", - "MeshDescriptionOperations", - "MeshBoneReduction", - "ImageWrapper", - "HTTP", - "Json", - "UnrealEd", - "MaterialUtilities", - "MeshMergeUtilities" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "MeshUtilities", - "MaterialUtilities", - "SimplygonMeshReduction", - "MeshReductionInterface" - } - ); - - PublicIncludePathModuleNames.AddRange( - new string[] { - "MeshReductionInterface" - } - ); - - - AddEngineThirdPartyPrivateStaticDependencies(Target, "Simplygon"); - AddEngineThirdPartyPrivateStaticDependencies(Target, "SSF"); - AddEngineThirdPartyPrivateStaticDependencies(Target, "SPL"); - AddEngineThirdPartyPrivateDynamicDependencies(Target, "PropertyEditor"); - - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/Inc/SimplygonSDK.h"; - if (Target.Platform == UnrealTargetPlatform.Win64 && File.Exists(SimplygonPath)) - { - PrecompileForTargets = PrecompileTargetsType.Editor; - } - else - { - PrecompileForTargets = PrecompileTargetsType.None; - } - } -} diff --git a/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp b/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp index a177be710801..756ecad5fcf6 100644 --- a/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp +++ b/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp @@ -42,9 +42,28 @@ FAdvancedPreviewScene::FAdvancedPreviewScene(ConstructionValues CVS, float InFlo const FTransform Transform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(1)); - // Set up sky light using the set cube map texture, reusing the sky light from PreviewScene class - SetSkyCubemap(Profile.EnvironmentCubeMap.Get()); - SetSkyBrightness(Profile.SkyLightIntensity); + static const auto CVarSupportStationarySkylight = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportStationarySkylight")); + bUseSkylight = CVarSupportStationarySkylight->GetValueOnAnyThread() != 0; + + if (bUseSkylight) + { + // Set up sky light using the set cube map texture, reusing the sky light from PreviewScene class + SetSkyCubemap(Profile.EnvironmentCubeMap.Get()); + SetSkyBrightness(Profile.SkyLightIntensity); + } + else + { + // Hide the inherited skylight + SkyLight->SetVisibility(false); + + // Setup sphere reflection + SphereReflectionComponent = NewObject(); + SphereReflectionComponent->Cubemap = Profile.EnvironmentCubeMap.Get(); + SphereReflectionComponent->ReflectionSourceType = EReflectionSourceType::SpecifiedCubemap; + SphereReflectionComponent->Brightness = Profile.SkyLightIntensity; + AddComponent(SphereReflectionComponent, Transform); + SphereReflectionComponent->UpdateReflectionCaptureContents(PreviewWorld); + } // Large scale to prevent sphere from clipping const FTransform SphereTransform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(2000)); @@ -89,7 +108,6 @@ FAdvancedPreviewScene::FAdvancedPreviewScene(ConstructionValues CVS, float InFlo bRotateLighting = Profile.bRotateLightingRig; CurrentRotationSpeed = Profile.RotationSpeed; bSkyChanged = false; - bUseSkylight = true; BindCommands(); @@ -201,7 +219,7 @@ void FAdvancedPreviewScene::UpdateScene(FPreviewSceneProfile& Profile, bool bUpd SkyComponent->SetVisibility(Profile.bShowEnvironment, true); if (bUseSkylight) { - SkyLight->SetVisibility(Profile.bShowEnvironment, true); + SkyLight->SetVisibility(Profile.bUseSkyLighting, true); } else { @@ -367,7 +385,7 @@ void FAdvancedPreviewScene::SetFloorVisibility(const bool bVisible, const bool b } else { - // Otherwise set visiblity directly on the component + // Otherwise set visibility directly on the component FloorMeshComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowFloor : bVisible); } } @@ -386,14 +404,10 @@ void FAdvancedPreviewScene::SetEnvironmentVisibility(const bool bVisible, const else { // Otherwise set visibility directly on the component - SkyComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); - if (bUseSkylight) + SkyComponent->SetVisibility(bVisible); + if (!bUseSkylight) { - SkyLight->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); - } - else - { - SphereReflectionComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); + SphereReflectionComponent->SetVisibility(bVisible); } } } @@ -479,7 +493,7 @@ void FAdvancedPreviewScene::OnAssetViewerSettingsRefresh(const FName& InProperty const bool bNameNone = InPropertyName == NAME_None; const bool bUpdateEnvironment = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, EnvironmentCubeMap)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, LightingRigRotation) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); - const bool bUpdateSkyLight = bUpdateEnvironment || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, SkyLightIntensity) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); + const bool bUpdateSkyLight = bUpdateEnvironment || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, SkyLightIntensity) || InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bUseSkyLighting) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); const bool bUpdateDirectionalLight = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightIntensity)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightColor)); const bool bUpdatePostProcessing = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, PostProcessingSettings)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bPostProcessingEnabled)); diff --git a/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h b/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h index 70ca35bea13a..4c56b314de92 100644 --- a/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h +++ b/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h @@ -23,6 +23,7 @@ struct FPreviewSceneProfile FPreviewSceneProfile() { bSharedProfile = false; + bUseSkyLighting = true; bShowFloor = true; bShowEnvironment = true; bRotateLightingRig = false; @@ -48,6 +49,10 @@ struct FPreviewSceneProfile UPROPERTY(EditAnywhere, config, Category = Profile) bool bSharedProfile; + /** Whether or not image based lighting is enabled for the environment cube map */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Lighting) + bool bUseSkyLighting; + /** Manually set the directional light intensity (0.0 - 20.0) */ UPROPERTY(EditAnywhere, config, Category = Lighting, meta = (UIMin = "0.0", UIMax = "20.0")) float DirectionalLightIntensity; diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp index a353aaf14154..207c1495f6af 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp @@ -39,7 +39,9 @@ void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimati OnApply(CurrentAnimSequence); // Apply transaction + ModifierTransaction.BeginOperation(); ModifierTransaction.Apply(); + ModifierTransaction.EndOperation(); GLog->RemoveOutputDevice(&OutputLog); @@ -59,7 +61,9 @@ void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimati // Revert changes if necessary, otherwise post edit and refresh animation data if (bShouldRevert) { + AnimationDataTransaction.BeginOperation(); AnimationDataTransaction.Apply(); + AnimationDataTransaction.EndOperation(); CurrentAnimSequence->RefreshCacheData(); CurrentAnimSequence->RefreshCurveData(); } @@ -112,7 +116,9 @@ void UAnimationModifier::RevertFromAnimationSequence(class UAnimSequence* InAnim OnRevert(CurrentAnimSequence); // Apply transaction + Transaction.BeginOperation(); Transaction.Apply(); + Transaction.EndOperation(); UpdateCompressedAnimationData(); diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h new file mode 100644 index 000000000000..b0ac81bf7f18 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ClassViewerModule.h" +#include "Widgets/SWidget.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/SBoxPanel.h" +#include "Modules/ModuleManager.h" +#include "Templates/SharedPointer.h" +#include "ClassViewerFilter.h" +#include "AnimationModifier.h" + +class FAnimationModifierHelpers +{ +public: + /** ClassViewerFilter for Animation Modifier classes */ + class FModifierClassFilter : public IClassViewerFilter + { + public: + bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override + { + return InClass->IsChildOf(UAnimationModifier::StaticClass()); + } + + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override + { + return InClass->IsChildOf(UAnimationModifier::StaticClass()); + } + }; + + static TSharedRef GetModifierPicker(const FOnClassPicked& OnClassPicked) + { + FClassViewerInitializationOptions Options; + Options.bShowUnloadedBlueprints = true; + Options.bShowNoneOption = false; + TSharedPtr ClassFilter = MakeShareable(new FModifierClassFilter); + Options.ClassFilter = ClassFilter; + + return SNew(SBox) + .WidthOverride(280) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .MaxHeight(500) + [ + FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnClassPicked) + ] + ]; + } + + /** Creates a new Modifier instance to store with the current asset */ + static UAnimationModifier* CreateModifierInstance(UObject* Outer, UClass* InClass, UObject* Template = nullptr) + { + checkf(Outer, TEXT("Invalid outer value for modifier instantiation")); + UAnimationModifier* ProcessorInstance = NewObject(Outer, InClass, NAME_None, RF_NoFlags, Template); + checkf(ProcessorInstance, TEXT("Unable to instantiate modifier class")); + ProcessorInstance->SetFlags(RF_Transactional); + return ProcessorInstance; + } + +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp index 3ef91833204a..3449ed5c2662 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp @@ -12,6 +12,9 @@ #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" +#include "SAnimationModifierContentBrowserWindow.h" +#include "Framework/Application/SlateApplication.h" +#include "Interfaces/IMainFrameModule.h" #define LOCTEXT_NAMESPACE "AnimationModifiersModule" @@ -65,4 +68,31 @@ void FAnimationModifiersModule::ShutdownModule() RegisteredApplicationModes.Empty(); } +void FAnimationModifiersModule::ShowAddAnimationModifierWindow(const TArray& InSequences) +{ + TSharedPtr WindowContent; + + TSharedRef Window = SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Add Animation Modifier(s)")) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(500, 500)); + + Window->SetContent + ( + SAssignNew(WindowContent, SAnimationModifierContentBrowserWindow) + .WidgetWindow(Window) + .AnimSequences(InSequences) + ); + + TSharedPtr ParentWindow; + + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); +} + #undef LOCTEXT_NAMESPACE // "AnimationModifiersModule" diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h index f51a38f931fa..9b9e8601c0d0 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h @@ -18,6 +18,10 @@ public: /** Called before the module is unloaded, right before the module object is destroyed */ virtual void ShutdownModule() override; + /** Begin IAnimationModifiersModule overrides */ + virtual void ShowAddAnimationModifierWindow(const TArray& InSequences) override; + /** End IAnimationModifiersModule overrides */ + protected: /** Callback for extending an application mode */ TSharedRef ExtendApplicationMode(const FName ModeName, TSharedRef InMode); diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp new file mode 100644 index 000000000000..075b6c0520c0 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp @@ -0,0 +1,267 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SAnimationModifierContentBrowserWindow.h" +#include "AnimationModifierHelpers.h" +#include "Widgets/SOverlay.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Text/STextBlock.h" + +#include "EditorStyleSet.h" +#include "SModifierListview.h" +#include "PropertyEditorModule.h" + +#include "Animation/AnimSequence.h" +#include "AnimationModifiersAssetUserData.h" +#include "Dialogs/Dialogs.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "AnimationModifierContentBrowserWindow" + +void SAnimationModifierContentBrowserWindow::Construct(const FArguments& InArgs) +{ + CreateInstanceDetailsView(); + + WidgetWindow = InArgs._WidgetWindow; + AnimSequences = InArgs._AnimSequences; + + FOnGetContent GetContent = FOnGetContent::CreateLambda( + [this]() + { + return FAnimationModifierHelpers::GetModifierPicker(FOnClassPicked::CreateRaw(this, &SAnimationModifierContentBrowserWindow::OnModifierPicked)); + }); + + this->ChildSlot + [ + SNew(SOverlay) + +SOverlay::Slot() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .Padding(2.0f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.0f, 3.0f) + .AutoWidth() + [ + SAssignNew(AddModifierCombobox, SComboButton) + .OnGetMenuContent(GetContent) + .ButtonContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("AnimationModifierWindow_AddModifier", "Add Modifier")) + ] + ] + ] + ] + + + SVerticalBox::Slot() + [ + SNew(SBorder) + .Padding(2.0f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SSplitter) + .Orientation(EOrientation::Orient_Vertical) + + + SSplitter::Slot() + .Value(.5f) + [ + SNew(SBox) + .Padding(2.0f) + [ + SAssignNew(ModifierListView, SModifierListView) + .Items(&ModifierItems) + .InstanceDetailsView(ModifierInstanceDetailsView) + .OnRemoveModifier(FOnModifierArray::CreateSP(this, &SAnimationModifierContentBrowserWindow::RemoveModifiersCallback)) + ] + ] + + + SSplitter::Slot() + .Value(.5f) + [ + SNew(SBox) + .Padding(2.0f) + [ + ModifierInstanceDetailsView->AsShared() + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(2) + [ + SNew(SUniformGridPanel) + .SlotPadding(2) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .Text(LOCTEXT("AnimationModifierWindow_Import", "Apply")) + .ToolTipText(LOCTEXT("AnimationModifierWindow_Import_ToolTip", "Apply adding modifiers(s).")) + .IsEnabled(this, &SAnimationModifierContentBrowserWindow::CanApply) + .OnClicked(this, &SAnimationModifierContentBrowserWindow::OnApply) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .Text(LOCTEXT("AnimationModifierWindow_Cancel", "Cancel")) + .ToolTipText(LOCTEXT("AnimationModifierWindow_Cancel_ToolTip", "Cancels adding modifiers(s).")) + .OnClicked(this, &SAnimationModifierContentBrowserWindow::OnCancel) + ] + ] + ] + ]; +} + +FReply SAnimationModifierContentBrowserWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (InKeyEvent.GetKey() == EKeys::Escape) + { + return OnCancel(); + } + + return FReply::Unhandled(); +} + +void SAnimationModifierContentBrowserWindow::RemoveModifiersCallback(const TArray>& ModifiersToRemove) +{ + Modifiers.RemoveAll([&ModifiersToRemove](UAnimationModifier* Modifier) { return ModifiersToRemove.Contains(Modifier); }); + ModifierItems.RemoveAll([&ModifiersToRemove](TSharedPtr ModifierItem) { return ModifiersToRemove.Contains(ModifierItem->Instance); }); + ModifierListView->Refresh(); +} + +void SAnimationModifierContentBrowserWindow::OnModifierPicked(UClass* PickedClass) +{ + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(GetTransientPackage(), PickedClass); + + Modifiers.Add(Processor); + + FModifierListviewItem* Item = new FModifierListviewItem(); + Item->Instance = Processor; + Item->Class = Processor->GetClass(); + Item->Index = Modifiers.Num() - 1; + Item->OuterClass = nullptr; + ModifierItems.Add(ModifierListviewItem(Item)); + + // Close the combo box + AddModifierCombobox->SetIsOpen(false); + + ModifierListView->Refresh(); +} + +void SAnimationModifierContentBrowserWindow::CreateInstanceDetailsView() +{ + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs( + /*bUpdateFromSelection=*/ false, + /*bLockable=*/ false, + /*bAllowSearch=*/ false, + FDetailsViewArgs::HideNameArea, + /*bHideSelectionTip=*/ true, + /*InNotifyHook=*/ nullptr, + /*InSearchInitialKeyFocus=*/ false, + /*InViewIdentifier=*/ NAME_None); + DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; + DetailsViewArgs.bShowOptions = false; + + ModifierInstanceDetailsView = EditModule.CreateDetailView(DetailsViewArgs); + ModifierInstanceDetailsView->SetDisableCustomDetailLayouts(true); +} + +FReply SAnimationModifierContentBrowserWindow::OnApply() +{ + const FScopedTransaction Transaction(LOCTEXT("UndoAction_ApplyModifiers", "Applying Animation Modifier(s) to Animation Sequence(s)")); + + TArray AssetUserData; + + bool bCloseWindow = true; + + // Retrieve or create asset user data + for (UAnimSequence* AnimationSequence : AnimSequences) + { + UAnimationModifiersAssetUserData* UserData = AnimationSequence->GetAssetUserData(); + if (!UserData) + { + UserData = NewObject(AnimationSequence, UAnimationModifiersAssetUserData::StaticClass()); + checkf(UserData, TEXT("Unable to instantiate AssetUserData class")); + UserData->SetFlags(RF_Transactional); + AnimationSequence->AddAssetUserData(UserData); + } + + AssetUserData.Add(UserData); + } + + // For each added modifier create add a new instance to each of the user data entries, using the one(s) set up in the window as template(s) + for (UAnimationModifier* Modifier : Modifiers) + { + for (UAnimationModifiersAssetUserData* UserData : AssetUserData) + { + const bool bAlreadyContainsModifier = UserData->GetAnimationModifierInstances().ContainsByPredicate([Modifier](UAnimationModifier* TestModifier) { return Modifier->GetClass() == TestModifier->GetClass(); }); + static const FText MessageFormat = LOCTEXT("AnimationModifierWindow_AlreadyContainsModifierDialogText", "{0} already contains Animation Modifier {1}, are you sure you want to add another instance?"); + + const bool bUserInputResult = bAlreadyContainsModifier ? OpenMsgDlgInt(EAppMsgType::YesNo, FText::FormatOrdered(MessageFormat, FText::FromString(UserData->GetOuter()->GetName()), FText::FromString(Modifier->GetClass()->GetName())), LOCTEXT("AnimationModifierWindow_AlreadyContainsModifierTitle", "Already contains Animation Modifier!")) == EAppReturnType::Yes : true; + bCloseWindow = bUserInputResult; + if (!bAlreadyContainsModifier || bUserInputResult) + { + UObject* Outer = UserData; + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, Modifier->GetClass(), Modifier); + UserData->Modify(); + UserData->AddAnimationModifier(Processor); + } + } + } + + /** For each user data retrieve all modifiers and apply them */ + for (int32 Index = 0; Index < AssetUserData.Num(); ++Index) + { + UAnimationModifiersAssetUserData* UserData = AssetUserData[Index]; + if (UserData) + { + UAnimSequence* AnimSequence = AnimSequences[Index]; + AnimSequence->Modify(); + + const TArray& ModifierInstances = UserData->GetAnimationModifierInstances(); + for (UAnimationModifier* Modifier : ModifierInstances) + { + Modifier->ApplyToAnimationSequence(AnimSequence); + } + } + } + + if (bCloseWindow && WidgetWindow.IsValid()) + { + WidgetWindow.Pin()->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply SAnimationModifierContentBrowserWindow::OnCancel() +{ + if (WidgetWindow.IsValid()) + { + WidgetWindow.Pin()->RequestDestroyWindow(); + } + return FReply::Handled(); +} + +bool SAnimationModifierContentBrowserWindow::CanApply() const +{ + return Modifiers.Num() > 0; +} + +#undef LOCTEXT_NAMESPACE // "AnimationModifierContentBrowserWindow" \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h new file mode 100644 index 000000000000..ed5a92d8cc24 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h @@ -0,0 +1,67 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/Input/SButton.h" + +#include "Templates/SharedPointer.h" +#include "Containers/Array.h" + +class SButton; +class SComboButton; +class SModifierListView; +class SMenuAnchor; +class UAnimationModifier; +class UAnimSequence; +class IDetailsView; + +struct FModifierListviewItem; + +/** UI slate widget allowing the user to add Animation Modifier(s) to a selection of Animation Sequences */ +class SAnimationModifierContentBrowserWindow : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAnimationModifierContentBrowserWindow) + : _WidgetWindow() + {} + SLATE_ARGUMENT(TSharedPtr, WidgetWindow) + SLATE_ARGUMENT(TArray, AnimSequences) + SLATE_END_ARGS() + +public: + SAnimationModifierContentBrowserWindow() {} + void Construct(const FArguments& InArgs); + virtual bool SupportsKeyboardFocus() const override { return true; } + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +protected: + /** Callback for when the user picks a specific animation modifier class */ + void OnModifierPicked(UClass* PickedClass); + /** Callback for when the user wants to remove modifier(s) from the listview */ + void RemoveModifiersCallback(const TArray>& ModifiersToRemove); + + /** Creates the details view widget used to show Animation Modifier object details */ + void CreateInstanceDetailsView(); + + /** Button callback, this applies all currently set up Animation Modifiers to the previously selected Animation Sequences */ + FReply OnApply(); + /** Button callback, closes the dialog/window */ + FReply OnCancel(); + /** Check to see whether or not the user can apply the modifiers in the current state */ + bool CanApply() const; +private: + /** Window owning this window */ + TWeakPtr WidgetWindow; + + TSharedPtr ModifierInstanceDetailsView; + TSharedPtr AddModifierCombobox; + TSharedPtr ModifierListView; + + /** Data structures used by the Modifier List View widget */ + TArray> ModifierItems; + /** Current set of Animation Modifiers that would be added during Apply */ + TArray Modifiers; + /** Previously user-selected Animation Sequences */ + TArray AnimSequences; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp index 072936062ce4..cab5f4f02101 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp @@ -25,24 +25,10 @@ #include "ScopedTransaction.h" #include "Widgets/Input/SMenuAnchor.h" #include "Engine/BlueprintGeneratedClass.h" +#include "AnimationModifierHelpers.h" #define LOCTEXT_NAMESPACE "SAnimationModifiersTab" -/** ClassViewerFilter for Animation Modifier classes */ -class FModifierClassFilter : public IClassViewerFilter -{ -public: - bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override - { - return InClass->IsChildOf(UAnimationModifier::StaticClass()); - } - - virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override - { - return InClass->IsChildOf(UAnimationModifier::StaticClass()); - } -}; - SAnimationModifiersTab::SAnimationModifiersTab() : Skeleton(nullptr), AnimationSequence(nullptr), AssetUserData(nullptr), bDirty(false) { @@ -58,16 +44,6 @@ SAnimationModifiersTab::~SAnimationModifiersTab() FAssetEditorManager::Get().OnAssetOpenedInEditor().RemoveAll(this); } -UAnimationModifier* SAnimationModifiersTab::CreateModifierInstance(UObject* Outer, UClass* InClass) -{ - checkf(Outer, TEXT("Invalid outer value for modifier instantiation")); - UAnimationModifier* ProcessorInstance = NewObject(Outer, InClass); - checkf(ProcessorInstance, TEXT("Unable to instantiate modifier class")); - ProcessorInstance->SetFlags(RF_Transactional); - return ProcessorInstance; -} - - void SAnimationModifiersTab::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { if (bDirty) @@ -97,6 +73,12 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) RetrieveModifierData(); CreateInstanceDetailsView(); + + FOnGetContent GetContent = FOnGetContent::CreateLambda( + [this]() + { + return FAnimationModifierHelpers::GetModifierPicker(FOnClassPicked::CreateRaw(this, &SAnimationModifiersTab::OnModifierPicked)); + }); this->ChildSlot [ @@ -117,7 +99,7 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) .AutoWidth() [ SAssignNew(AddModifierCombobox, SComboButton) - .OnGetMenuContent(this, &SAnimationModifiersTab::GetModifierPicker) + .OnGetMenuContent(GetContent) .ButtonContent() [ SNew(STextBlock) @@ -193,36 +175,12 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) FAssetEditorManager::Get().OnAssetOpenedInEditor().AddSP(this, &SAnimationModifiersTab::OnAssetOpened); } - -TSharedRef SAnimationModifiersTab::GetModifierPicker() -{ - FClassViewerInitializationOptions Options; - Options.bShowUnloadedBlueprints = true; - Options.bShowNoneOption = false; - TSharedPtr ClassFilter = MakeShareable(new FModifierClassFilter); - Options.ClassFilter = ClassFilter; - - FOnClassPicked OnPicked(FOnClassPicked::CreateRaw(this, &SAnimationModifiersTab::OnModifierPicked)); - - return SNew(SBox) - .WidthOverride(280) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .MaxHeight(500) - [ - FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnPicked) - ] - ]; -} - void SAnimationModifiersTab::OnModifierPicked(UClass* PickedClass) { FScopedTransaction Transaction(LOCTEXT("AddModifierTransaction", "Adding Animation Modifier")); UObject* Outer = AssetUserData; - UAnimationModifier* Processor = CreateModifierInstance(Outer, PickedClass); + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, PickedClass); AssetUserData->Modify(); AssetUserData->AddAnimationModifier(Processor); @@ -403,7 +361,7 @@ void SAnimationModifiersTab::OnOpenModifier(const TWeakObjectPtr GetModifierPicker(); /** Callback for when user has picked a modifier to add */ void OnModifierPicked(UClass* PickedClass); @@ -70,8 +68,7 @@ protected: /** Retrieves the currently opened animation asset type and modifier user data */ void RetrieveAnimationAsset(); - /** Creates a new Modifier instance to store with the current asset */ - UAnimationModifier* CreateModifierInstance(UObject* Outer, UClass* InClass); + /** Retrieves all animation sequences which are dependent on the current opened skeleton */ void FindAnimSequencesForSkeleton(TArray &ReferencedAnimSequences); protected: diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp index f9c80abc5eb0..4f741c80181a 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp @@ -138,22 +138,31 @@ TSharedPtr SModifierListView::OnContextMenuOpening() MenuBuilder.BeginSection(NAME_None); { - if (Listview->GetNumItemsSelected() == 1) + if (Listview->GetNumItemsSelected() == 1 && OnOpenModifierDelegate.IsBound()) { MenuBuilder.AddMenuEntry(LOCTEXT("OpenModifierLabel", "Open Blueprint"), LOCTEXT("OpenModifierToolTip", "Open selected Modifier Blueprint"), FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Blueprint"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnOpenModifier))); } const FText ApplyLabel = FText::FormatOrdered(LOCTEXT("ApplyModifierLabel", "Apply {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText ApplyTooltip = FText::FormatOrdered(LOCTEXT("ApplyModifierToolTip", "Apply selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(ApplyLabel, ApplyTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Redo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnApplyModifier))); - + if (OnApplyModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(ApplyLabel, ApplyTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Redo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnApplyModifier))); + } + const FText RevertLabel = FText::FormatOrdered(LOCTEXT("ApplyRevertLabel", "Revert {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText RevertTooltip = FText::FormatOrdered(LOCTEXT("ApplyRevertToolTip", "Revert selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(RevertLabel, RevertTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Undo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRevertModifier))); + if (OnRevertModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(RevertLabel, RevertTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Undo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRevertModifier))); + } const FText RemoveLabel = FText::FormatOrdered(LOCTEXT("RemoveModifierLabel", "Remove {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText RemoveTooltip = FText::FormatOrdered(LOCTEXT("RemoveModifierToolTip", "Remove selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(RemoveLabel, RemoveTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Delete"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRemoveModifier))); + if (OnRemoveModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(RemoveLabel, RemoveTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Delete"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRemoveModifier))); + } } MenuBuilder.EndSection(); @@ -161,9 +170,15 @@ TSharedPtr SModifierListView::OnContextMenuOpening() { MenuBuilder.BeginSection(NAME_None); { - MenuBuilder.AddMenuEntry(LOCTEXT("MoveUpModifierLabel", "Move Up"), LOCTEXT("MoveUpModifierToolTip", "Move selected Modifier Up in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveUpModifier), FCanExecuteAction::CreateSP( this, &SModifierListView::CanMoveSelectedItemUp))); + if (OnMoveUpModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(LOCTEXT("MoveUpModifierLabel", "Move Up"), LOCTEXT("MoveUpModifierToolTip", "Move selected Modifier Up in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveUpModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemUp))); + } - MenuBuilder.AddMenuEntry(LOCTEXT("MoveDownModifierLabel", "Move Down"), LOCTEXT("MoveDownModifierToolTip", "Move selected Modifier Down in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveDownModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemDown))); + if (OnMoveDownModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(LOCTEXT("MoveDownModifierLabel", "Move Down"), LOCTEXT("MoveDownModifierToolTip", "Move selected Modifier Down in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveDownModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemDown))); + } } MenuBuilder.EndSection(); } diff --git a/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h b/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h index aac763449b44..04d36ba16b6a 100644 --- a/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h +++ b/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h @@ -3,7 +3,13 @@ #pragma once #include "Modules/ModuleInterface.h" +#include "Containers/Array.h" + +class UAnimSequence; class IAnimationModifiersModule : public IModuleInterface { +public: + /** Shows a new modal dialog allowing the user to setup Animation Modifiers to be added for all AnimSequences part of InSequences */ + virtual void ShowAddAnimationModifierWindow(const TArray& InSequences) = 0; }; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp index 2f61536650d2..46acc38abfb4 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp @@ -15,6 +15,7 @@ #include "K2Node.h" #include "Widgets/Input/SButton.h" #include "K2Node_BreakStruct.h" +#include "K2Node_GetClassDefaults.h" #include "ScopedTransaction.h" #define LOCTEXT_NAMESPACE "SkeletalControlNodeDetails" @@ -36,16 +37,16 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D const TArray< TWeakObjectPtr >& SelectedObjects = DetailBuilder.GetSelectedObjects(); for (const TWeakObjectPtr& CurrentObject : SelectedObjects) { - if (UK2Node_BreakStruct* CurrBreakStruct = Cast(CurrentObject.Get())) + if (Cast(CurrentObject.Get()) || Cast(CurrentObject.Get())) { - if (BreakStructNode.IsValid()) + if (HideUnconnectedPinsNode.IsValid()) { // Have more than one break struct node, don't cache so we don't // create the hide unconnected pins UI - BreakStructNode = nullptr; + HideUnconnectedPinsNode = nullptr; break; } - BreakStructNode = CurrBreakStruct; + HideUnconnectedPinsNode = Cast(CurrentObject.Get()); } } @@ -90,7 +91,7 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D } // Add the action buttons - if(BreakStructNode.IsValid()) + if(HideUnconnectedPinsNode.IsValid()) { FDetailWidgetRow& GroupActionsRow = DetailCategory->AddCustomRow(LOCTEXT("GroupActionsSearchText", "Split Sort")) .ValueContent() @@ -99,7 +100,7 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D [ SNew(SButton) .OnClicked(this, &FSkeletalControlNodeDetails::HideAllUnconnectedPins) - .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins of the structure get hidden (removed from the graph node)")) + .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins get hidden (removed from the graph node)")) [ SNew(STextBlock) .Text(LOCTEXT("HideAllUnconnectedPins", "Hide Unconnected Pins")) @@ -246,7 +247,7 @@ void FSkeletalControlNodeDetails::OnGenerateElementForPropertyPin(TSharedRefGetNumElements(NumChildren); @@ -261,7 +262,7 @@ FReply FSkeletalControlNodeDetails::HideAllUnconnectedPins() FName ActualPropertyName; if (PropertyNameHandle.IsValid() && PropertyNameHandle->GetValue(ActualPropertyName) == FPropertyAccess::Success) { - const UEdGraphPin* Pin = BreakStructNode->FindPin(ActualPropertyName.ToString(), EGPD_Output); + const UEdGraphPin* Pin = HideUnconnectedPinsNode->FindPin(ActualPropertyName.ToString(), EGPD_Output); if (Pin && Pin->LinkedTo.Num() <= 0) { OnShowPinChanged(ECheckBoxState::Unchecked, ElementHandle); diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h index cbc175c40148..fd7316b3aaae 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h @@ -37,6 +37,6 @@ protected: private: IDetailCategoryBuilder* DetailCategory; - TWeakObjectPtr BreakStructNode; + TWeakObjectPtr HideUnconnectedPinsNode; TSharedPtr ArrayProperty; }; diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index 24855ec5e2d6..4a1968899d72 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -5369,8 +5369,11 @@ void FSlateEditorStyle::FStyle::SetupPersonaStyle() // Anim Slot Manager Set("AnimSlotManager.SaveSkeleton", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_SaveSkeleton_40x", Icon40x40)); + Set("AnimSlotManager.SaveSkeleton.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_SaveSkeleton_40x", Icon20x20)); Set("AnimSlotManager.AddGroup", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddGroup_40x", Icon40x40)); + Set("AnimSlotManager.AddGroup.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddGroup_40x", Icon20x20)); Set("AnimSlotManager.AddSlot", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddSlot_40x", Icon40x40)); + Set("AnimSlotManager.AddSlot.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddSlot_40x", Icon20x20)); Set("AnimSlotManager.Warning", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_Warning_14x", Icon16x16)); // Anim Notify Editor @@ -6170,6 +6173,7 @@ void FSlateEditorStyle::FStyle::SetupClassIconsAndThumbnails() TEXT("AnimComposite"), TEXT("AnimMontage"), TEXT("AnimSequence"), + TEXT("AnimationSharingSetup"), TEXT("ApplicationLifecycleComponent"), TEXT("AtmosphericFog"), TEXT("BehaviorTree"), diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp index 113944f0def1..7941e6729c1a 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp @@ -5303,6 +5303,18 @@ void FBlueprintEditor::DeleteSelectedNodes() { if (Node->CanUserDeleteNode()) { + // if it's state based anim node, make sure we close the sub graph first + // otherwise, we leave the orphan graph around + UAnimStateNodeBase* StateNode = Cast(Node); + if (StateNode) + { + UEdGraph* NodeGraph = StateNode->GetBoundGraph(); + if (NodeGraph) + { + CloseDocumentTab(NodeGraph); + } + } + UK2Node* K2Node = Cast(Node); if (K2Node != NULL && K2Node->NodeCausesStructuralBlueprintChange()) { diff --git a/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp b/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp index 1c2762d88aa0..89c7f723fb78 100644 --- a/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp +++ b/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp @@ -122,28 +122,11 @@ void FComponentSelectionControl::UpdateSelectedStaticMeshComponents() TSharedRef FComponentSelectionControl::MakeComponentListItemWidget(TSharedPtr ComponentData, const TSharedRef& OwnerTable) { check(ComponentData->PrimComponent != nullptr); - - // Retrieve information about the mesh component - const FString OwningActorName = ComponentData->PrimComponent->GetOwner()->GetName(); - + // If box should be enabled bool bEnabled = true; bool bIsMesh = false; - FString ComponentInfo; - if (UStaticMeshComponent* StaticMeshComponent = Cast(ComponentData->PrimComponent.Get())) - { - ComponentInfo = (StaticMeshComponent->GetStaticMesh() != nullptr) ? StaticMeshComponent->GetStaticMesh()->GetName() : TEXT("No Static Mesh Available"); - bEnabled = (StaticMeshComponent->GetStaticMesh() != nullptr); - bIsMesh = true; - } - else if (UShapeComponent* ShapeComponent = Cast(ComponentData->PrimComponent.Get())) - { - ComponentInfo = ShapeComponent->GetClass()->GetName(); - } - - const FString ComponentName = ComponentData->PrimComponent->GetName(); - // See if we stored a checkbox state for this mesh component, and set accordingly ECheckBoxState State = bEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; ECheckBoxState* StoredState = StoredCheckBoxStates.Find(ComponentData->PrimComponent.Get()); @@ -152,7 +135,6 @@ TSharedRef FComponentSelectionControl::MakeComponentListItemWidget(TS State = *StoredState; } - return SNew(STableRow>, OwnerTable) [ SNew(SBox) diff --git a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp index b98c6b1a0120..83975b37f2d2 100644 --- a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp @@ -32,8 +32,6 @@ void FAnimViewportShowCommands::RegisterCommands() UI_COMMAND( ShowBoneWeight, "Selected Bone Weight", "Display color overlay of the weight from selected bone in the viewport", EUserInterfaceActionType::RadioButton, FInputChord() ); UI_COMMAND( ShowMorphTargetVerts, "Selected Morph Target Vertices", "Display color overlay with the change of selected morph target in the viewport", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(ShowVertexColors, "Vertex Colors", "Display mesh vertex colors", EUserInterfaceActionType::ToggleButton, FInputChord()); - UI_COMMAND( ShowRawAnimation, "Uncompressed Animation", "Display skeleton with uncompressed animation data", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ShowNonRetargetedAnimation, "Non-Retargeted Animation", "Display Skeleton With non-retargeted animation data", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ShowAdditiveBaseBones, "Additive Base", "Display skeleton in additive base pose", EUserInterfaceActionType::ToggleButton, FInputChord() ); diff --git a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h index 6b2d021c91aa..36a9976f0f6a 100644 --- a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h +++ b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h @@ -98,9 +98,6 @@ public: TSharedPtr< FUICommandInfo > ShowBoneWeight; TSharedPtr< FUICommandInfo > ShowMorphTargetVerts; - /** Show mesh vertex colors */ - TSharedPtr< FUICommandInfo > ShowVertexColors; - /** Show socket hit point diamonds */ TSharedPtr< FUICommandInfo > ShowSockets; diff --git a/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp b/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp index 166fc73c9e37..9bd1c687c5a5 100644 --- a/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp @@ -1909,6 +1909,12 @@ void FAnimationViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFami void FAnimationViewportClient::HandleToggleShowFlag(FEngineShowFlags::EShowFlag EngineShowFlagIndex) { FEditorViewportClient::HandleToggleShowFlag(EngineShowFlagIndex); + + if (UDebugSkelMeshComponent* Component = GetAnimPreviewScene()->GetPreviewMeshComponent()) + { + Component->bDisplayVertexColors = EngineShowFlags.VertexColors; + Component->MarkRenderStateDirty(); + } ConfigOption->SetShowGrid(EngineShowFlags.Grid); } diff --git a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp index bdf57b7bf423..4aa884491f97 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp @@ -164,7 +164,8 @@ public: UpdateNameInternal(AnimSequenceBase.Get()->RawCurveData, RequestedNameUID, RequestedName); if (UAnimSequence* Seq = AnimSequence.Get()) { - UpdateNameInternal(Seq->CompressedCurveData, RequestedNameUID, RequestedName); + FSmartName NewCurveName(RequestedName, RequestedNameUID); + Seq->UpdateCompressedCurveName(CurveUID, NewCurveName); } CurveUID = RequestedNameUID; } @@ -175,13 +176,6 @@ public: void SetCurveTypeFlag(EAnimAssetCurveFlags InFlag, bool bValue) { AnimSequenceBase.Get()->RawCurveData.GetCurveData(CurveUID)->SetCurveTypeFlag(InFlag, bValue); - if (UAnimSequence* Seq = AnimSequence.Get()) - { - if (FAnimCurveBase* CompressedCurve = Seq->CompressedCurveData.GetCurveData(CurveUID)) - { - CompressedCurve->SetCurveTypeFlag(InFlag, bValue); - } - } } /** @@ -190,13 +184,6 @@ public: void ToggleCurveTypeFlag(EAnimAssetCurveFlags InFlag) { AnimSequenceBase.Get()->RawCurveData.GetCurveData(CurveUID)->ToggleCurveTypeFlag(InFlag); - if (UAnimSequence* Seq = AnimSequence.Get()) - { - if (FAnimCurveBase* CompressedCurve = Seq->CompressedCurveData.GetCurveData(CurveUID)) - { - CompressedCurve->ToggleCurveTypeFlag(InFlag); - } - } } /** diff --git a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp index 6f0385c1eff5..fa1c4dd2a7fb 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp @@ -604,7 +604,6 @@ TSharedRef SAnimViewportToolBar::GenerateCharacterMenu() const SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().UseFixedBounds); SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowPreviewMesh ); SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowMorphTargets ); - SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowVertexColors ); } SubMenuBuilder.EndSection(); @@ -842,6 +841,7 @@ TSharedRef SAnimViewportToolBar::GenerateShowMenu() const .IncludeFlag(FEngineShowFlags::SF_SeparateTranslucency) .IncludeFlag(FEngineShowFlags::SF_TemporalAA) .IncludeFlag(FEngineShowFlags::SF_Tessellation) + .IncludeFlag(FEngineShowFlags::SF_VertexColors) ; FShowFlagMenuCommands::Get().BuildShowFlagsMenu(InMenuBuilder, ShowFlagFilter); diff --git a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp index 0ef8b4d569b6..a159c27fdc49 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp @@ -737,13 +737,7 @@ void SAnimationEditorViewportTabBody::BindCommands() FExecuteAction::CreateSP(this, &SAnimationEditorViewportTabBody::OnShowOverlayMorphTargetVert), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SAnimationEditorViewportTabBody::IsShowingOverlayMorphTargetVerts)); - - CommandList.MapAction( - ViewportShowMenuCommands.ShowVertexColors, - FExecuteAction::CreateSP(this, &SAnimationEditorViewportTabBody::OnShowVertexColorsChanged), - FCanExecuteAction(), - FIsActionChecked::CreateSP(this, &SAnimationEditorViewportTabBody::IsShowingVertexColors)); - + CommandList.EndGroup(); // Show sockets @@ -1715,38 +1709,6 @@ bool SAnimationEditorViewportTabBody::IsPreviewingRootMotion() const return false; } -bool SAnimationEditorViewportTabBody::IsShowingVertexColors() const -{ - return GetAnimationViewportClient()->EngineShowFlags.VertexColors; -} - -void SAnimationEditorViewportTabBody::OnShowVertexColorsChanged() -{ - FEngineShowFlags& ShowFlags = GetAnimationViewportClient()->EngineShowFlags; - - if(UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent()) - { - if(!ShowFlags.VertexColors) - { - ShowFlags.SetVertexColors(true); - ShowFlags.SetLighting(false); - ShowFlags.SetIndirectLightingCache(false); - PreviewComponent->bDisplayVertexColors = true; - } - else - { - ShowFlags.SetVertexColors(false); - ShowFlags.SetLighting(true); - ShowFlags.SetIndirectLightingCache(true); - PreviewComponent->bDisplayVertexColors = false; - } - - PreviewComponent->RecreateRenderState_Concurrent(); - } - - RefreshViewport(); -} - #if WITH_APEX_CLOTHING bool SAnimationEditorViewportTabBody::IsClothSimulationEnabled() const { diff --git a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h index aad79bf6d27b..b519d90c81a0 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h +++ b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h @@ -461,13 +461,6 @@ private: /** Whether or not we are previewing root motion */ bool IsPreviewingRootMotion() const; - - /** Callback when user checks the vertex colors box in the show menu */ - void OnShowVertexColorsChanged(); - - /** Whether or not vertex color display is enabled */ - bool IsShowingVertexColors() const; - private: /** Selected Turn Table speed */ EAnimationPlaybackSpeeds::Type SelectedTurnTableSpeed; diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp index bd4321eb4adb..2b5558dfa837 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp @@ -43,6 +43,7 @@ #include "ComponentReregisterContext.h" #include "EditorFramework/AssetImportData.h" #include "Factories/FbxSkeletalMeshImportData.h" +#include "AnimPreviewInstance.h" const FName SkeletalMeshEditorAppIdentifier = FName(TEXT("SkeletalMeshEditorApp")); @@ -176,6 +177,9 @@ void FSkeletalMeshEditor::BindCommands() ToolkitCommands->MapAction(FPersonaCommonCommands::Get().TogglePlay, FExecuteAction::CreateRaw(&GetPersonaToolkit()->GetPreviewScene().Get(), &IPersonaPreviewScene::TogglePlayback)); + + ToolkitCommands->MapAction(FSkeletalMeshEditorCommands::Get().UpdateRefPose, + FExecuteAction::CreateSP(this, &FSkeletalMeshEditor::HandleUpdateRefPose)); } void FSkeletalMeshEditor::ExtendToolbar() @@ -812,6 +816,25 @@ void FSkeletalMeshEditor::ExtendMenu() { MenuExtender = MakeShareable(new FExtender); + struct Local + { + static void AddAssetMenu(FMenuBuilder& MenuBuilder, FSkeletalMeshEditor* InSkeletalMeshEditor) + { + MenuBuilder.BeginSection("SkeletalMeshEditor", LOCTEXT("SkeletalMeshEditorAssetMenu_Animation", "SkeletalMesh")); + { + MenuBuilder.AddMenuEntry(FSkeletalMeshEditorCommands::Get().UpdateRefPose); + } + MenuBuilder.EndSection(); + } + }; + + MenuExtender->AddMenuExtension( + "AssetEditorActions", + EExtensionHook::After, + GetToolkitCommands(), + FMenuExtensionDelegate::CreateStatic(&Local::AddAssetMenu, this) + ); + AddMenuExtender(MenuExtender); ISkeletalMeshEditorModule& SkeletalMeshEditorModule = FModuleManager::GetModuleChecked("SkeletalMeshEditor"); @@ -943,6 +966,56 @@ void ReimportAllCustomLODs(USkeletalMesh* SkeletalMesh, UDebugSkelMeshComponent* } } +void FSkeletalMeshEditor::HandleUpdateRefPose() +{ + UDebugSkelMeshComponent* Component = GetPersonaToolkit()->GetPreviewMeshComponent(); + + if (SkeletalMesh && Component) + { + FScopedTransaction Transaction(LOCTEXT("ModifyRefPose", "Update Reference Pose")); + SkeletalMesh->Modify(); + + // need to be in the scope so that it SkeletonModifier to update the transform of RefSkeleton + { + USkeleton* Skeleton = SkeletalMesh->Skeleton; + FReferenceSkeleton& CurrentRefSkeleton = SkeletalMesh->RefSkeleton; + FReferenceSkeletonModifier RefSkeletonModifier(CurrentRefSkeleton, Skeleton); + // get current preview mesh transform + TArray CurrentPose = Component->GetComponentSpaceTransforms(); + + // we want to only apply these to raw ref pose transform + const TArray& RefPose = CurrentRefSkeleton.GetRawRefBonePose(); + // make sure ref pose count is smaller than current pose + check(RefPose.Num() <= CurrentPose.Num()); + + for (int32 Index = 0; Index < RefPose.Num(); ++Index) + { + FTransform LocalTransform; + if (Index == 0) + { + LocalTransform = CurrentPose[Index]; + } + else + { + const int32 ParentIndex = CurrentRefSkeleton.GetParentIndex(Index); + LocalTransform = CurrentPose[Index].GetRelativeTransform(CurrentPose[ParentIndex]); + } + + RefSkeletonModifier.UpdateRefPoseTransform(Index, LocalTransform); + } + } + + // calculate inverse ref matrices + SkeletalMesh->CalculateInvRefMatrices(); + + // now clear any bone transform modified + if (Component && Component->PreviewInstance) + { + Component->PreviewInstance->ResetModifiedBone(); + } + } +} + void FSkeletalMeshEditor::HandleReimportAllMesh(int32 SourceFileIndex /*= INDEX_NONE*/) { // Reimport the asset @@ -972,6 +1045,7 @@ void FSkeletalMeshEditor::HandleReimportAllMeshWithNewFile(int32 SourceFileIndex } } + void FSkeletalMeshEditor::ToggleMeshSectionSelection() { TSharedRef PreviewScene = GetPersonaToolkit()->GetPreviewScene(); diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h index 294842ee4b66..242af1efb35b 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h @@ -103,6 +103,8 @@ private: void HandleReimportAllMesh(int32 SourceFileIndex = INDEX_NONE); void HandleReimportAllMeshWithNewFile(int32 SourceFileIndex = INDEX_NONE); + void HandleUpdateRefPose(); + /** Callback for toggling UV drawing in the viewport */ void ToggleMeshSectionSelection(); diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp index eca608f481f5..c7fc2f539e61 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp @@ -13,6 +13,8 @@ void FSkeletalMeshEditorCommands::RegisterCommands() UI_COMMAND(ReimportAllMeshWithNewFile, "Reimport All Mesh With New File", "Reimport the current mesh using a new source file and all the custom LODs (No new source file for LODs).", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(MeshSectionSelection, "Section Selection", "Enables selecting Mesh Sections in the viewport (disables selecting bones using their physics shape).", EUserInterfaceActionType::ToggleButton, FInputChord()); + + UI_COMMAND(UpdateRefPose, "Update Reference Pose", "Update Reference Pose with current pose", EUserInterfaceActionType::Button, FInputChord()); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h index a5bbb9f7968c..9ab4c4d4406a 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h @@ -28,4 +28,7 @@ public: // selecting mesh section using hit proxies TSharedPtr MeshSectionSelection; + + // update ref pose + TSharedPtr UpdateRefPose; }; diff --git a/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs b/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs index 4530203af5f5..a2488538a415 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs +++ b/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs @@ -22,7 +22,6 @@ public class SkeletalMeshEditor : ModuleRules "SlateCore", "EditorStyle", "UnrealEd", - "SkeletonEditor", "Kismet", "KismetWidgets", @@ -34,7 +33,8 @@ public class SkeletalMeshEditor : ModuleRules "RHI", "ClothingSystemRuntime", "ClothingSystemEditorInterface", - "ClothingSystemRuntimeInterface" + "ClothingSystemRuntimeInterface", + "AnimGraph" } ); diff --git a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp index 988e6b80cf77..ad2e74ad2432 100644 --- a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp +++ b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp @@ -297,7 +297,9 @@ void FEditableSkeleton::RenameSmartname(const FName InContainerName, SmartName:: if (UAnimSequence* Seq = Cast(SequenceBase)) { SequencesToRecompress.Add(Seq); - Seq->CompressedCurveData.Empty(); + + Seq->CompressedCurveByteStream.Empty(); + Seq->CurveCompressionCodec = nullptr; } } } diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h new file mode 100644 index 000000000000..d9ff9ce1418c --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AnimCurveCompressionSettingsFactory.generated.h" + +UCLASS(HideCategories = Object, MinimalAPI) +class UAnimCurveCompressionSettingsFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp index bd42aaf4f189..f338ccbbbb52 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp @@ -1349,7 +1349,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, ABrush::OnRebuildDone(); const bool bShouldBuildTextureStreamingForWorld = bShouldBuildTextureStreaming && !bShouldBuildTextureStreamingForAll; - if (bShouldBuildLighting || bShouldBuildTextureStreamingForWorld || bShouldBuildHLOD || bShouldBuildReflectionCaptures) + const bool bBuildingNonHLODData = (bShouldBuildLighting || bShouldBuildTextureStreamingForWorld || bShouldBuildReflectionCaptures); + if (bBuildingNonHLODData || bShouldBuildHLOD) { bool bShouldProceedWithRebuild = true; @@ -1407,12 +1408,13 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, FString WorldPackageCheckedOutUser; if (FPackageName::DoesPackageExist(World->GetOutermost()->GetName(), NULL, &WorldPackageName)) { - if(bShouldBuildHLOD) + // If we are only building HLODs check if level can be checked out, if so add to list of files that will be saved/checked-out after rebuilding the data + if(bShouldBuildHLOD && !bBuildingNonHLODData) { if (CanCheckoutFile(WorldPackageName, WorldPackageCheckedOutUser) || !bSkipCheckedOutFiles) { - SublevelFilenames.Add(WorldPackageName); - } + SublevelFilenames.Add(WorldPackageName); + } else { bShouldProceedWithRebuild = false; @@ -1444,8 +1446,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, for (ULevelStreaming* NextStreamingLevel : World->GetStreamingLevels()) { - // If we are building HLODs, we dont check out ahead of time - if(!bShouldBuildHLOD) + // If we are not building HLODs or are but also rebuilding lighting we check out the level file, otherwise we don't to try and ensure a minimal HLOD rebuild + if (!bShouldBuildHLOD || bBuildingNonHLODData) { CheckOutLevelFile(NextStreamingLevel->GetLoadedLevel()); } @@ -1454,14 +1456,14 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, const FString StreamingLevelWorldAssetPackageName = NextStreamingLevel->GetWorldAssetPackageName(); if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename)) { - // If we are building HLODs, we dont check out ahead of time - if(bShouldBuildHLOD) + // If we are building HLODs only, we dont check out the files ahead of rebuilding the data + if(bShouldBuildHLOD && !bBuildingNonHLODData) { FString CurrentlyCheckedOutUser; if (CanCheckoutFile(StreamingLevelPackageFilename, CurrentlyCheckedOutUser) || !bSkipCheckedOutFiles) { - SublevelFilenames.Add(StreamingLevelPackageFilename); - } + SublevelFilenames.Add(StreamingLevelPackageFilename); + } else { UE_LOG(LogContentCommandlet, Warning, TEXT("[REPORT] Skipping %s as it is checked out by %s"), *StreamingLevelPackageFilename, *CurrentlyCheckedOutUser); @@ -1476,6 +1478,7 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, } else { + UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] %s is currently already checked out, cannot continue resaving"), *StreamingLevelPackageFilename); bShouldProceedWithRebuild = false; break; } @@ -1688,11 +1691,11 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, { FString StreamingLevelPackageFilename; const FString StreamingLevelWorldAssetPackageName = NextStreamingLevel->GetWorldAssetPackageName(); - if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename) && SublevelFilenames.Contains(StreamingLevelWorldAssetPackageName)) + if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename) && SublevelFilenames.Contains(StreamingLevelPackageFilename)) { UPackage* SubLevelPackage = NextStreamingLevel->GetLoadedLevel()->GetOutermost(); bool bSaveSubLevelPackage = true; - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { // If we are building HLOD, only save packages that were dirtied bSaveSubLevelPackage = SubLevelPackage->IsDirty(); @@ -1702,12 +1705,14 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, { // When building HLODs we dont check out/modify maps unless dirty bool bFileCheckedOut = true; - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { bFileCheckedOut = CheckoutFile(StreamingLevelPackageFilename, true); } - if (!bFileCheckedOut || !SavePackageHelper(SubLevelPackage, StreamingLevelPackageFilename)) + // Try to save the level package + const bool bSavePackageResult = SavePackageHelper(SubLevelPackage, StreamingLevelPackageFilename); + if (!bFileCheckedOut && !bSavePackageResult) { UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to save sub level: %s"), *StreamingLevelPackageFilename); } @@ -1726,8 +1731,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, } else { - UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to complete steps necessary to start a lightmass or texture streaming build of %s"), *World->GetName()); - } + UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to complete steps necessary to start a lightmass or texture streaming build of %s"), *World->GetName()); + } } if ((bShouldProceedWithRebuild == false)||(bSavePackage == false)) @@ -1751,9 +1756,9 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, FilesToSubmit.AddUnique(SublevelFilename); } - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { - // Don't save outer package if it isn't dirty + // Don't save outer package if it isn't dirty when doing a HLOD rebuild only bSavePackage = World->GetOutermost()->IsDirty(); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp index dc0e1161ac54..c2749ece71c0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp @@ -850,6 +850,7 @@ void UEditorEngine::Init(IEngineLoop* InEngineLoop) TEXT("KismetCompiler"), TEXT("Kismet"), TEXT("Persona"), + TEXT("AnimationBlueprintEditor"), TEXT("LevelEditor"), TEXT("MainFrame"), TEXT("PropertyEditor"), diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp new file mode 100644 index 000000000000..3bb7968b487e --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= +AnimCurveCompressionSettingsFactory.cpp: Factory for animation curve compression settings assets +=============================================================================*/ + +#include "Factories/AnimCurveCompressionSettingsFactory.h" +#include "Animation/AnimCurveCompressionSettings.h" + +UAnimCurveCompressionSettingsFactory::UAnimCurveCompressionSettingsFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bCreateNew = true; + SupportedClass = UAnimCurveCompressionSettings::StaticClass(); +} + +UObject* UAnimCurveCompressionSettingsFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs index 4d29d9f0f75d..796932ff5f6e 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs @@ -387,21 +387,6 @@ namespace UnrealBuildTool /// public bool bForceBuildShaderFormats = false; - /// - /// Whether we should compile in support for Simplygon or not. - /// - [RequiresUniqueBuildEnvironment] - [CommandLine("-WithSimplygon")] - [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bCompileSimplygon")] - public bool bCompileSimplygon = true; - - /// - /// Whether we should compile in support for Simplygon's SSF library or not. - /// - [RequiresUniqueBuildEnvironment] - [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bCompileSimplygonSSF")] - public bool bCompileSimplygonSSF = true; - /// /// Whether we should compile SQLite using the custom "Unreal" platform (true), or using the native platform (false). /// @@ -1154,25 +1139,7 @@ namespace UnrealBuildTool { UEBuildPlatform.GetBuildPlatform(Platform).ResetTarget(this); } - - // Check that the appropriate headers exist to enable Simplygon - if(bCompileSimplygon) - { - FileReference HeaderFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Source", "ThirdParty", "NotForLicensees", "Simplygon", "Simplygon-latest", "Inc", "SimplygonSDK.h"); - if(!FileReference.Exists(HeaderFile)) - { - bCompileSimplygon = false; - } - } - if(bCompileSimplygonSSF) - { - FileReference HeaderFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Source", "ThirdParty", "NotForLicensees", "SSF", "Public", "ssf.h"); - if(!FileReference.Exists(HeaderFile)) - { - bCompileSimplygonSSF = false; - } - } - + // If we've got a changelist set, set that we're making a formal build bFormalBuild = (Version.Changelist != 0 && Version.IsPromotedBuild); @@ -1659,16 +1626,6 @@ namespace UnrealBuildTool get { return Inner.bForceBuildShaderFormats; } } - public bool bCompileSimplygon - { - get { return Inner.bCompileSimplygon; } - } - - public bool bCompileSimplygonSSF - { - get { return Inner.bCompileSimplygonSSF; } - } - public bool bCompileCustomSQLitePlatform { get { return Inner.bCompileCustomSQLitePlatform; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs index 86fad1d7cc4d..587207c78d64 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs @@ -739,7 +739,7 @@ namespace UnrealBuildTool public virtual bool HasDefaultBuildConfig(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { string[] BoolKeys = new string[] { - "bCompileApex", "bCompileICU", "bCompileSimplygon", "bCompileSimplygonSSF", + "bCompileApex", "bCompileICU", "bCompileLeanAndMeanUE", "bCompileRecast", "bCompileSpeedTree", "bCompileWithPluginSupport", "bCompilePhysXVehicle", "bCompileFreeType", "bCompileForSize", "bCompileCEF3", "bCompileCustomSQLitePlatform" diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEBuildAndroid.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEBuildAndroid.cs index e1851d7a6706..0b9a2c2fc467 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEBuildAndroid.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEBuildAndroid.cs @@ -105,9 +105,7 @@ namespace UnrealBuildTool Target.bCompileNvCloth = false; Target.bBuildEditor = false; - Target.bBuildDeveloperTools = false; - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + Target.bBuildDeveloperTools = false; Target.bCompileRecast = true; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/HTML5/UEBuildHTML5.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/HTML5/UEBuildHTML5.cs index f4bf7761c8c5..2d3346a1ad3f 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/HTML5/UEBuildHTML5.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/HTML5/UEBuildHTML5.cs @@ -49,9 +49,7 @@ namespace UnrealBuildTool Target.bCompileLeanAndMeanUE = true; Target.bCompileAPEX = false; Target.bCompileNvCloth = false; - Target.bCompilePhysX = true; - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + Target.bCompilePhysX = true; Target.bCompileForSize = true; // {true:[all:-Oz], false:[developer:-O2, shipping:-O3]} WARNING: need emscripten version >= 1.37.13 Target.bUsePCHFiles = false; Target.bDeployAfterCompile = true; diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs index a0b231ca0a1e..9bde6f132b28 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs @@ -623,9 +623,7 @@ namespace UnrealBuildTool Target.bBuildEditor = false; Target.bBuildDeveloperTools = false; Target.bCompileAPEX = false; - Target.bCompileNvCloth = false; - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + Target.bCompileNvCloth = false; Target.bBuildDeveloperTools = false; Target.bDeployAfterCompile = true; diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs index 28240f5e3f7f..352bd59b572d 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs @@ -238,17 +238,12 @@ namespace UnrealBuildTool throw new BuildException("LTO (LTCG) for modular builds is not supported (lld is not currently used for dynamic libraries)."); } - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; // depends on arch, APEX cannot be as of November'16 compiled for AArch32/64 Target.bCompileAPEX = Target.Architecture.StartsWith("x86_64"); Target.bCompileNvCloth = Target.Architecture.StartsWith("x86_64"); - // Disable Simplygon support if compiling against the NULL RHI. if (Target.GlobalDefinitions.Contains("USE_NULL_RHI=1")) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { Target.bCompileCEF3 = false; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs index bfe86d6ca8c0..5de898a9ddb5 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs @@ -122,9 +122,7 @@ namespace UnrealBuildTool } public override void ResetTarget(TargetRules Target) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { } public override void ValidateTarget(TargetRules Target) diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs index 313d14edd527..7b871ff25b9b 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs @@ -486,11 +486,8 @@ namespace UnrealBuildTool /// public override void ValidateTarget(TargetRules Target) { - // Disable Simplygon support if compiling against the NULL RHI. if (Target.GlobalDefinitions.Contains("USE_NULL_RHI=1")) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { Target.bCompileCEF3 = false; } diff --git a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs index a8ac0c37bd4d..e3ec38f26da1 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs @@ -479,9 +479,7 @@ namespace UnrealBuildTool if (Rules.bCompileLeanAndMeanUE) { Rules.bBuildEditor = false; - Rules.bBuildDeveloperTools = Rules.bBuildDeveloperTools ?? false; - Rules.bCompileSimplygon = false; - Rules.bCompileSimplygonSSF = false; + Rules.bBuildDeveloperTools = Rules.bBuildDeveloperTools ?? false; Rules.bCompileSpeedTree = false; } diff --git a/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs b/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs index 42e8639a1f1d..bbd55fe48832 100644 --- a/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs +++ b/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs @@ -18,8 +18,6 @@ public class UnrealMultiUserServerTarget : TargetRules // Lean and mean also override the build developer switch, so just set all the switch it sets manually since we want developer tools (i.e. concert plugins) //bCompileLeanAndMeanUE = true; bBuildEditor = false; - bCompileSimplygon = false; - bCompileSimplygonSSF = false; bCompileSpeedTree = false; // Editor-only data, however, is needed diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h new file mode 100644 index 000000000000..69fbc0879738 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h @@ -0,0 +1,75 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimTypes.h" +#include "AnimCurveCompressionCodec.generated.h" + +class UAnimCurveCompressionCodec; +class UAnimSequence; +struct FBlendedCurve; + +#if WITH_EDITORONLY_DATA +/** Holds the result from animation curve compression */ +struct FAnimCurveCompressionResult +{ + /** The animation curves as raw compressed bytes */ + TArray CompressedBytes; + + /** The codec used by the compressed bytes */ + UAnimCurveCompressionCodec* Codec; + + /** Default constructor */ + FAnimCurveCompressionResult() : CompressedBytes(), Codec(nullptr) {} +}; +#endif + +/* + * Base class for all curve compression codecs. + */ +UCLASS(abstract, hidecategories = Object, EditInlineNew) +class ENGINE_API UAnimCurveCompressionCodec : public UObject +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** A GUID that is unique to this codec instance. After creation, it never changes. */ + FGuid InstanceGuid; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UObject overrides + virtual void PostInitProperties() override; + virtual void PostDuplicate(bool bDuplicateForPIE) override; + virtual void Serialize(FArchive& Ar) override; + + /** Returns whether or not we can use this codec to compress. */ + virtual bool IsCodecValid() const { return true; } + + /** Compresses the curve data from an animation sequence. */ + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) PURE_VIRTUAL(UAnimCurveCompressionCodec::Compress, return false;); + + /* + * Called to generate a unique DDC key for this codec instance. + * A suitable key should be generated from: the InstanceGuid, a codec version, and all relevant properties that drive the behavior. + */ + virtual void PopulateDDCKey(FArchive& Ar); +#endif + + /* + * Decompresses all the active blended curves. + * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression + * behavior should entirely be driven by code and the compressed data. + */ + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurves, ); + + /* + * Decompress a single curve. + * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression + * behavior should entirely be driven by code and the compressed data. + */ + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurve, return 0.0f;); +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h new file mode 100644 index 000000000000..293996abca50 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** +* Stores the raw rich curves as FCompressedRichCurve internally with optional key reduction and key time quantization. +*/ + +#include "CoreMinimal.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "AnimCurveCompressionCodec_CompressedRichCurve.generated.h" + +UCLASS(meta = (DisplayName = "Compressed Rich Curves")) +class ENGINE_API UAnimCurveCompressionCodec_CompressedRichCurve : public UAnimCurveCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** Max error allowed when compressing the rich curves */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0")) + float MaxCurveError; + + /** Whether to use the animation sequence sample rate or an explicit value */ + UPROPERTY(Category = Compression, EditAnywhere) + bool UseAnimSequenceSampleRate; + + /** Sample rate to use when measuring the curve error */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0", EditCondition = "!UseAnimSequenceSampleRate")) + float ErrorSampleRate; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UAnimCurveCompressionCodec overrides + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h new file mode 100644 index 000000000000..cb636bfdb5a7 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** +* Stores the raw rich curves as FCompressedRichCurve internally with optional key reduction and key time quantization. +*/ + +#include "CoreMinimal.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "AnimCurveCompressionCodec_UniformlySampled.generated.h" + +UCLASS(meta = (DisplayName = "Uniformly Sampled")) +class ENGINE_API UAnimCurveCompressionCodec_UniformlySampled : public UAnimCurveCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** Whether to use the animation sequence sample rate or an explicit value */ + UPROPERTY(Category = Compression, EditAnywhere) + bool UseAnimSequenceSampleRate; + + /** Sample rate to use when uniformly sampling */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0", EditCondition = "!UseAnimSequenceSampleRate")) + float SampleRate; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UAnimCurveCompressionCodec overrides + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h new file mode 100644 index 000000000000..d2fa7704fbf0 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h @@ -0,0 +1,45 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimCurveCompressionSettings.generated.h" + +class UAnimCurveCompressionCodec; +class UAnimSequence; + +/* + * This object is used to wrap a curve compression codec. It allows a clean integration in the editor by avoiding the need + * to create asset types and factory wrappers for every codec. + */ +UCLASS(hidecategories = Object) +class ENGINE_API UAnimCurveCompressionSettings : public UObject +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** An animation curve compression codec. */ + UPROPERTY(Category = Compression, Instanced, EditAnywhere, NoClear) + UAnimCurveCompressionCodec* Codec; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UObject overrides + virtual void PostInitProperties() override; + + /** Returns whether or not we can use these settings to compress. */ + bool AreSettingsValid() const; + + /* + * Compresses the animation curves inside the supplied sequence. + * Note that this will modify the animation sequence by populating the compressed bytes + * and the codec used but it is left unchanged if compression fails. + */ + bool Compress(UAnimSequence& AnimSeq) const; + + /** Generates a DDC key that takes into account the current settings and selected codec. */ + FString MakeDDCKey() const; +#endif +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h index e422cbdc178d..7e599f2f7f51 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h @@ -280,7 +280,7 @@ private: }; USTRUCT() -struct FAnimMontageInstance +struct ENGINE_API FAnimMontageInstance { GENERATED_USTRUCT_BODY() @@ -384,7 +384,7 @@ public: * If Follower gets ticked after Leader, then synchronization will be exact and support more complex cases (i.e. timeline jumps). * This can be enforced by setting up tick pre-requisites if desired. */ - ENGINE_API void MontageSync_Follow(struct FAnimMontageInstance* NewLeaderMontageInstance); + void MontageSync_Follow(struct FAnimMontageInstance* NewLeaderMontageInstance); /** Stop leading, release all followers. */ void MontageSync_StopLeading(); /** Stop following our leader */ @@ -428,7 +428,7 @@ public: //~ Begin montage instance Interfaces void Play(float InPlayRate = 1.f); - ENGINE_API void Stop(const FAlphaBlend& InBlendOut, bool bInterrupt=true); + void Stop(const FAlphaBlend& InBlendOut, bool bInterrupt=true); void Pause(); void Initialize(class UAnimMontage * InMontage); @@ -482,10 +482,10 @@ public: * second is normal tick. This tick has to happen later when all node ticks * to accumulate and update curve data/notifies/branching points */ - ENGINE_API void UpdateWeight(float DeltaTime); + void UpdateWeight(float DeltaTime); #if WITH_EDITOR - ENGINE_API void EditorOnly_PreAdvance(); + void EditorOnly_PreAdvance(); #endif /** Simulate is same as Advance, but without calling any events or touching any of the instance data. So it performs a simulation of advancing the timeline. */ @@ -494,7 +494,7 @@ public: FName GetCurrentSection() const; FName GetNextSection() const; - ENGINE_API int32 GetNextSectionID(int32 const & CurrentSectionID) const; + int32 GetNextSectionID(int32 const & CurrentSectionID) const; FName GetSectionNameFromID(int32 const & SectionID) const; // reference has to be managed manually @@ -502,7 +502,7 @@ public: /** Delegate function handlers */ - ENGINE_API void HandleEvents(float PreviousTrackPos, float CurrentTrackPos, const FBranchingPointMarker* BranchingPointMarker); + void HandleEvents(float PreviousTrackPos, float CurrentTrackPos, const FBranchingPointMarker* BranchingPointMarker); private: /** Called by blueprint functions that modify the montages current position. */ @@ -519,11 +519,11 @@ private: public: /** static functions that are used by matinee functionality */ - ENGINE_API static UAnimMontage* SetMatineeAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping); - ENGINE_API static UAnimMontage* PreviewMatineeSetAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, bool bFireNotifies, float DeltaTime); + static UAnimMontage* SetMatineeAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping); + static UAnimMontage* PreviewMatineeSetAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, bool bFireNotifies, float DeltaTime); /** static functions that are used by sequencer montage support*/ - ENGINE_API static UAnimMontage* SetSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bPlaying); - ENGINE_API static UAnimMontage* PreviewSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bFireNotifies, bool bPlaying); + static UAnimMontage* SetSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bPlaying); + static UAnimMontage* PreviewSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bFireNotifies, bool bPlaying); private: static UAnimMontage* InitializeMatineeControl(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, bool bLooping); }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h index 56b7b6f30792..7444d6045d96 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h @@ -448,6 +448,12 @@ protected: */ TArray CompressedTrackToSkeletonMapTable; + /** + * Much like track indices above, we need to be able to remap curve names. The FName inside FSmartName + * is serialized and the UID is generated at runtime. Stored in the DDC. + */ + TArray CompressedCurveNames; + /** * Raw uncompressed keyframe data. */ @@ -491,8 +497,15 @@ public: */ UPROPERTY(Category = Compression, EditAnywhere) bool bAllowFrameStripping; + + /** The curve compression settings used to compress curves in this sequence. */ + UPROPERTY(Category=Compression, EditAnywhere) + class UAnimCurveCompressionSettings* CurveCompressionSettings; #endif // WITH_EDITORONLY_DATA + /** The codec used by the compressed data as determined by the compression settings. */ + class UAnimCurveCompressionCodec* CurveCompressionCodec; + /** The compression format that was used to compress translation tracks. */ TEnumAsByte TranslationCompressionFormat; @@ -548,8 +561,8 @@ public: class AnimEncoding* ScaleCodec; - // Built during compression, could be baked additive or original curve data - FRawCurveTracks CompressedCurveData; + /* Compressed curve data stream used by AnimCurveCompressionCodec */ + TArray CompressedCurveByteStream; // The size of the raw data used to create the compressed data int32 CompressedRawDataSize; @@ -666,6 +679,7 @@ public: #endif // WITH_EDITOR virtual void BeginDestroy() override; virtual void GetAssetRegistryTags(TArray& OutTags) const override; + static void AddReferencedObjects(UObject* This, FReferenceCollector& Collector); //~ End UObject Interface //~ Begin UAnimationAsset Interface @@ -683,8 +697,11 @@ public: virtual bool HasRootMotion() const override { return bEnableRootMotion; } virtual void RefreshCacheData() override; virtual EAdditiveAnimationType GetAdditiveAnimType() const override { return AdditiveAnimType; } - virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData=false) const; - virtual const FRawCurveTracks& GetCurveData() const; + + virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData = false) const override; + virtual float EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData = false) const override; + virtual bool HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const override; + #if WITH_EDITOR virtual void MarkRawDataAsModified(bool bForceNewRawDatGuid = true) override { @@ -738,6 +755,8 @@ public: const TArray& GetAdditiveBaseAnimationData() const { return TemporaryAdditiveBaseAnimationData; } void UpdateCompressedTrackMapFromRaw() { CompressedTrackToSkeletonMapTable = TrackToSkeletonMapTable; } void UpdateCompressedNumFramesFromRaw() { CompressedNumFrames = NumFrames; } + void UpdateCompressedCurveNames(); + void UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName); // Adds a new track (if no track of the supplied name is found) to the raw animation data, optionally setting it to TrackData. int32 AddNewRawTrack(FName TrackName, FRawAnimSequenceTrack* TrackData = nullptr); @@ -745,6 +764,7 @@ public: const TArray& GetRawTrackToSkeletonMapTable() const { return TrackToSkeletonMapTable; } const TArray& GetCompressedTrackToSkeletonMapTable() const { return CompressedTrackToSkeletonMapTable; } + const TArray& GetCompressedCurveNames() const { return CompressedCurveNames; } FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) { return RawAnimationData[TrackIndex]; } const FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) const { return RawAnimationData[TrackIndex]; } @@ -874,6 +894,7 @@ public: void RequestAnimCompression(FRequestAnimCompressionParams Params); void RequestSyncAnimRecompression(bool bOutput = false) { RequestAnimCompression(FRequestAnimCompressionParams(false, false, bOutput)); } bool IsCompressedDataValid() const; + bool IsCurveCompressedDataValid() const; // Write the compressed data to the supplied FArchive void SerializeCompressedData(FArchive& Ar, bool bDDCData); diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h index 5f62ad385146..ea64555ee8c5 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h @@ -101,9 +101,11 @@ class ENGINE_API UAnimSequenceBase : public UAnimationAsset virtual void GetAnimNotifiesFromDeltaPositions(const float& PreviousPosition, const float & CurrentPosition, TArray& OutActiveNotifies) const; /** Evaluate curve data to Instance at the time of CurrentTime **/ - virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData=false) const; + virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData = false) const; + virtual float EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData = false) const; virtual const FRawCurveTracks& GetCurveData() const { return RawCurveData; } + virtual bool HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData = false) const; /** Return Number of Frames **/ virtual int32 GetNumberOfFrames() const; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h index 19d2fb5d171f..a48163ab3f09 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h @@ -38,6 +38,37 @@ enum ERichCurveTangentWeightMode RCTWM_WeightedBoth UMETA(DisplayName="Both") }; +/** Enumerates curve compression options. */ +UENUM() +enum ERichCurveCompressionFormat +{ + /** No keys are present */ + RCCF_Empty UMETA(DisplayName = "Empty"), + + /** All keys use constant interpolation */ + RCCF_Constant UMETA(DisplayName = "Constant"), + + /** All keys use linear interpolation */ + RCCF_Linear UMETA(DisplayName = "Linear"), + + /** All keys use cubic interpolation */ + RCCF_Cubic UMETA(DisplayName = "Cubic"), + + /** Keys use mixed interpolation modes */ + RCCF_Mixed UMETA(DisplayName = "Mixed"), +}; + +/** Enumerates key time compression options. */ +UENUM() +enum ERichCurveKeyTimeCompressionFormat +{ + /** Key time is quantized to 16 bits */ + RCKTCF_uint16 UMETA(DisplayName = "uint16"), + + /** Key time uses full precision */ + RCKTCF_float32 UMETA(DisplayName = "float32"), +}; + /** One key in a rich, editable float curve */ USTRUCT() struct ENGINE_API FRichCurveKey @@ -225,6 +256,12 @@ public: /** Returns a pair for the specified key */ virtual TPair GetKeyTimeValuePair(FKeyHandle KeyHandle) const final override; + /** Returns whether the curve is constant or not */ + bool IsConstant(float Tolerance = SMALL_NUMBER) const; + + /** Returns whether the curve is empty or not */ + bool IsEmpty() const { return Keys.Num() == 0; } + /** Set the interp mode of the specified key */ virtual void SetKeyInterpMode(FKeyHandle KeyHandle, ERichCurveInterpMode NewInterpMode) final override; @@ -272,6 +309,9 @@ public: virtual void RemoveRedundantKeys(float Tolerance) final override; virtual void RemoveRedundantKeys(float Tolerance, float FirstKeyTime, float LastKeyTime) final override; + /** Compresses a rich curve for efficient runtime storage and evaluation */ + void CompressCurve(struct FCompressedRichCurve& OutCurve, float ErrorThreshold = 0.0001f, float SampleRate = 120.0f) const; + private: void RemoveRedundantKeysInternal(float Tolerance, int32 InStartKeepKey, int32 InEndKeepKey); virtual int32 GetKeyIndex(float KeyTime, float KeyTimeTolerance) const override final; @@ -289,6 +329,85 @@ public: TArray Keys; }; +/** + * A runtime optimized representation of a FRichCurve. It consumes less memory and evaluates faster. + */ +USTRUCT() +struct ENGINE_API FCompressedRichCurve +{ + GENERATED_USTRUCT_BODY() + + /** Compression format used by CompressedKeys */ + TEnumAsByte CompressionFormat; + + /** Compression format used to pack the key time */ + TEnumAsByte KeyTimeCompressionFormat; + + /** Pre-infinity extrapolation state */ + TEnumAsByte PreInfinityExtrap; + + /** Post-infinity extrapolation state */ + TEnumAsByte PostInfinityExtrap; + + union TConstantValueNumKeys + { + float ConstantValue; + int32 NumKeys; + + TConstantValueNumKeys() : NumKeys(0) {} + }; + + /** + * If the compression format is constant, this is the value returned + * Inline here to reduce the likelihood of accessing the compressed keys data for the common case of constant/zero/empty curves + * When a curve is linear/cubic/mixed, the constant float value isn't used and instead we use the number of keys + */ + TConstantValueNumKeys ConstantValueNumKeys; + + /** Compressed keys, used only outside of the editor */ + TArray CompressedKeys; + + FCompressedRichCurve() + : CompressionFormat(RCCF_Empty) + , KeyTimeCompressionFormat(RCKTCF_float32) + , PreInfinityExtrap(RCCE_None) + , PostInfinityExtrap(RCCE_None) + , ConstantValueNumKeys() + , CompressedKeys() + {} + + /** Evaluate this rich curve at the specified time */ + float Eval(float InTime, float InDefaultValue = 0.0f) const; + + /** Evaluate this rich curve at the specified time */ + static float StaticEval(ERichCurveCompressionFormat CompressionFormat, ERichCurveKeyTimeCompressionFormat KeyTimeCompressionFormat, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue = 0.0f); + + /** ICPPStructOps interface */ + bool Serialize(FArchive& Ar); + bool operator==(const FCompressedRichCurve& Other) const; + bool operator!=(const FCompressedRichCurve& Other) const { return !(*this == Other); } + + friend FArchive& operator<<(FArchive& Ar, FCompressedRichCurve& Curve) + { + Curve.Serialize(Ar); + return Ar; + } +}; + +/* + * Override serialization for compressed rich curves to handle the union + */ +template<> +struct TStructOpsTypeTraits + : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, + WithCopy = false, + WithIdenticalViaEquality = true, + }; +}; /** * Info about a curve to be edited. diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp index 6e1112307f3e..e07749e78619 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp @@ -6,6 +6,7 @@ #include "AnimationUtils.h" #include "AnimEncoding.h" #include "Animation/AnimCompress.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "AnimationCompression.h" #include "UObject/Package.h" @@ -70,6 +71,7 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons // * Baked Additive Flag // * Additive ref pose GUID or hardcoded string if not available // * Compression Settings + // * Curve compression settings uint8 AdditiveSettings = bCanBakeAdditive ? (OriginalAnimSequence->RefPoseType << 4) + OriginalAnimSequence->AdditiveAnimType : 0; @@ -78,7 +80,7 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons const int32 StripFrame = bPerformStripping ? 1 : 0; - FString Ret = FString::Printf(TEXT("%i_%i_%i_%i_%s%s%s_%c%c%i_%s_%s"), + FString Ret = FString::Printf(TEXT("%i_%i_%i_%i_%s%s%s_%c%c%i_%s_%s_%s"), (int32)UE_ANIMCOMPRESSION_DERIVEDDATA_VER, (int32)CURRENT_ANIMATION_ENCODING_PACKAGE_VERSION, OriginalAnimSequence->CompressCommandletVersion, @@ -90,7 +92,8 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons RefType, OriginalAnimSequence->RefFrameIndex, (bCanBakeAdditive && AdditiveBase) ? *AdditiveBase->GetRawDataGuid().ToString() : TEXT("NoAdditiveBase"), - *OriginalAnimSequence->CompressionScheme->MakeDDCKey() + *OriginalAnimSequence->CompressionScheme->MakeDDCKey(), + *OriginalAnimSequence->CurveCompressionSettings->MakeDDCKey() ); return Ret; @@ -155,13 +158,9 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) } AnimToOperateOn->UpdateCompressedTrackMapFromRaw(); - AnimToOperateOn->CompressedCurveData = AnimToOperateOn->RawCurveData; //Curves don't actually get compressed, but could have additives baked in + AnimToOperateOn->UpdateCompressedCurveNames(); - const float MaxCurveError = AnimToOperateOn->CompressionScheme->MaxCurveError; - for (FFloatCurve& Curve : AnimToOperateOn->CompressedCurveData.FloatCurves) - { - Curve.FloatCurve.RemoveRedundantKeys(MaxCurveError); - } + bool bCurveCompressionSuccess = FAnimationUtils::CompressAnimCurves(*AnimToOperateOn); #if DO_CHECK FString CompressionName = AnimToOperateOn->CompressionScheme->GetFullName(); @@ -172,7 +171,7 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) AnimToOperateOn->UpdateCompressedNumFramesFromRaw(); //Do this before compression so compress code can read the correct value FAnimationUtils::CompressAnimSequence(AnimToOperateOn, *CompressContext.Get()); - bCompressionSuccessful = AnimToOperateOn->IsCompressedDataValid(); + bCompressionSuccessful = AnimToOperateOn->IsCompressedDataValid() && bCurveCompressionSuccess; ensureMsgf(bCompressionSuccessful, TEXT("Anim Compression failed for Sequence '%s' with compression scheme '%s': compressed data empty\n\tAnimIndex: %i\n\tMaxAnim:%i\n\tAllowAltCompressor:%s\n\tOutput:%s"), *AnimToOperateOn->GetFullName(), @@ -191,6 +190,7 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) { CA_SUPPRESS(6011); // See https://connect.microsoft.com/VisualStudio/feedback/details/3007725 OriginalAnimSequence->CompressionScheme = static_cast(StaticDuplicateObject(AnimToOperateOn->CompressionScheme, OriginalAnimSequence)); + OriginalAnimSequence->CurveCompressionSettings = AnimToOperateOn->CurveCompressionSettings; } if (bCompressionSuccessful) diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h index 747a013706ed..276aacdd1702 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h @@ -47,7 +47,7 @@ public: // This is a version string that mimics the old versioning scheme. If you // want to bump this version, generate a new guid using VS->Tools->Create GUID and // return it here. Ex. - return TEXT("EFCFC622A8794B758D53CDE253471CBD"); + return TEXT("D58C7C3E48274C04BFC4D7823AF46F5E"); } virtual FString GetPluginSpecificCacheKeySuffix() const override; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp new file mode 100644 index 000000000000..1548cc7b869a --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec.h" + +UAnimCurveCompressionCodec::UAnimCurveCompressionCodec(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimCurveCompressionCodec::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + InstanceGuid = FGuid::NewGuid(); + } +} + +void UAnimCurveCompressionCodec::PostDuplicate(bool bDuplicateForPIE) +{ + Super::PostDuplicate(bDuplicateForPIE); + + InstanceGuid = FGuid::NewGuid(); +} + +void UAnimCurveCompressionCodec::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (!Ar.IsCooking()) + { + Ar << InstanceGuid; + } +} + +void UAnimCurveCompressionCodec::PopulateDDCKey(FArchive& Ar) +{ + Ar << InstanceGuid; +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp new file mode 100644 index 000000000000..d5de3d8d1368 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp @@ -0,0 +1,137 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec_CompressedRichCurve.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionCodec_CompressedRichCurve::UAnimCurveCompressionCodec_CompressedRichCurve(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + MaxCurveError = 0.0f; + UseAnimSequenceSampleRate = true; + ErrorSampleRate = 60.0f; +#endif +} + +// This mirrors in part the FCompressedRichCurve +struct FCurveDesc +{ + TEnumAsByte CompressionFormat; + TEnumAsByte KeyTimeCompressionFormat; + TEnumAsByte PreInfinityExtrap; + TEnumAsByte PostInfinityExtrap; + FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys; + int32 KeyDataOffset; +}; + +#if WITH_EDITORONLY_DATA +bool UAnimCurveCompressionCodec_CompressedRichCurve::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +{ + int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); + + TArray Curves; + Curves.Reserve(NumCurves); + Curves.AddUninitialized(NumCurves); + + int32 KeyDataOffset = 0; + KeyDataOffset += sizeof(FCurveDesc) * NumCurves; + + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + const float SampleRate = UseAnimSequenceSampleRate ? Helper.KeysPerSecond() : ErrorSampleRate; + + TArray KeyData; + + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + + FRichCurve RawCurve = Curve.FloatCurve; // Copy + RawCurve.RemoveRedundantKeys(MaxCurveError); + + FCompressedRichCurve CompressedCurve; + RawCurve.CompressCurve(CompressedCurve, MaxCurveError, SampleRate); + + FCurveDesc& CurveDesc = Curves[CurveIndex]; + CurveDesc.CompressionFormat = CompressedCurve.CompressionFormat; + CurveDesc.KeyTimeCompressionFormat = CompressedCurve.KeyTimeCompressionFormat; + CurveDesc.PreInfinityExtrap = CompressedCurve.PreInfinityExtrap; + CurveDesc.PostInfinityExtrap = CompressedCurve.PostInfinityExtrap; + CurveDesc.ConstantValueNumKeys = CompressedCurve.ConstantValueNumKeys; + CurveDesc.KeyDataOffset = KeyDataOffset; + + KeyDataOffset += CompressedCurve.CompressedKeys.Num(); + KeyData.Append(CompressedCurve.CompressedKeys); + } + + TArray TempBytes; + TempBytes.Reserve(KeyDataOffset); + + // Serialize the compression settings into a temporary array. The archive + // is flagged as persistent so that machines of different endianness produce + // identical binary results. + FMemoryWriter Ar(TempBytes, /*bIsPersistent=*/ true); + + Ar.Serialize(Curves.GetData(), sizeof(FCurveDesc) * NumCurves); + Ar.Serialize(KeyData.GetData(), KeyData.Num()); + + OutResult.CompressedBytes = TempBytes; + OutResult.Codec = this; + + return true; +} + +void UAnimCurveCompressionCodec_CompressedRichCurve::PopulateDDCKey(FArchive& Ar) +{ + Super::PopulateDDCKey(Ar); + + int32 CodecVersion = 0; + + Ar << CodecVersion; + Ar << MaxCurveError; + Ar << UseAnimSequenceSampleRate; + Ar << ErrorSampleRate; +} +#endif + +void UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +{ + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); + + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + if (Curves.IsEnabled(CurveName.UID)) + { + const FCurveDesc& Curve = CurveDescriptions[CurveIndex]; + const uint8* CompressedKeys = Buffer + Curve.KeyDataOffset; + const float Value = FCompressedRichCurve::StaticEval(Curve.CompressionFormat, Curve.KeyTimeCompressionFormat, Curve.PreInfinityExtrap, Curve.PostInfinityExtrap, Curve.ConstantValueNumKeys, CompressedKeys, CurrentTime); + Curves.Set(CurveName.UID, Value); + } + } +} + +float UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +{ + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); + + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + if (CurveName.UID == CurveUID) + { + const FCurveDesc& Curve = CurveDescriptions[CurveIndex]; + const uint8* CompressedKeys = Buffer + Curve.KeyDataOffset; + const float Value = FCompressedRichCurve::StaticEval(Curve.CompressionFormat, Curve.KeyTimeCompressionFormat, Curve.PreInfinityExtrap, Curve.PostInfinityExtrap, Curve.ConstantValueNumKeys, CompressedKeys, CurrentTime); + return Value; + } + } + + return 0.0f; +} diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp new file mode 100644 index 000000000000..efbe63ff0e63 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp @@ -0,0 +1,293 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec_UniformlySampled.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionCodec_UniformlySampled::UAnimCurveCompressionCodec_UniformlySampled(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + UseAnimSequenceSampleRate = true; + SampleRate = 30.0f; +#endif +} + +#if WITH_EDITORONLY_DATA +bool UAnimCurveCompressionCodec_UniformlySampled::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +{ + const int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); + const float Duration = AnimSeq.SequenceLength; + + int32 NumSamples; + float SampleRate_; + if (UseAnimSequenceSampleRate) + { + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + SampleRate_ = Helper.KeysPerSecond(); + NumSamples = FMath::RoundToInt(Duration * SampleRate_) + 1; + } + else + { + // If our duration isn't an exact multiple of the sample rate, we'll round + // and end up with a sample rate slightly corrected to make sure we spread + // the resulting error over the whole duration + NumSamples = FMath::RoundToInt(Duration * SampleRate) + 1; + SampleRate_ = (NumSamples - 1) / Duration; + } + + int32 NumConstantCurves = 0; + for (const FFloatCurve& Curve : AnimSeq.RawCurveData.FloatCurves) + { + if (Curve.FloatCurve.IsConstant()) + { + NumConstantCurves++; + } + } + + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + // 1 bit per curve, round up + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + + int32 BufferSize = 0; + BufferSize += sizeof(int32); // NumConstantCurves + BufferSize += sizeof(int32); // NumSamples + BufferSize += sizeof(float); // SampleRate + BufferSize += ConstantBitsetSize; // Constant curve bitset + BufferSize += sizeof(float) * NumConstantCurves; // Constant curve samples + BufferSize += sizeof(float) * NumAnimatedCurves * NumSamples; // Animated curve samples + + TArray Buffer; + Buffer.Reserve(BufferSize); + Buffer.AddUninitialized(BufferSize); + + int32 BufferOffset = 0; + int32* NumConstantCurvesPtr = (int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + int32* NumSamplesPtr = (int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + float* SampleRatePtr = (float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + *NumConstantCurvesPtr = NumConstantCurves; + *NumSamplesPtr = NumSamples; + *SampleRatePtr = SampleRate_; + + if (NumCurves > 0 && NumSamples > 0) + { + uint32* ConstantCurvesBitsetPtr = (uint32*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + FMemory::Memzero(ConstantCurvesBitsetPtr, ConstantBitsetSize); + + float* ConstantSamplesPtr = NumConstantCurves > 0 ? (float*)&Buffer[BufferOffset] : nullptr; + BufferOffset += sizeof(float) * NumConstantCurves; + + float* AnimatedSamplesPtr = NumAnimatedCurves > 0 ? (float*)&Buffer[BufferOffset] : nullptr; + BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + if (Curve.FloatCurve.IsConstant()) + { + if (Curve.FloatCurve.IsEmpty()) + { + ConstantSamplesPtr[ConstantCurveIndex] = Curve.FloatCurve.DefaultValue; + } + else + { + ConstantSamplesPtr[ConstantCurveIndex] = Curve.FloatCurve.Keys[0].Value; + } + + // Bitset uses little-endian bit ordering + ConstantCurvesBitsetPtr[CurveIndex / 32] |= 1 << (CurveIndex % 32); + ConstantCurveIndex++; + } + } + + // Write out samples sorted by time first in order to have everything contiguous in memory + // for improved cache locality + // Curve 0 Key 0, Curve 0 Key 1, Curve 0 Key N, Curve 1 Key 0, Curve 1 Key 1, Curve 1 Key N, Curve M Key 0, ... + const float InvSampleRate = 1.0f / SampleRate_; + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const float SampleTime = FMath::Clamp(SampleIndex * InvSampleRate, 0.0f, Duration); + float* AnimatedSamples = AnimatedSamplesPtr + (SampleIndex * NumAnimatedCurves); + + for (int32 CurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + if (Curve.FloatCurve.IsConstant()) + { + // Skip constant curves, their data has already been written + continue; + } + + const float SampleValue = Curve.FloatCurve.Eval(SampleTime); + + AnimatedSamples[AnimatedCurveIndex] = SampleValue; + AnimatedCurveIndex++; + } + } + } + + check(BufferOffset == BufferSize); + + OutResult.CompressedBytes = Buffer; + OutResult.Codec = this; + + return true; +} + +void UAnimCurveCompressionCodec_UniformlySampled::PopulateDDCKey(FArchive& Ar) +{ + Super::PopulateDDCKey(Ar); + + int32 CodecVersion = 0; + + Ar << CodecVersion; + Ar << UseAnimSequenceSampleRate; + Ar << SampleRate; +} +#endif + +void UAnimCurveCompressionCodec_UniformlySampled::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return; + } + + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + + int32 BufferOffset = 0; + const int32 NumConstantCurves = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const int32 NumSamples = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + if (NumSamples == 0) + { + return; + } + + const float SampleRate_ = *(const float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + const uint32* ConstantCurvesBitsetPtr = (const uint32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(uint32) * NumCurves; + + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + const float* ConstantSamplesPtr = (const float*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + + const float* AnimatedSamplesPtr = (const float*)&Buffer[BufferOffset]; + //const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + //BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + const float SamplePoint = CurrentTime * SampleRate_; + const int32 SampleIndex0 = FMath::Clamp(FMath::FloorToInt(SamplePoint), 0, NumSamples - 1); + const int32 SampleIndex1 = FMath::Min(SampleIndex0 + 1, NumSamples - 1); + const float InterpolationAlpha = SamplePoint - float(SampleIndex0); + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + const float* AnimatedSamples0 = AnimatedSamplesPtr + (SampleIndex0 * NumAnimatedCurves); + const float* AnimatedSamples1 = AnimatedSamplesPtr + (SampleIndex1 * NumAnimatedCurves); + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + const bool bIsConstant = (ConstantCurvesBitsetPtr[CurveIndex / 32] & (1 << (CurveIndex % 32))) != 0; + if (Curves.IsEnabled(CurveName.UID)) + { + float Sample; + if (bIsConstant) + { + Sample = ConstantSamplesPtr[ConstantCurveIndex]; + } + else + { + const float Sample0 = AnimatedSamples0[AnimatedCurveIndex]; + const float Sample1 = AnimatedSamples1[AnimatedCurveIndex]; + Sample = FMath::Lerp(Sample0, Sample1, InterpolationAlpha); + } + + Curves.Set(CurveName.UID, Sample); + } + + (bIsConstant ? ConstantCurveIndex : AnimatedCurveIndex)++; + } +} + +float UAnimCurveCompressionCodec_UniformlySampled::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return 0.0f; + } + + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + + int32 BufferOffset = 0; + const int32 NumConstantCurves = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const int32 NumSamples = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const float SampleRate_ = *(const float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + const uint32* ConstantCurvesBitsetPtr = (const uint32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(uint32) * NumCurves; + + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + const float* ConstantSamplesPtr = (const float*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + + const float* AnimatedSamplesPtr = (const float*)&Buffer[BufferOffset]; + //const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + //BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + const bool bIsConstant = (ConstantCurvesBitsetPtr[CurveIndex / 32] & (1 << (CurveIndex % 32))) != 0; + if (CurveName.UID == CurveUID) + { + float Sample; + if (bIsConstant) + { + Sample = ConstantSamplesPtr[ConstantCurveIndex]; + } + else + { + const float SamplePoint = CurrentTime * SampleRate_; + const int32 SampleIndex0 = FMath::Clamp(FMath::FloorToInt(SamplePoint), 0, NumSamples - 1); + const int32 SampleIndex1 = FMath::Min(SampleIndex0 + 1, NumSamples - 1); + const float InterpolationAlpha = SamplePoint - float(SampleIndex0); + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + const float Sample0 = AnimatedSamplesPtr[(SampleIndex0 * NumAnimatedCurves) + CurveIndex]; + const float Sample1 = AnimatedSamplesPtr[(SampleIndex1 * NumAnimatedCurves) + CurveIndex]; + Sample = FMath::Lerp(Sample0, Sample1, InterpolationAlpha); + } + + return Sample; + } + + (bIsConstant ? ConstantCurveIndex : AnimatedCurveIndex)++; + } + + return 0.0f; +} diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp new file mode 100644 index 000000000000..f1986ba890b6 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp @@ -0,0 +1,74 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionSettings.h" +#include "Animation/AnimCurveCompressionCodec_CompressedRichCurve.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionSettings::UAnimCurveCompressionSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimCurveCompressionSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + // Ensure we are never null + Codec = NewObject(this, NAME_None, RF_Public); + } +} + +bool UAnimCurveCompressionSettings::AreSettingsValid() const +{ + return Codec != nullptr && Codec->IsCodecValid(); +} + +bool UAnimCurveCompressionSettings::Compress(UAnimSequence& AnimSeq) const +{ + if (Codec == nullptr || !AreSettingsValid()) + { + return false; + } + + FAnimCurveCompressionResult CompressionResult; + bool Success = Codec->Compress(AnimSeq, CompressionResult); + if (Success) + { + AnimSeq.CompressedCurveByteStream = CompressionResult.CompressedBytes; + AnimSeq.CurveCompressionCodec = CompressionResult.Codec; + } + + return Success; +} + +FString UAnimCurveCompressionSettings::MakeDDCKey() const +{ + if (Codec == nullptr) + { + return TEXT(""); + } + + TArray TempBytes; + TempBytes.Reserve(64); + + // Serialize the compression settings into a temporary array. The archive + // is flagged as persistent so that machines of different endianness produce + // identical binary results. + FMemoryWriter Ar(TempBytes, /*bIsPersistent=*/ true); + + Codec->PopulateDDCKey(Ar); + + FString Key; + Key.Reserve(TempBytes.Num() + 1); + for (int32 ByteIndex = 0; ByteIndex < TempBytes.Num(); ++ByteIndex) + { + ByteToHex(TempBytes[ByteIndex], Key); + } + + return Key; +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp index bbf8028a6e9a..dc0c4dbf7ace 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp @@ -3,7 +3,7 @@ #include "Animation/AnimCurveTypes.h" #include "UObject/FrameworkObjectVersion.h" -DECLARE_CYCLE_STAT(TEXT("AnimSeq EvalCurveData"), STAT_AnimSeq_EvalCurveData, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("EvalRawCurveData"), STAT_EvalRawCurveData, STATGROUP_Anim); ///////////////////////////////////////////////////// // FFloatCurve @@ -293,7 +293,7 @@ void FTransformCurve::Resize(float NewLength, bool bInsert/* whether insert or r void FRawCurveTracks::EvaluateCurveData( FBlendedCurve& Curves, float CurrentTime ) const { - SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + SCOPE_CYCLE_COUNTER(STAT_EvalRawCurveData); if (Curves.NumValidCurveCount > 0) { // evaluate the curve data at the CurrentTime and add to Instance @@ -302,7 +302,8 @@ void FRawCurveTracks::EvaluateCurveData( FBlendedCurve& Curves, float CurrentTim const FFloatCurve& Curve = *CurveIter; if (Curves.IsEnabled(Curve.Name.UID)) { - Curves.Set(Curve.Name.UID, Curve.Evaluate(CurrentTime)); + float Value = Curve.Evaluate(CurrentTime); + Curves.Set(Curve.Name.UID, Value); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp index 0d178c6e51d3..f0e361faf26c 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp @@ -34,12 +34,7 @@ void FAnimNode_SubInstance::Update_AnyThread(const FAnimationUpdateContext& Cont { FAnimInstanceProxy& Proxy = InstanceToRun->GetProxyOnAnyThread(); - // Only update if we've not had a single-threaded update already - if(InstanceToRun->bNeedsUpdate) - { - Proxy.UpdateAnimation(); - } - + // First copy properties check(InstanceProperties.Num() == SubInstanceProperties.Num()); for(int32 PropIdx = 0; PropIdx < InstanceProperties.Num(); ++PropIdx) { @@ -58,6 +53,12 @@ void FAnimNode_SubInstance::Update_AnyThread(const FAnimationUpdateContext& Cont CallerProperty->CopyCompleteValue(DestPtr, SrcPtr); } } + + // Only update if we've not had a single-threaded update already + if(InstanceToRun->bNeedsUpdate) + { + Proxy.UpdateAnimation(); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp index d2c1dcad1edf..9a814332d6d6 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp @@ -21,6 +21,8 @@ #include "Animation/AnimNotifies/AnimNotify.h" #include "Animation/Rig.h" #include "Animation/AnimationSettings.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "EditorFramework/AssetImportData.h" #include "Logging/TokenizedMessage.h" #include "Logging/MessageLog.h" @@ -41,6 +43,7 @@ CSV_DEFINE_CATEGORY(Animation, (!UE_BUILD_SHIPPING)); DECLARE_CYCLE_STAT(TEXT("AnimSeq GetBonePose"), STAT_AnimSeq_GetBonePose, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("AnimSeq EvalCurveData"), STAT_AnimSeq_EvalCurveData, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("Build Anim Track Pairs"), STAT_BuildAnimTrackPairs, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("Extract Pose From Anim Data"), STAT_ExtractPoseFromAnimData, STATGROUP_Anim); @@ -277,9 +280,24 @@ void UAnimSequence::GetAssetRegistryTags(TArray& OutTags) con Super::GetAssetRegistryTags(OutTags); } +void UAnimSequence::AddReferencedObjects(UObject* This, FReferenceCollector& Collector) +{ + Super::AddReferencedObjects(This, Collector); + + UAnimSequence* AnimSeq = CastChecked(This); + Collector.AddReferencedObject(AnimSeq->CurveCompressionCodec); +} + int32 UAnimSequence::GetUncompressedRawSize() const { - return ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * RawAnimationData.Num() * NumFrames); + int32 BoneRawSize = ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * RawAnimationData.Num() * NumFrames); + int32 CurveRawSize = 0; + for (const FFloatCurve& Curve : RawCurveData.FloatCurves) + { + CurveRawSize += sizeof(FFloatCurve); + CurveRawSize += sizeof(FRichCurveKey) * Curve.FloatCurve.Keys.Num(); + } + return BoneRawSize + CurveRawSize; } int32 UAnimSequence::GetApproxRawSize() const @@ -293,13 +311,19 @@ int32 UAnimSequence::GetApproxRawSize() const sizeof( FQuat ) * RawTrack.RotKeys.Num() + sizeof( FVector ) * RawTrack.ScaleKeys.Num(); } + for (const FFloatCurve& Curve : RawCurveData.FloatCurves) + { + Total += sizeof(FFloatCurve); + Total += sizeof(FRichCurveKey) * Curve.FloatCurve.Keys.Num(); + } return Total; } int32 UAnimSequence::GetApproxCompressedSize() const { - const int32 Total = sizeof(int32)*CompressedTrackOffsets.Num() + CompressedByteStream.Num() + CompressedScaleOffsets.GetMemorySize() + sizeof(FCompressedSegment)*CompressedSegments.Num(); - return Total; + int32 BoneTotal = sizeof(int32)*CompressedTrackOffsets.Num() + CompressedByteStream.Num() + CompressedScaleOffsets.GetMemorySize() + sizeof(FCompressedSegment)*CompressedSegments.Num(); + int32 CurveTotal = CompressedCurveByteStream.Num(); + return BoneTotal + CurveTotal; } /** @@ -654,7 +678,10 @@ void UAnimSequence::PostLoad() if (USkeleton* CurrentSkeleton = GetSkeleton()) { - VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimCurveMappingName, CompressedCurveData.FloatCurves); + for (FSmartName& CurveName : CompressedCurveNames) + { + CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); + } #if WITH_EDITOR VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimTrackCurveMappingName, RawCurveData.TransformCurves); @@ -666,18 +693,6 @@ void UAnimSequence::PostLoad() #endif } -#if WITH_EDITOR - - // Compressed curve flags are not authoritative (they come from the DDC). Keep them up to date with - // actual anim flags - for (FFloatCurve& Curve : RawCurveData.FloatCurves) - { - if (FAnimCurveBase* CompressedCurve = CompressedCurveData.GetCurveData(Curve.Name.UID)) - { - CompressedCurve->SetCurveTypeFlags(Curve.GetCurveTypeFlags()); - } - } -#endif // WITH_EDITOR AddAnimLoadingDebugEntry(TEXT("PostLoadEnd")); } @@ -845,11 +860,17 @@ void UAnimSequence::PostEditChangeProperty(FPropertyChangedEvent& PropertyChange } } + // @Todo fix me: This is temporary fix to make sure they always have compressed data if (RawAnimationData.Num() > 0 && (!IsCompressedDataValid() || bAdditiveSettingsChanged)) { PostProcessSequence(); } + + if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAnimSequence, CurveCompressionSettings)) + { + RequestSyncAnimRecompression(false); + } } void UAnimSequence::PostDuplicate(bool bDuplicateForPIE) @@ -1461,6 +1482,7 @@ void UAnimSequence::BuildPoseFromRawData(const TArray& In void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, const FAnimExtractContext& ExtractionContext, bool bForceUseRawData) const { SCOPE_CYCLE_COUNTER(STAT_AnimSeq_GetBonePose); + CSV_SCOPED_TIMING_STAT(Animation, AnimSeq_GetBonePose); const FBoneContainer& RequiredBones = OutPose.GetBoneContainer(); const bool bUseRawDataForPoseExtraction = bForceUseRawData || UseRawDataForPoseExtraction(RequiredBones); @@ -1736,6 +1758,31 @@ void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, } #if WITH_EDITORONLY_DATA +void UAnimSequence::UpdateCompressedCurveNames() +{ + // Copy our curve names to the compressed array + const int32 NumCurves = RawCurveData.FloatCurves.Num(); + CompressedCurveNames.Reset(NumCurves); + CompressedCurveNames.AddUninitialized(NumCurves); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = RawCurveData.FloatCurves[CurveIndex]; + CompressedCurveNames[CurveIndex] = Curve.Name; + } +} + +void UAnimSequence::UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName) +{ + for (FSmartName& CurveName : CompressedCurveNames) + { + if (CurveName.UID == CurveUID) + { + CurveName = NewCurveName; + break; + } + } +} + int32 UAnimSequence::AddNewRawTrack(FName TrackName, FRawAnimSequenceTrack* TrackData) { const int32 SkeletonIndex = GetSkeleton() ? GetSkeleton()->GetReferenceSkeleton().FindBoneIndex(TrackName) : INDEX_NONE; @@ -2428,6 +2475,11 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) CompressionScheme = FAnimationUtils::GetDefaultAnimationCompressionAlgorithm(); } + if (CurveCompressionSettings == nullptr || !CurveCompressionSettings->AreSettingsValid()) + { + CurveCompressionSettings = FAnimationUtils::GetDefaultAnimationCurveCompressionSettings(); + } + if (!RawDataGuid.IsValid()) { RawDataGuid = GenerateGuidFromRawData(); @@ -2526,7 +2578,7 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) Ar << CompressedSegments; Ar << CompressedTrackToSkeletonMapTable; - Ar << CompressedCurveData; + Ar << CompressedCurveNames; Ar << CompressedRawDataSize; Ar << CompressedNumFrames; @@ -2552,6 +2604,31 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) // and then use the codecs to byte swap check(RotationCodec != NULL); ((AnimEncoding*)RotationCodec)->ByteSwapIn(*this, MemoryReader); + +#if WITH_EDITOR + if (bDDCData) + { + FString CurveCodecPath; + Ar << CurveCodecPath; + + CurveCompressionCodec = LoadObject(nullptr, *CurveCodecPath); + } + else +#else + check(!bDDCData); +#endif + { + UAnimCurveCompressionCodec* CurveCodec = nullptr; + Ar << CurveCodec; + CurveCompressionCodec = CurveCodec; + } + + int32 NumCurveBytes; + Ar << NumCurveBytes; + + CompressedCurveByteStream.Empty(NumCurveBytes); + CompressedCurveByteStream.AddUninitialized(NumCurveBytes); + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); } else if (Ar.IsSaving() || Ar.IsCountingMemory()) { @@ -2578,6 +2655,24 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) // Count compressed data. Ar.CountBytes(SerializedData.Num(), SerializedData.Num()); + +#if WITH_EDITOR + if (bDDCData) + { + FString CurveCodecPath = CurveCompressionCodec->GetPathName(); + Ar << CurveCodecPath; + } + else +#else + check(!bDDCData); +#endif + { + Ar << CurveCompressionCodec; + } + + int32 NumCurveBytes = CompressedCurveByteStream.Num(); + Ar << NumCurveBytes; + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); } #if WITH_EDITOR @@ -2590,8 +2685,14 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) { if(USkeleton* CurrentSkeleton = GetSkeleton()) { - VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimCurveMappingName, CompressedCurveData.FloatCurves); - bUseRawDataOnly = !IsCompressedDataValid(); + // Refresh the compressed curve names since the IDs might have changed since + for (FSmartName& CurveName : CompressedCurveNames) + { + CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); + } + + bUseRawDataOnly = !IsCompressedDataValid() || !IsCurveCompressedDataValid(); + ensureMsgf(!bUseRawDataOnly, TEXT("Anim Compression failed for Sequence '%s' Guid:%s CompressedDebugData:\n\tOriginal Anim:%s\n\tAdditiveSetting:%i\n\tCompression Scheme:%s\n\tRawDataGuid:%s"), *GetFullName(), *RawDataGuid.ToString(), @@ -3023,12 +3124,14 @@ void UAnimSequence::RecycleAnimSequence() CompressedSegments.Empty(0); SourceRawAnimationData.Empty(0); RawCurveData.Empty(); - CompressedCurveData.Empty(); + CompressedCurveByteStream.Empty(0); + CompressedCurveNames.Empty(0); AuthoredSyncMarkers.Empty(); UniqueMarkerNames.Empty(); Notifies.Empty(); AnimNotifyTracks.Empty(); CompressionScheme = nullptr; + CurveCompressionCodec = nullptr; TranslationCompressionFormat = RotationCompressionFormat = ScaleCompressionFormat = ACF_None; #endif // WITH_EDITORONLY_DATA } @@ -4993,28 +5096,56 @@ void UAnimSequence::RefreshCacheData() void UAnimSequence::EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData) const { - if (bUseRawDataOnly || bForceUseRawData) + SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + + if (OutCurve.NumValidCurveCount == 0) { - Super::EvaluateCurveData(OutCurve, CurrentTime); + return; + } + + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) + { + Super::EvaluateCurveData(OutCurve, CurrentTime, bForceUseRawData); } else { - CompressedCurveData.EvaluateCurveData(OutCurve, CurrentTime); + CSV_SCOPED_TIMING_STAT(Animation, EvaluateCurveData); + CurveCompressionCodec->DecompressCurves(*this, OutCurve, CurrentTime); } } -const FRawCurveTracks& UAnimSequence::GetCurveData() const +float UAnimSequence::EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData) const { - if (bUseRawDataOnly) + SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) { - return Super::GetCurveData(); + return Super::EvaluateCurveData(CurveUID, CurrentTime, bForceUseRawData); } else { - return CompressedCurveData; + return CurveCompressionCodec->DecompressCurve(*this, CurveUID, CurrentTime); } } +bool UAnimSequence::HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const +{ + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) + { + return Super::HasCurveData(CurveUID, bForceUseRawData); + } + + for (const FSmartName& CurveName : CompressedCurveNames) + { + if (CurveName.UID == CurveUID) + { + return true; + } + } + + return false; +} + void UAnimSequence::RefreshSyncMarkerDataFromAuthored() { #if WITH_EDITOR @@ -5048,7 +5179,7 @@ void UAnimSequence::AdvanceMarkerPhaseAsLeader(bool bLooping, float MoveDelta, c { check(MoveDelta != 0.f); const bool bPlayingForwards = MoveDelta > 0.f; - float CurrentMoveDelta = MoveDelta * RateScale; + float CurrentMoveDelta = MoveDelta; bool bOffsetInitialized = false; float MarkerTimeOffset = 0.f; @@ -5740,13 +5871,13 @@ void UAnimSequence::OnRawDataChanged() CompressedTrackOffsets.Empty(); CompressedScaleOffsets.Empty(); CompressedByteStream.Empty(); + CompressedSegments.Empty(); bUseRawDataOnly = true; RequestSyncAnimRecompression(false); //MDW - Once we have async anim ddc requests we should do this too //RequestDependentAnimRecompression(); } - #endif bool UAnimSequence::IsCompressedDataValid() const @@ -5755,6 +5886,29 @@ bool UAnimSequence::IsCompressedDataValid() const (TranslationCompressionFormat == ACF_Identity && RotationCompressionFormat == ACF_Identity && ScaleCompressionFormat == ACF_Identity); } +bool UAnimSequence::IsCurveCompressedDataValid() const +{ + if (CurveCompressionCodec == nullptr) + { + // No codec + return false; + } + + if (CompressedCurveByteStream.Num() == 0 && RawCurveData.FloatCurves.Num() != 0) + { + // No compressed data but we have raw data + if (!IsValidAdditive()) + { + return false; + } + + // Additive sequences can have raw curves that all end up being 0.0 (e.g. they 100% match the base sequence curves) + // in which case there will be no compressed curve data. + } + + return true; +} + /*----------------------------------------------------------------------------- AnimNotify& subclasses -----------------------------------------------------------------------------*/ diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp index f8edee061771..0fcc8fce0a5f 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp @@ -12,6 +12,7 @@ #include "UObject/FortniteMainBranchObjectVersion.h" DEFINE_LOG_CATEGORY(LogAnimMarkerSync); +CSV_DECLARE_CATEGORY_EXTERN(Animation); #define LOCTEXT_NAMESPACE "AnimSequenceBase" ///////////////////////////////////////////////////// @@ -720,9 +721,21 @@ void UAnimSequenceBase::RefreshParentAssetData() /** Add curve data to Instance at the time of CurrentTime **/ void UAnimSequenceBase::EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData) const { + CSV_SCOPED_TIMING_STAT(Animation, EvaluateCurveData); RawCurveData.EvaluateCurveData(OutCurve, CurrentTime); } +float UAnimSequenceBase::EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData) const +{ + const FFloatCurve* Curve = (const FFloatCurve*)RawCurveData.GetCurveData(CurveUID, ERawCurveTrackTypes::RCT_Float); + return Curve != nullptr ? Curve->Evaluate(CurrentTime) : 0.0f; +} + +bool UAnimSequenceBase::HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const +{ + return RawCurveData.GetCurveData(CurveUID) != nullptr; +} + void UAnimSequenceBase::Serialize(FArchive& Ar) { Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp index 63d8f0e0a8a3..b74d7e93f57f 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp @@ -16,6 +16,7 @@ #include "Animation/AnimCompress_Automatic.h" #include "Animation/AnimSet.h" #include "Animation/AnimationSettings.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "AnimationCompression.h" #include "Engine/SkeletalMeshSocket.h" #include "AnimEncoding.h" @@ -2233,3 +2234,79 @@ void FAnimationUtils::TallyErrorsFromPerturbation( TrackError.MaxErrorInScaleDueToScale = MaxErrorS_DueToR; } } + +#if WITH_EDITOR +static UAnimCurveCompressionSettings* DefaultCurveCompressionSettings = nullptr; + +UAnimCurveCompressionSettings* FAnimationUtils::GetDefaultAnimationCurveCompressionSettings() +{ + if (DefaultCurveCompressionSettings == nullptr) + { + FConfigSection* AnimDefaultObjectSettingsSection = GConfig->GetSectionPrivate(TEXT("Animation.DefaultObjectSettings"), false, true, GEngineIni); + const FConfigValue* Value = AnimDefaultObjectSettingsSection != nullptr ? AnimDefaultObjectSettingsSection->Find(TEXT("CurveCompressionSettings")) : nullptr; + + if (Value != nullptr) + { + const FString& CurveCompressionSettingsName = Value->GetValue(); + DefaultCurveCompressionSettings = LoadObject(nullptr, *CurveCompressionSettingsName); + } + + if (DefaultCurveCompressionSettings == nullptr) + { + UE_LOG(LogAnimationCompression, Fatal, TEXT("Couldn't find default curve compression settings under '[Animation.DefaultObjectSettings]'")); + } + + // Force load the default settings and all its dependencies just in case it hasn't happened yet + bool bLoadDependencies = false; + if (DefaultCurveCompressionSettings->HasAnyFlags(RF_NeedLoad)) + { + DefaultCurveCompressionSettings->GetLinker()->Preload(DefaultCurveCompressionSettings); + bLoadDependencies = true; + } + + if (DefaultCurveCompressionSettings->HasAnyFlags(RF_NeedPostLoad)) + { + DefaultCurveCompressionSettings->ConditionalPostLoad(); + bLoadDependencies = true; + } + + if (bLoadDependencies) + { + TArray ObjectReferences; + FReferenceFinder(ObjectReferences, nullptr, false, true, false, true).FindReferences(DefaultCurveCompressionSettings); + + for (UObject* Dependency : ObjectReferences) + { + if (Dependency->HasAnyFlags(RF_NeedLoad)) + { + Dependency->GetLinker()->Preload(Dependency); + } + + if (Dependency->HasAnyFlags(RF_NeedPostLoad)) + { + Dependency->ConditionalPostLoad(); + } + } + } + + DefaultCurveCompressionSettings->AddToRoot(); + } + + return DefaultCurveCompressionSettings; +} + +bool FAnimationUtils::CompressAnimCurves(UAnimSequence& AnimSeq) +{ + // Clear any previous data we might have even if we end up failing to compress + AnimSeq.CompressedCurveByteStream.Empty(); + AnimSeq.CurveCompressionCodec = nullptr; + + if (AnimSeq.CurveCompressionSettings == nullptr || !AnimSeq.CurveCompressionSettings->AreSettingsValid()) + { + return false; + } + + check(AnimSeq.CurveCompressionSettings->AreSettingsValid()); + return AnimSeq.CurveCompressionSettings->Compress(AnimSeq); +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp index 2459b81bdd56..7e734cafe7d7 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp @@ -1574,6 +1574,7 @@ void USkinnedMeshComponent::RefreshSlaveComponents() MeshCompPtr->UpdateChildTransforms(EUpdateTransformFlags::OnlyUpdateIfUsingSocket); MeshCompPtr->MarkRenderDynamicDataDirty(); + MeshCompPtr->MarkRenderTransformDirty(); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp b/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp index 2c1379f55d3e..0cccf3d99adc 100644 --- a/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp +++ b/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp @@ -1,7 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Curves/RichCurve.h" - +#include "Templates/Function.h" DECLARE_CYCLE_STAT(TEXT("RichCurve Eval"), STAT_RichCurve_Eval, STATGROUP_Engine); @@ -353,6 +353,25 @@ float FRichCurve::GetKeyValue(FKeyHandle KeyHandle) const return GetKey(KeyHandle).Value; } +bool FRichCurve::IsConstant(float Tolerance) const +{ + if (Keys.Num() <= 1) + { + return true; + } + + const FRichCurveKey& RefKey = Keys[0]; + for (const FRichCurveKey& Key : Keys) + { + if (!FMath::IsNearlyEqual(Key.Value, RefKey.Value, Tolerance)) + { + return false; + } + } + + return true; +} + TPair FRichCurve::GetKeyTimeValuePair(FKeyHandle KeyHandle) const { if (!IsKeyHandleValid(KeyHandle)) @@ -785,7 +804,7 @@ void FRichCurve::RemoveRedundantKeys(float Tolerance, float FirstKeyTime, float } /** Util to find float value on bezier defined by 4 control points */ -static float BezierInterp(float P0, float P1, float P2, float P3, float Alpha) +FORCEINLINE_DEBUGGABLE static float BezierInterp(float P0, float P1, float P2, float P3, float Alpha) { const float P01 = FMath::Lerp(P0, P1, Alpha); const float P12 = FMath::Lerp(P1, P2, Alpha); @@ -1084,3 +1103,801 @@ bool FRichCurve::operator==(const FRichCurve& Curve) const return true; } + +static ERichCurveCompressionFormat FindRichCurveCompressionFormat(const FRichCurve& Curve) +{ + if (Curve.Keys.Num() == 0) + { + return RCCF_Empty; + } + + if (Curve.IsConstant()) + { + return RCCF_Constant; + } + + const FRichCurveKey& RefKey = Curve.Keys[0]; + for (const FRichCurveKey& Key : Curve.Keys) + { + if (Key.InterpMode != RefKey.InterpMode) + { + return RCCF_Mixed; + } + } + + switch (RefKey.InterpMode) + { + case RCIM_Constant: + case RCIM_None: + default: + return RCCF_Constant; + case RCIM_Linear: + return RCCF_Linear; + case RCIM_Cubic: + return RCCF_Cubic; + } +} + +static ERichCurveKeyTimeCompressionFormat FindRichCurveKeyFormat(const FRichCurve& Curve, float ErrorThreshold, float SampleRate, ERichCurveCompressionFormat CompressionFormat) +{ + const int32 NumKeys = Curve.Keys.Num(); + if (NumKeys == 0 || CompressionFormat == RCCF_Constant || CompressionFormat == RCCF_Empty || ErrorThreshold <= 0.0f || SampleRate <= 0.0f) + { + return RCKTCF_float32; + } + + auto EvalForTwoKeys = [](const FRichCurveKey& Key1, float KeyTime1, const FRichCurveKey& Key2, float KeyTime2, float InTime) + { + const float Diff = KeyTime2 - KeyTime1; + + if (Diff > 0.f && Key1.InterpMode != RCIM_Constant) + { + const float Alpha = (InTime - KeyTime1) / Diff; + const float P0 = Key1.Value; + const float P3 = Key2.Value; + + if (Key1.InterpMode == RCIM_Linear) + { + return FMath::Lerp(P0, P3, Alpha); + } + else + { + const float OneThird = 1.0f / 3.0f; + const float P1 = P0 + (Key1.LeaveTangent * Diff * OneThird); + const float P2 = P3 - (Key2.ArriveTangent * Diff * OneThird); + + return BezierInterp(P0, P1, P2, P3, Alpha); + } + } + else + { + return Key1.Value; + } + }; + + auto DecayTime = [](const FRichCurveKey& Key, float MinTime, float DeltaTime, float InvDeltaTime, float QuantizationScale, float InvQuantizationScale) + { + // 0.0f -> 0, 1.0f -> 255 for 8 bits + const float NormalizedTime = FMath::Clamp((Key.Time - MinTime) * InvDeltaTime, 0.0f, 1.0f); + const float QuantizedTime = FMath::RoundHalfFromZero(NormalizedTime * QuantizationScale); + const float LossyNormalizedTime = QuantizedTime * InvQuantizationScale; + return (LossyNormalizedTime * DeltaTime) + MinTime; + }; + + const float MinTime = Curve.Keys[0].Time; + const float MaxTime = Curve.Keys.Last().Time; + const float DeltaTime = MaxTime - MinTime; + const float InvDeltaTime = 1.0f / DeltaTime; + const float SampleRateIncrement = 1.0f / SampleRate; + + // This is only acceptable if the maximum error is within a reasonable value + bool bFitsOn16Bits = true; + + int32 CurrentLossyKey = 0; + int32 CurrentRefKey = 0; + float CurrentTime = MinTime; + while (CurrentTime <= MaxTime && bFitsOn16Bits) + { + if (CurrentTime > Curve.Keys[CurrentRefKey + 1].Time) + { + CurrentRefKey++; + + if (CurrentRefKey >= NumKeys) + { + break; + } + } + + float LossyTime1_16; + float LossyTime2_16 = DecayTime(Curve.Keys[CurrentLossyKey + 1], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + if (CurrentTime > LossyTime2_16) + { + CurrentLossyKey++; + + if (CurrentLossyKey >= NumKeys) + { + break; + } + + LossyTime1_16 = LossyTime2_16; + LossyTime2_16 = DecayTime(Curve.Keys[CurrentLossyKey + 1], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + } + else + { + LossyTime1_16 = DecayTime(Curve.Keys[CurrentLossyKey], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + } + + const float Result_16 = EvalForTwoKeys(Curve.Keys[CurrentLossyKey], LossyTime1_16, Curve.Keys[CurrentLossyKey + 1], LossyTime2_16, CurrentTime); + const float Result_Ref = ::EvalForTwoKeys(Curve.Keys[CurrentRefKey], Curve.Keys[CurrentRefKey + 1], CurrentTime); + + const float Error_16 = FMath::Abs(Result_Ref - Result_16); + + bFitsOn16Bits &= Error_16 <= ErrorThreshold; + + CurrentTime += SampleRateIncrement; + } + + // In order to normalize time values, we need to store the MinTime and the DeltaTime with full precision + // This means we need 8 bytes of overhead + // If the number of keys is too small, the overhead is larger or equal to the space we save and isn't + // worth it. + + // For 8 bits, the formula is: 8 + (N * sizeof(uint8)) + // With 8 bits, 2 keys need 8 bytes with full precision and 10 bytes packed. No savings, not worth using. + // With 8 bits, 3 keys need 12 bytes with full precision and 11 bytes packed. We save 1 byte, worth using. + // For 16 bits, the formula is: 8 + (N * sizeof(uint16)) + // With 16 bits, 6 keys need 20 bytes with full precision and 20 bytes packed. No savings, not worth using. + // With 16 bits, 7 keys need 24 bytes with full precision and 22 bytes packed. We save 2 bytes, worth using. + // Alignment and padding must also be taken into account + + // Note: Support for storing key time on 8 bits was attempted but it was rarely selected and wasn't worth the complexity + + int32 SizeInterpMode = 0; + if (CompressionFormat == RCCF_Mixed) + { + SizeInterpMode += NumKeys * sizeof(uint8); + } + + const int32 SizeUInt16 = Align(Align(SizeInterpMode, sizeof(uint16)) + (NumKeys * sizeof(uint16)), sizeof(float)) + (2 * sizeof(float)); + const int32 SizeFloat32 = Align(SizeInterpMode, sizeof(float)) + NumKeys * sizeof(float); + + if (bFitsOn16Bits && SizeUInt16 < SizeFloat32) + { + return RCKTCF_uint16; + } + else + { + return RCKTCF_float32; + } +} + +void FRichCurve::CompressCurve(FCompressedRichCurve& OutCurve, float ErrorThreshold, float SampleRate) const +{ + ERichCurveCompressionFormat CompressionFormat = FindRichCurveCompressionFormat(*this); + OutCurve.CompressionFormat = CompressionFormat; + + ERichCurveKeyTimeCompressionFormat KeyFormat = FindRichCurveKeyFormat(*this, ErrorThreshold, SampleRate, CompressionFormat); + OutCurve.KeyTimeCompressionFormat = KeyFormat; + + OutCurve.PreInfinityExtrap = PreInfinityExtrap; + OutCurve.PostInfinityExtrap = PostInfinityExtrap; + + if (CompressionFormat == RCCF_Empty) + { + OutCurve.ConstantValueNumKeys.ConstantValue = DefaultValue; + OutCurve.CompressedKeys.Empty(); + } + else if (CompressionFormat == RCCF_Constant) + { + OutCurve.ConstantValueNumKeys.ConstantValue = Keys[0].Value; + OutCurve.CompressedKeys.Empty(); + } + else + { + int32 PackedDataSize = 0; + + // If we are mixed, we need to store the interp mode for every key, this data comes first following the header + // Next comes the quantized time values followed by the normalization range + // And the values/tangents follow last + + if (CompressionFormat == RCCF_Mixed) + { + PackedDataSize += Keys.Num() * sizeof(uint8); + } + + if (KeyFormat == RCKTCF_uint16) + { + PackedDataSize = Align(PackedDataSize, sizeof(uint16)); + PackedDataSize += Keys.Num() * sizeof(uint16); + PackedDataSize = Align(PackedDataSize, sizeof(float)); + PackedDataSize += 2 * sizeof(float); + } + else + { + check(KeyFormat == RCKTCF_float32); + PackedDataSize = Align(PackedDataSize, sizeof(float)); + PackedDataSize += Keys.Num() * sizeof(float); + } + + PackedDataSize += Keys.Num() * sizeof(float); // Key values + + // Key tangents + if (CompressionFormat == RCCF_Cubic) + { + PackedDataSize += Keys.Num() * 2 * sizeof(float); + } + else if (CompressionFormat == RCCF_Mixed) + { + for (const FRichCurveKey& Key : Keys) + { + if (Key.InterpMode == RCIM_Cubic) + { + PackedDataSize += 2 * sizeof(float); + } + } + } + + OutCurve.CompressedKeys.Empty(PackedDataSize); + OutCurve.CompressedKeys.AddUninitialized(PackedDataSize); + + int32 WriteOffset = 0; + uint8* BasePtr = OutCurve.CompressedKeys.GetData(); + + OutCurve.ConstantValueNumKeys.NumKeys = Keys.Num(); + + // Key interp modes + if (CompressionFormat == RCCF_Mixed) + { + uint8* InterpModes = BasePtr + WriteOffset; + WriteOffset += Keys.Num() * sizeof(uint8); + + for (const FRichCurveKey& Key : Keys) + { + if (Key.InterpMode == RCIM_Linear) + { + *InterpModes++ = (uint8)RCCF_Linear; + } + else if (Key.InterpMode == RCIM_Cubic) + { + *InterpModes++ = (uint8)RCCF_Cubic; + } + else + { + *InterpModes++ = (uint8)RCCF_Constant; + } + } + } + + // Key times + if (KeyFormat == RCKTCF_uint16) + { + const float MinTime = Keys[0].Time; + const float MaxTime = Keys.Last().Time; + const float DeltaTime = MaxTime - MinTime; + const float InvDeltaTime = 1.0f / DeltaTime; + + const int32 KeySize = sizeof(uint16); + + if (KeyFormat == RCKTCF_uint16) + { + WriteOffset = Align(WriteOffset, sizeof(uint16)); + } + + uint8* KeyTimes8 = BasePtr + WriteOffset; + uint16* KeyTimes16 = reinterpret_cast(KeyTimes8); + WriteOffset += Keys.Num() * KeySize; + + for (const FRichCurveKey& Key : Keys) + { + const float NormalizedTime = FMath::Clamp((Key.Time - MinTime) * InvDeltaTime, 0.0f, 1.0f); + const uint16 QuantizedTime = (uint16)FMath::RoundHalfFromZero(NormalizedTime * 65535.0f); + *KeyTimes16++ = QuantizedTime; + } + + WriteOffset = Align(WriteOffset, sizeof(float)); + float* RangeData = reinterpret_cast(BasePtr + WriteOffset); + WriteOffset += 2 * sizeof(float); + + RangeData[0] = MinTime; + RangeData[1] = DeltaTime; + } + else + { + WriteOffset = Align(WriteOffset, sizeof(float)); + float* KeyTimes = reinterpret_cast(BasePtr + WriteOffset); + WriteOffset += Keys.Num() * sizeof(float); + + for (const FRichCurveKey& Key : Keys) + { + *KeyTimes++ = Key.Time; + } + } + + // Key values and tangents + float* KeyData = reinterpret_cast(BasePtr + WriteOffset); + for (const FRichCurveKey& Key : Keys) + { + *KeyData++ = Key.Value; + + if (Key.InterpMode == RCIM_Cubic) + { + check(CompressionFormat == RCCF_Cubic || CompressionFormat == RCCF_Mixed); + *KeyData++ = Key.ArriveTangent; + *KeyData++ = Key.LeaveTangent; + } + } + + check(((uint8*)KeyData - BasePtr) == PackedDataSize); + } +} + +struct Quantized16BitKeyTimeAdapter +{ + static constexpr float QuantizationScale = 1.0f / 65535.0f; + static constexpr int32 KeySize = sizeof(uint16); + static constexpr int32 RangeDataSize = 2 * sizeof(float); + + using KeyTimeType = uint16; + + const uint16* KeyTimes; + float MinTime; + float DeltaTime; + int32 KeyDataOffset; + + Quantized16BitKeyTimeAdapter(const uint8* BasePtr, int32 KeyTimesOffset, int32 NumKeys) + { + const int32 RangeDataOffset = Align(KeyTimesOffset + (NumKeys * sizeof(uint16)), sizeof(float)); + KeyDataOffset = RangeDataOffset + RangeDataSize; + const float* RangeData = reinterpret_cast(BasePtr + RangeDataOffset); + + KeyTimes = reinterpret_cast(BasePtr + KeyTimesOffset); + MinTime = RangeData[0]; + DeltaTime = RangeData[1]; + } + + float GetTime(int32 KeyIndex) const + { + const float KeyNormalizedTime = KeyTimes[KeyIndex] * QuantizationScale; + return (KeyNormalizedTime * DeltaTime) + MinTime; + }; +}; + +struct Float32BitKeyTimeAdapter +{ + static constexpr int32 KeySize = sizeof(float); + static constexpr int32 RangeDataSize = 0; + + using KeyTimeType = float; + + const float* KeyTimes; + int32 KeyDataOffset; + + Float32BitKeyTimeAdapter(const uint8* BasePtr, int32 KeyTimesOffset, int32 NumKeys) + { + KeyTimes = reinterpret_cast(BasePtr + KeyTimesOffset); + KeyDataOffset = Align(KeyTimesOffset + (NumKeys * sizeof(float)), sizeof(float)); + } + + constexpr float GetTime(int32 KeyIndex) const + { + return KeyTimes[KeyIndex]; + }; +}; + +using KeyDataHandle = int32; + +template +struct UniformKeyDataAdapter +{ + const float* KeyData; + + template + + constexpr UniformKeyDataAdapter(const uint8* BasePtr, const KeyTimeAdapterType& KeyTimeAdapter) + : KeyData(reinterpret_cast(BasePtr + KeyTimeAdapter.KeyDataOffset)) + {} + + constexpr KeyDataHandle GetKeyDataHandle(int32 KeyIndexToQuery) const + { + return Format == RCCF_Cubic ? (KeyIndexToQuery * 3) : KeyIndexToQuery; + }; + + constexpr float GetKeyValue(KeyDataHandle Handle) const + { + return KeyData[Handle]; + } + + constexpr float GetKeyArriveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 1]; + } + + constexpr float GetKeyLeaveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 2]; + } + + constexpr ERichCurveCompressionFormat GetKeyInterpMode(int32 KeyIndex) const + { + return Format; + } +}; + +struct MixedKeyDataAdapter +{ + const uint8* InterpModes; + const float* KeyData; + + template + MixedKeyDataAdapter(const uint8* BasePtr, int32 InterpModesOffset, const KeyTimeAdapterType& KeyTimeAdapter) + { + InterpModes = BasePtr + InterpModesOffset; + KeyData = reinterpret_cast(BasePtr + KeyTimeAdapter.KeyDataOffset); + } + + KeyDataHandle GetKeyDataHandle(int32 KeyIndexToQuery) const + { + int32 Offset = 0; + for (int32 KeyIndex = 0; KeyIndex < KeyIndexToQuery; ++KeyIndex) + { + Offset += InterpModes[KeyIndex] == RCCF_Cubic ? 3 : 1; + } + + return Offset; + }; + + constexpr float GetKeyValue(KeyDataHandle Handle) const + { + return KeyData[Handle]; + } + + constexpr float GetKeyArriveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 1]; + } + + constexpr float GetKeyLeaveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 2]; + } + + constexpr ERichCurveCompressionFormat GetKeyInterpMode(int32 KeyIndex) const + { + return (ERichCurveCompressionFormat)InterpModes[KeyIndex]; + } +}; + +static void CycleTime(float MinTime, float MaxTime, float& InTime, int& CycleCount) +{ + float InitTime = InTime; + float Duration = MaxTime - MinTime; + + if (InTime > MaxTime) + { + CycleCount = FMath::FloorToInt((MaxTime-InTime)/Duration); + InTime = InTime + Duration*CycleCount; + } + else if (InTime < MinTime) + { + CycleCount = FMath::FloorToInt((InTime-MinTime)/Duration); + InTime = InTime - Duration*CycleCount; + } + + if (InTime == MaxTime && InitTime < MinTime) + { + InTime = MinTime; + } + + if (InTime == MinTime && InitTime > MaxTime) + { + InTime = MaxTime; + } + + CycleCount = FMath::Abs(CycleCount); +} + +template +static float RemapTimeValue(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, int32 NumKeys, ERichCurveExtrapolation InfinityExtrap, int32 KeyIndex0, int32 KeyIndex1, float& CycleValueOffset) +{ + // For Pre-infinity, key0 and key1 are the actual key 0 and key 1 + // For Post-infinity, key0 and key1 are the last and second to last key + const float MinTime = KeyTimeAdapter.GetTime(0); + const float MaxTime = KeyTimeAdapter.GetTime(NumKeys - 1); + + int CycleCount = 0; + CycleTime(MinTime, MaxTime, InTime, CycleCount); + + if (InfinityExtrap == RCCE_CycleWithOffset) + { + const KeyDataHandle ValueHandle0 = KeyDataAdapter.GetKeyDataHandle(KeyIndex0); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(ValueHandle0); + const KeyDataHandle ValueHandle1 = KeyDataAdapter.GetKeyDataHandle(KeyIndex1); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(ValueHandle1); + + const float DV = KeyValue0 - KeyValue1; + CycleValueOffset = DV * CycleCount; + } + else if (InfinityExtrap == RCCE_Oscillate) + { + if (CycleCount % 2 == 1) + { + InTime = MinTime + (MaxTime - InTime); + } + } + + return InTime; +} + +template +static float InterpEvalExtrapolate(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, ERichCurveExtrapolation InfinityExtrap, int32 KeyIndex0, int32 KeyIndex1, float KeyTime0) +{ + // For Pre-infinity, key0 and key1 are the actual key 0 and key 1 + // For Post-infinity, key0 and key1 are the last and second to last key + const KeyDataHandle ValueHandle0 = KeyDataAdapter.GetKeyDataHandle(KeyIndex0); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(ValueHandle0); + + if (InfinityExtrap == RCCE_Linear) + { + const float KeyTime1 = KeyTimeAdapter.GetTime(KeyIndex1); + const float DT = KeyTime1 - KeyTime0; + + if (FMath::IsNearlyZero(DT)) + { + return KeyValue0; + } + else + { + const KeyDataHandle ValueHandle1 = KeyDataAdapter.GetKeyDataHandle(KeyIndex1); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(ValueHandle1); + const float DV = KeyValue1 - KeyValue0; + const float Slope = DV / DT; + + return Slope * (InTime - KeyTime0) + KeyValue0; + } + } + else + { + // Otherwise if constant or in a cycle or oscillate, always use the first key value + return KeyValue0; + } +} + +// Each template permutation is only called from one place, force inline it to avoid issues +template +FORCEINLINE_DEBUGGABLE static float InterpEval(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, int32 NumKeys, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap) +{ + float CycleValueOffset = 0.0; + + const float FirstKeyTime = KeyTimeAdapter.GetTime(0); + if (InTime <= FirstKeyTime) + { + if (PreInfinityExtrap != RCCE_Linear && PreInfinityExtrap != RCCE_Constant) + { + InTime = RemapTimeValue(InTime, KeyTimeAdapter, KeyDataAdapter, NumKeys, PreInfinityExtrap, 0, NumKeys - 1, CycleValueOffset); + } + else + { + return InterpEvalExtrapolate(InTime, KeyTimeAdapter, KeyDataAdapter, PreInfinityExtrap, 0, 1, FirstKeyTime); + } + } + + const float LastKeyTime = KeyTimeAdapter.GetTime(NumKeys - 1); + if (InTime >= LastKeyTime) + { + if (PostInfinityExtrap != RCCE_Linear && PostInfinityExtrap != RCCE_Constant) + { + InTime = RemapTimeValue(InTime, KeyTimeAdapter, KeyDataAdapter, NumKeys, PostInfinityExtrap, NumKeys - 1, 0, CycleValueOffset); + } + else + { + return InterpEvalExtrapolate(InTime, KeyTimeAdapter, KeyDataAdapter, PostInfinityExtrap, NumKeys - 1, NumKeys - 2, LastKeyTime); + } + } + + // perform a lower bound to get the second of the interpolation nodes + int32 First = 1; + int32 Last = NumKeys - 1; + int32 Count = Last - First; + + while (Count > 0) + { + const int32 Step = Count / 2; + const int32 Middle = First + Step; + + // TODO: Can we do the search with integers? In order to do so, we need to Floorf(..) the key times + const float KeyTime = KeyTimeAdapter.GetTime(Middle); + if (InTime >= KeyTime) + { + First = Middle + 1; + Count -= Step + 1; + } + else + { + Count = Step; + } + } + + const float KeyTime0 = KeyTimeAdapter.GetTime(First - 1); + const float KeyTime1 = KeyTimeAdapter.GetTime(First); + const float Diff = KeyTime1 - KeyTime0; + + const KeyDataHandle KeyValueHandle0 = KeyDataAdapter.GetKeyDataHandle(First - 1); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(KeyValueHandle0); + + // const value here allows the code to be stripped statically if the data is uniform + // which it is most of the time + const ERichCurveCompressionFormat KeyInterpMode0 = KeyDataAdapter.GetKeyInterpMode(First - 1); + float InterpolatedValue; + if (Diff > 0.0f && KeyInterpMode0 != RCCF_Constant) + { + const KeyDataHandle KeyValueHandle1 = KeyDataAdapter.GetKeyDataHandle(First); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(KeyValueHandle1); + + const float Alpha = (InTime - KeyTime0) / Diff; + const float P0 = KeyValue0; + const float P3 = KeyValue1; + + if (KeyInterpMode0 == RCCF_Linear) + { + InterpolatedValue = FMath::Lerp(P0, P3, Alpha); + } + else + { + const float OneThird = 1.0f / 3.0f; + const float ScaledDiff = Diff * OneThird; + const float KeyLeaveTangent0 = KeyDataAdapter.GetKeyLeaveTangent(KeyValueHandle0); + const float KeyArriveTangent1 = KeyDataAdapter.GetKeyArriveTangent(KeyValueHandle1); + const float P1 = P0 + (KeyLeaveTangent0 * ScaledDiff); + const float P2 = P3 - (KeyArriveTangent1 * ScaledDiff); + + InterpolatedValue = BezierInterp(P0, P1, P2, P3, Alpha); + } + } + else + { + InterpolatedValue = KeyValue0; + } + + return InterpolatedValue + CycleValueOffset; +} + +static TFunction InterpEvalMap[5][2] +{ + // RCCF_Empty + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + return ConstantValueNumKeys.ConstantValue == MAX_flt ? InDefaultValue : ConstantValueNumKeys.ConstantValue; + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + return ConstantValueNumKeys.ConstantValue == MAX_flt ? InDefaultValue : ConstantValueNumKeys.ConstantValue; + }, + }, + // RCCF_Constant + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) { return ConstantValueNumKeys.ConstantValue; }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) { return ConstantValueNumKeys.ConstantValue; }, + }, + // RCCF_Linear + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, + // RCCF_Cubic + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, + // RCCF_Mixed + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 InterpModesOffset = 0; + const int32 KeyTimesOffset = InterpModesOffset + Align(ConstantValueNumKeys.NumKeys * sizeof(uint8), sizeof(uint16)); + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + MixedKeyDataAdapter KeyDataAdapter(CompressedKeys, InterpModesOffset, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 InterpModesOffset = 0; + const int32 KeyTimesOffset = InterpModesOffset + Align(ConstantValueNumKeys.NumKeys * sizeof(uint8), sizeof(float)); + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + MixedKeyDataAdapter KeyDataAdapter(CompressedKeys, InterpModesOffset, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, +}; + +float FCompressedRichCurve::Eval(float InTime, float InDefaultValue) const +{ + SCOPE_CYCLE_COUNTER(STAT_RichCurve_Eval); + + // Dynamic dispatch into a template optimized code path + const float Value = InterpEvalMap[CompressionFormat][KeyTimeCompressionFormat](PreInfinityExtrap, PostInfinityExtrap, ConstantValueNumKeys, CompressedKeys.GetData(), InTime, InDefaultValue); + return Value; +} + +float FCompressedRichCurve::StaticEval(ERichCurveCompressionFormat CompressionFormat, ERichCurveKeyTimeCompressionFormat KeyTimeCompressionFormat, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) +{ + SCOPE_CYCLE_COUNTER(STAT_RichCurve_Eval); + + // Dynamic dispatch into a template optimized code path + const float Value = InterpEvalMap[CompressionFormat][KeyTimeCompressionFormat](PreInfinityExtrap, PostInfinityExtrap, ConstantValueNumKeys, CompressedKeys, InTime, InDefaultValue); + return Value; +} + +bool FCompressedRichCurve::Serialize(FArchive& Ar) +{ + Ar << CompressionFormat; + Ar << KeyTimeCompressionFormat; + Ar << PreInfinityExtrap; + Ar << PostInfinityExtrap; + + int32 NumKeysOrConstant = ConstantValueNumKeys.NumKeys; + Ar << NumKeysOrConstant; + ConstantValueNumKeys.NumKeys = NumKeysOrConstant; + + if (Ar.IsLoading()) + { + int32 NumBytes; + Ar << NumBytes; + + CompressedKeys.Empty(NumBytes); + CompressedKeys.AddUninitialized(NumBytes); + Ar.Serialize(CompressedKeys.GetData(), NumBytes); + } + else + { + int32 NumBytes = CompressedKeys.Num(); + Ar << NumBytes; + Ar.Serialize(CompressedKeys.GetData(), NumBytes); + } + + return true; +} + +bool FCompressedRichCurve::operator==(const FCompressedRichCurve& Other) const +{ + return CompressionFormat == Other.CompressionFormat + && KeyTimeCompressionFormat == Other.KeyTimeCompressionFormat + && PreInfinityExtrap == Other.PreInfinityExtrap + && PostInfinityExtrap == Other.PostInfinityExtrap + && ConstantValueNumKeys.NumKeys == Other.ConstantValueNumKeys.NumKeys + && CompressedKeys == Other.CompressedKeys; +} diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp index a6c40db66cf6..9303ccf87908 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp @@ -3079,6 +3079,7 @@ void USkeletalMesh::GetMappableNodeData(TArray& OutNames, TArrayCopyConstraintParamsFrom(&OutConstraintsetup->DefaultInstance); ConInst->ConstraintIndex = ConstraintIdx; // Set the ConstraintIndex property in the ConstraintInstance. #if WITH_EDITOR - if(GetWorld()->IsGameWorld()) + UWorld* World = GetWorld(); + if(World && World->IsGameWorld()) { //In the editor we may be currently editing the physics asset, so make sure to use the default profile OutConstraintsetup->ApplyConstraintProfile(NAME_None, *ConInst, /*bDefaultIfNotFound=*/true); diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp index c4d204b13411..7226c85539db 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp @@ -155,7 +155,7 @@ void FSkeletalMeshObjectGPUSkin::InitResources(USkinnedMeshComponent* InMeshComp FSkeletalMeshObjectLOD& SkelLOD = LODs[LODIndex]; // Skip LODs that have their render data stripped - if (SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) + if (SkelLOD.SkelMeshRenderData && SkelLOD.SkelMeshRenderData->LODRenderData.IsValidIndex(LODIndex) && SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) { const FSkelMeshObjectLODInfo& MeshLODInfo = LODInfo[LODIndex]; @@ -177,7 +177,7 @@ void FSkeletalMeshObjectGPUSkin::ReleaseResources() FSkeletalMeshObjectLOD& SkelLOD = LODs[LODIndex]; // Skip LODs that have their render data stripped - if (SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) + if (SkelLOD.SkelMeshRenderData && SkelLOD.SkelMeshRenderData->LODRenderData.IsValidIndex(LODIndex) && SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) { SkelLOD.ReleaseResources(); } diff --git a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp index 448b57d84fad..d146dd31d7fe 100644 --- a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp +++ b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp @@ -5341,7 +5341,7 @@ bool UEngine::HandleListAnimsCommand(const TCHAR* Cmd, FOutputDevice& Ar) NumKeys = AnimSeq->GetRawNumberOfFrames(); SequenceLength = AnimSeq->GetPlayLength(); RateScale = AnimSeq->RateScale; - NumCurves = AnimSeq->CompressedCurveData.FloatCurves.Num(); + NumCurves = AnimSeq->RawCurveData.FloatCurves.Num(); TranslationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->TranslationCompressionFormat); RotationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->RotationCompressionFormat); diff --git a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h index df634d5f5f6e..6b378aa0def6 100644 --- a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h +++ b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h @@ -638,6 +638,12 @@ struct FAnimKeyHelper return (NumKeys > 1) ? Length / (float)(NumKeys - 1) : MINIMUM_ANIMATION_LENGTH; } + // Returns the FPS for the given length and number of keys + float KeysPerSecond() const + { + return NumKeys > 0 ? (float(NumKeys - 1) / Length) : 0.0f; + } + int32 LastKey() const { return (NumKeys > 1) ? NumKeys - 1 : 0; diff --git a/Engine/Source/Runtime/Engine/Public/AnimationUtils.h b/Engine/Source/Runtime/Engine/Public/AnimationUtils.h index c526f43ce01a..fdd2a39978c5 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimationUtils.h +++ b/Engine/Source/Runtime/Engine/Public/AnimationUtils.h @@ -276,4 +276,16 @@ public: */ ENGINE_API static UAnimCompress* GetDefaultAnimationCompressionAlgorithm(); +#if WITH_EDITOR + /** Returns the default animation curve compression settings, can never by null. */ + ENGINE_API static UAnimCurveCompressionSettings* GetDefaultAnimationCurveCompressionSettings(); + + /** + * Compresses the animation curves within a sequence with the chosen settings. + * Note: This modifies the sequence. + * + * @return Returns true on success, false it we fail to compress + */ + ENGINE_API static bool CompressAnimCurves(UAnimSequence& AnimSeq); +#endif }; diff --git a/Engine/Source/ThirdParty/SPL/SPL.Build.cs b/Engine/Source/ThirdParty/SPL/SPL.Build.cs deleted file mode 100644 index 2dcdb4274afa..000000000000 --- a/Engine/Source/ThirdParty/SPL/SPL.Build.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -using UnrealBuildTool; - -public class SPL : ModuleRules -{ - public SPL(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - string SPLDirectory = Target.UEThirdPartySourceDirectory + "NotForLicensees/SPL/"; - string SPLLibPath = SPLDirectory; - PublicIncludePaths.Add(SPLDirectory + "Public/Include"); - PublicIncludePaths.Add(SPLDirectory + "Public/json"); - - if (Target.Platform == UnrealTargetPlatform.Win64) - { - SPLLibPath = SPLLibPath + "lib/win64/vs" + Target.WindowsPlatform.GetVisualStudioCompilerVersionName() + "/"; - PublicLibraryPaths.Add(SPLLibPath ); - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add("SPLd.lib"); - } - else - { - PublicAdditionalLibraries.Add("SPL.lib"); - } - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string LibPath = SPLDirectory + "lib/Mac/"; - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add(LibPath + "libspl_debug.a"); - } - else - { - PublicAdditionalLibraries.Add(LibPath + "libspl.a"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/SPL/SPL.tps b/Engine/Source/ThirdParty/SPL/SPL.tps deleted file mode 100644 index 780670f5b662..000000000000 --- a/Engine/Source/ThirdParty/SPL/SPL.tps +++ /dev/null @@ -1,16 +0,0 @@ - - - - /Engine/Source/ThirdParty/NotForLicensees/SPL/ - - - - - - - -Redirect: ../Simplygon/Simplygon.tps -Notes: This was added by the Simplygon partners as part of the Simplygon integration. - - - \ No newline at end of file diff --git a/Engine/Source/ThirdParty/SSF/SSF.Build.cs b/Engine/Source/ThirdParty/SSF/SSF.Build.cs deleted file mode 100644 index a3b2b286a434..000000000000 --- a/Engine/Source/ThirdParty/SSF/SSF.Build.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -using UnrealBuildTool; - -public class SSF : ModuleRules -{ - public SSF(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - string SSFDirectory = Target.UEThirdPartySourceDirectory + "NotForLicensees/SSF/"; - string SSFLibPath = SSFDirectory; - PublicIncludePaths.Add(SSFDirectory + "Public"); - - if (Target.Platform == UnrealTargetPlatform.Win64) - { - SSFLibPath = SSFLibPath + "lib/win64/vs" + Target.WindowsPlatform.GetVisualStudioCompilerVersionName() + "/"; - PublicLibraryPaths.Add(SSFLibPath); - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add("SSFd.lib"); - } - else - { - PublicAdditionalLibraries.Add("SSF.lib"); - } - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string LibPath = SSFDirectory + "lib/Mac/"; - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add(LibPath + "libssf_debug.a"); - } - else - { - PublicAdditionalLibraries.Add(LibPath + "libssf.a"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/SSF/SSF.tps b/Engine/Source/ThirdParty/SSF/SSF.tps deleted file mode 100644 index e1bba6c5c233..000000000000 --- a/Engine/Source/ThirdParty/SSF/SSF.tps +++ /dev/null @@ -1,7 +0,0 @@ - - - -Redirect: ../NotForLicensees/Simplygon/Simplygon.tps -Notes: This was added by the Simplygon partners as part of the Simplygon integration. - - diff --git a/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs b/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs deleted file mode 100644 index a3621522fc29..000000000000 --- a/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.IO; - -public class Simplygon : ModuleRules -{ - public Simplygon(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - PublicDefinitions.Add("SGDEPRECATED_OFF=1"); - - //@third party code BEGIN SIMPLYGON - //Change the path to make it easier to update Simplygon - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/"; - //@third party code END SIMPLYGON - PublicIncludePaths.Add(SimplygonPath + "Inc"); - - // Simplygon depends on D3DX9. - if ((Target.Platform == UnrealTargetPlatform.Win64) || - (Target.Platform == UnrealTargetPlatform.Win32)) - { - if (Target.Platform == UnrealTargetPlatform.Win64) - { - PublicLibraryPaths.Add( Target.UEThirdPartySourceDirectory + "Windows/DirectX/Lib/x64" ); - } - else - { - PublicLibraryPaths.Add( Target.UEThirdPartySourceDirectory + "Windows/DirectX/Lib/x86" ); - } - PublicAdditionalLibraries.Add( - (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) ? "d3dx9d.lib" : "d3dx9.lib" - ); - - // Simplygon requires GetProcessMemoryInfo exported by psapi.dll. http://msdn.microsoft.com/en-us/library/windows/desktop/ms683219(v=vs.85).aspx - PublicAdditionalLibraries.Add("psapi.lib"); - - PublicDelayLoadDLLs.Add("d3dcompiler_47.dll"); - string EngineDir = Path.GetFullPath(Target.RelativeEnginePath); - if (Target.Platform == UnrealTargetPlatform.Win32) - { - RuntimeDependencies.Add(EngineDir + "Binaries/ThirdParty/Windows/DirectX/x86/d3dcompiler_47.dll"); - } - else if (Target.Platform == UnrealTargetPlatform.Win64) - { - RuntimeDependencies.Add(EngineDir + "Binaries/ThirdParty/Windows/DirectX/x64/d3dcompiler_47.dll"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/Simplygon/Simplygon.tps b/Engine/Source/ThirdParty/Simplygon/Simplygon.tps deleted file mode 100644 index 5af4f816b19e..000000000000 --- a/Engine/Source/ThirdParty/Simplygon/Simplygon.tps +++ /dev/null @@ -1,7 +0,0 @@ - - - -Redirect: ../NotForLicensees/Simplygon/Simplygon.tps -Notes: We don't distribute the Simplygon SDK, but our TPS info for internal usage is given at the above link. - -