Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObject/Private/MuCO/CustomizableObjectInstance.cpp
2025-05-18 13:04:45 +08:00

7615 lines
264 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCO/CustomizableObjectInstance.h"
#include "Algo/Find.h"
#include "Algo/MaxElement.h"
#include "Animation/AnimClassInterface.h"
#include "Animation/AnimBlueprintGeneratedClass.h"
#include "Animation/AnimInstance.h"
#include "Animation/Skeleton.h"
#include "BoneControllers/AnimNode_RigidBody.h"
#include "ClothConfig.h"
#include "ClothingAsset.h"
#include "MuCO/CustomizableObjectInstanceUsagePrivate.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Engine/SkeletalMeshLODSettings.h"
#include "MaterialDomain.h"
#include "MutableStreamRequest.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Misc/Optional.h"
#include "Modules/ModuleManager.h"
#include "Tasks/Task.h"
#include "MuCO/CustomizableObjectSystemPrivate.h"
#include "MuCO/CustomizableObjectSkeletalMesh.h"
#include "MuCO/CustomizableObjectInstancePrivate.h"
#include "MuCO/CustomizableObjectExtension.h"
#include "MuCO/CustomizableObjectMipDataProvider.h"
#include "MuCO/CustomizableObjectPrivate.h"
#include "MuCO/CustomizableObjectInstanceUsage.h"
#include "MuCO/CustomizableObjectInstanceAssetUserData.h"
#include "MuCO/ICustomizableObjectModule.h"
#include "MuCO/UnrealConversionUtils.h"
#include "MuCO/UnrealPortabilityHelpers.h"
#include "MuCO/LogBenchmarkUtil.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Rendering/Texture2DResource.h"
#include "RenderingThread.h"
#include "SkeletalMergingLibrary.h"
#include "MuCO/ICustomizableObjectEditorModule.h"
#include "UObject/UObjectIterator.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "PhysicsEngine/AggregateGeom.h"
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
#include "PhysicsEngine/SkeletalBodySetup.h"
#include "Hash/CityHash.h"
#include "MuCO/CustomizableObjectCustomVersion.h"
#include "MuCO/CustomizableObjectResourceData.h"
#include "MuCO/CustomizableObjectStreamedResourceData.h"
#include "MuCO/CustomizableObjectResourceDataTypes.h"
#include "MuCO/LoadUtils.h"
#include "Serialization/BulkData.h"
#include "HAL/PlatformFileManager.h"
#include "MuCO/Plugins/IMutableClothingModule.h"
#include "Rendering/SkeletalMeshModel.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CustomizableObjectInstance)
#if WITH_EDITOR
#include "Logging/MessageLog.h"
#include "MessageLogModule.h"
#include "UnrealEdMisc.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Application/ThrottleManager.h"
#endif
namespace
{
#ifndef REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES
#define REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES !PLATFORM_DESKTOP
#endif
bool bDisableClothingPhysicsEditsPropagation = false;
static FAutoConsoleVariableRef CVarDisableClothingPhysicsEditsPropagation(
TEXT("mutable.DisableClothingPhysicsEditsPropagation"),
bDisableClothingPhysicsEditsPropagation,
TEXT("If set to true, disables clothing physics edits propagation from the render mesh."),
ECVF_Default);
bool bDisableNotifyComponentsOfTextureUpdates = false;
static FAutoConsoleVariableRef CVarDisableNotifyComponentsOfTextureUpdates(
TEXT("mutable.DisableNotifyComponentsOfTextureUpdates"),
bDisableNotifyComponentsOfTextureUpdates,
TEXT("If set to true, disables Mutable notifying the streaming system that a component has had a change in at least one texture of its components."),
ECVF_Default);
}
const FString MULTILAYER_PROJECTOR_PARAMETERS_INVALID = TEXT("Invalid Multilayer Projector Parameters.");
const FString NUM_LAYERS_PARAMETER_POSTFIX = FString("_NumLayers");
const FString OPACITY_PARAMETER_POSTFIX = FString("_Opacity");
const FString IMAGE_PARAMETER_POSTFIX = FString("_SelectedImages");
const FString POSE_PARAMETER_POSTFIX = FString("_SelectedPoses");
// Struct used by BuildMaterials() to identify common materials between LODs
struct FMutableMaterialPlaceholder
{
enum class EPlaceHolderParamType { Vector, Scalar, Texture };
struct FMutableMaterialPlaceHolderParam
{
FName ParamName;
EPlaceHolderParamType Type;
int32 LayerIndex; // Set to -1 for non-multilayer params
float Scalar;
FLinearColor Vector;
FGeneratedTexture Texture;
FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const FLinearColor& InVector)
: ParamName(InParamName), Type(EPlaceHolderParamType::Vector), LayerIndex(InLayerIndex), Vector(InVector) {}
FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const float InScalar)
: ParamName(InParamName), Type(EPlaceHolderParamType::Scalar), LayerIndex(InLayerIndex), Scalar(InScalar) {}
FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const FGeneratedTexture& InTexture)
: ParamName(InParamName), Type(EPlaceHolderParamType::Texture), LayerIndex(InLayerIndex), Texture(InTexture) {}
bool operator<(const FMutableMaterialPlaceHolderParam& Other) const
{
return Type < Other.Type || ParamName.CompareIndexes(Other.ParamName);
}
bool operator==(const FMutableMaterialPlaceHolderParam& Other) const = default;
};
uint32 ParentMaterialID = 0;
int32 MatIndex = -1;
private:
mutable TArray<FMutableMaterialPlaceHolderParam> Params;
public:
void AddParam(const FMutableMaterialPlaceHolderParam& NewParam) { Params.Add(NewParam); }
const TArray<FMutableMaterialPlaceHolderParam>& GetParams() const { return Params; }
bool operator==(const FMutableMaterialPlaceholder& Other) const;
friend uint32 GetTypeHash(const FMutableMaterialPlaceholder& PlaceHolder);
};
bool FMutableMaterialPlaceholder::operator==(const FMutableMaterialPlaceholder& Other) const
{
return ParentMaterialID == Other.ParentMaterialID &&
Params == Other.Params;
}
// Return a hash of the material and its parameters
uint32 GetTypeHash(const FMutableMaterialPlaceholder& PlaceHolder)
{
uint32 Hash = GetTypeHash(PlaceHolder.ParentMaterialID);
// Sort parameters before building the hash.
PlaceHolder.Params.Sort();
for (const FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam& Param : PlaceHolder.Params)
{
uint32 ParamHash = GetTypeHash(Param.ParamName);
ParamHash = HashCombineFast(ParamHash, (uint32)Param.LayerIndex);
ParamHash = HashCombineFast(ParamHash, (uint32)Param.Type);
switch (Param.Type)
{
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Vector:
ParamHash = HashCombineFast(ParamHash, GetTypeHash(Param.Vector));
break;
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Scalar:
ParamHash = HashCombineFast(ParamHash, GetTypeHash(Param.Scalar));
break;
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Texture:
ParamHash = HashCombineFast(ParamHash, Param.Texture.Texture->GetUniqueID());
break;
}
Hash = HashCombineFast(Hash, ParamHash);
}
return Hash;
}
UTexture2D* UCustomizableInstancePrivate::CreateTexture(const FString& TextureName)
{
UTexture2D* NewTexture = NewObject<UTexture2D>(
GetTransientPackage(),
GetData(TextureName),
RF_Transient
);
UCustomizableObjectSystem::GetInstance()->GetPrivate()->LogBenchmarkUtil.AddTexture(*NewTexture);
NewTexture->SetPlatformData( nullptr );
return NewTexture;
}
void UCustomizableInstancePrivate::SetLastMeshId(FCustomizableObjectComponentIndex ObjectComponentIndex, int32 LODIndex, mu::FResourceID MeshId)
{
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
if (ComponentData && ComponentData->LastMeshIdPerLOD.IsValidIndex(LODIndex))
{
ComponentData->LastMeshIdPerLOD[LODIndex] = MeshId;
}
else
{
check(false);
}
}
void UCustomizableInstancePrivate::InvalidateGeneratedData()
{
SkeletalMeshStatus = ESkeletalMeshStatus::NotGenerated;
SkeletalMeshes.Reset();
CommittedDescriptor = {};
CommittedDescriptorHash = {};
// Init Component Data
FCustomizableInstanceComponentData TemplateComponentData;
TemplateComponentData.LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT);
ComponentsData.Init(TemplateComponentData, ComponentsData.Num());
GeneratedMaterials.Empty();
}
void UCustomizableInstancePrivate::InitCustomizableObjectData(const UCustomizableObject* InCustomizableObject)
{
InvalidateGeneratedData();
if (!InCustomizableObject || !InCustomizableObject->IsCompiled())
{
return;
}
// Init Component Data
FCustomizableInstanceComponentData TemplateComponentData;
TemplateComponentData.LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT);
ComponentsData.Init(TemplateComponentData, InCustomizableObject->GetComponentCount());
ExtensionInstanceData.Empty();
}
FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(const FName& ComponentName)
{
UCustomizableObject* Object = GetPublic()->GetCustomizableObject();
if (!Object || !Object->IsCompiled())
{
return nullptr;
}
const int32 ObjectComponentIndex = Object->GetPrivate()->GetModelResourcesChecked().ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName);
if (ObjectComponentIndex == INDEX_NONE)
{
return nullptr;
}
if (!ComponentsData.IsValidIndex(ObjectComponentIndex))
{
return nullptr;
}
return &ComponentsData[ObjectComponentIndex];
}
FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(FCustomizableObjectComponentIndex ObjectComponentIndex)
{
return ComponentsData.IsValidIndex(ObjectComponentIndex.GetValue()) ? &ComponentsData[ObjectComponentIndex.GetValue()] : nullptr;
}
const FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(FCustomizableObjectComponentIndex ObjectComponentIndex) const
{
return ComponentsData.IsValidIndex(ObjectComponentIndex.GetValue()) ? &ComponentsData[ObjectComponentIndex.GetValue()] : nullptr;
}
UCustomizableObjectInstance::UCustomizableObjectInstance()
{
SetFlags(RF_Transactional);
}
void UCustomizableInstancePrivate::SetDescriptor(const FCustomizableObjectInstanceDescriptor& InDescriptor)
{
UCustomizableObject* InCustomizableObject = InDescriptor.GetCustomizableObject();
const bool bCustomizableObjectChanged = GetPublic()->Descriptor.GetCustomizableObject() != InCustomizableObject;
#if WITH_EDITOR
// Bind a lambda to the PostCompileDelegate and unbind from the previous object if any.
BindObjectDelegates(GetPublic()->GetCustomizableObject(), InCustomizableObject);
#endif
GetPublic()->Descriptor = InDescriptor;
if (bCustomizableObjectChanged)
{
InitCustomizableObjectData(InCustomizableObject);
}
}
void UCustomizableInstancePrivate::PrepareForUpdate(const TSharedRef<FUpdateContextPrivate>& OperationData)
{
// Clear the ComponentData from previous updates
for (FCustomizableInstanceComponentData& ComponentData : ComponentsData)
{
ComponentData.AnimSlotToBP.Empty();
ComponentData.AssetUserDataArray.Empty();
ComponentData.Skeletons.Skeleton = nullptr;
ComponentData.Skeletons.SkeletonIds.Empty();
ComponentData.Skeletons.SkeletonsToMerge.Empty();
ComponentData.PhysicsAssets.PhysicsAssetToLoad.Empty();
ComponentData.PhysicsAssets.PhysicsAssetsToMerge.Empty();
ComponentData.ClothingPhysicsAssetsToStream.Empty();
ComponentData.StreamedResourceIndex.Empty();
ComponentData.OverlayMaterial = nullptr;
#if WITH_EDITORONLY_DATA
ComponentData.MeshPartPaths.Empty();
#endif
}
}
#if WITH_EDITOR
void UCustomizableInstancePrivate::PostDuplicate(bool bDuplicateForPIE)
{
Super::PostDuplicate(bDuplicateForPIE);
// Invalidate all generated data to avoid modifying resources shared between CO instances.
InvalidateGeneratedData();
// Empty after duplicating or ReleasingMutableResources may free textures used by the other CO instance.
GeneratedTextures.Empty();
}
void UCustomizableInstancePrivate::OnPostCompile()
{
GetDescriptor().ReloadParameters();
InitCustomizableObjectData(GetPublic()->GetCustomizableObject());
}
void UCustomizableInstancePrivate::OnObjectStatusChanged(FCustomizableObjectStatus::EState Previous, FCustomizableObjectStatus::EState Next)
{
if (Previous != Next && Next == FCustomizableObjectStatus::EState::ModelLoaded)
{
OnPostCompile();
}
}
void UCustomizableInstancePrivate::BindObjectDelegates(UCustomizableObject* CurrentCustomizableObject, UCustomizableObject* NewCustomizableObject)
{
if (CurrentCustomizableObject == NewCustomizableObject)
{
return;
}
// Unbind callback from the previous CO
if (CurrentCustomizableObject)
{
CurrentCustomizableObject->GetPrivate()->Status.GetOnStateChangedDelegate().RemoveAll(this);
}
// Bind callback to the new CO
if (NewCustomizableObject)
{
NewCustomizableObject->GetPrivate()->Status.GetOnStateChangedDelegate().AddUObject(this, &UCustomizableInstancePrivate::OnObjectStatusChanged);
}
}
bool UCustomizableObjectInstance::CanEditChange(const FProperty* InProperty) const
{
bool bIsMutable = Super::CanEditChange(InProperty);
if (bIsMutable && InProperty != NULL)
{
if (InProperty->GetFName() == TEXT("CustomizationObject"))
{
bIsMutable = false;
}
if (InProperty->GetFName() == TEXT("ParameterName"))
{
bIsMutable = false;
}
}
return bIsMutable;
}
void UCustomizableObjectInstance::PostTransacted(const FTransactionObjectEvent& TransactionEvent)
{
Super::PostTransacted(TransactionEvent);
GetPrivate()->OnInstanceTransactedDelegate.Broadcast(TransactionEvent);
}
#endif // WITH_EDITOR
bool UCustomizableObjectInstance::IsEditorOnly() const
{
if (UCustomizableObject* CustomizableObject = GetCustomizableObject())
{
return CustomizableObject->IsEditorOnly();
}
return false;
}
void UCustomizableObjectInstance::PostInitProperties()
{
UObject::PostInitProperties();
if (!HasAllFlags(RF_ClassDefaultObject))
{
if (!PrivateData)
{
PrivateData = NewObject<UCustomizableInstancePrivate>(this, FName("Private"));
}
else if (PrivateData->GetOuter() != this)
{
PrivateData = Cast<UCustomizableInstancePrivate>(StaticDuplicateObject(PrivateData, this, FName("Private")));
}
}
}
void UCustomizableObjectInstance::BeginDestroy()
{
// Release the Live Instance ID if there it hadn't been released before
DestroyLiveUpdateInstance();
if (PrivateData)
{
#if WITH_EDITOR
// Unbind Object delegates
PrivateData->BindObjectDelegates(GetCustomizableObject(), nullptr);
#endif
PrivateData.Get()->ReleaseMutableResources(true, *this);
}
Super::BeginDestroy();
}
void UCustomizableObjectInstance::DestroyLiveUpdateInstance()
{
if (PrivateData && PrivateData->LiveUpdateModeInstanceID)
{
// If UCustomizableObjectSystemPrivate::SSystem is nullptr it means it has already been destroyed, no point in registering an instanceID release
// since the Mutable system has already been destroyed. Just checking UCustomizableObjectSystem::GetInstance() will try to recreate the system when
// everything is shutting down, so it's better to check UCustomizableObjectSystemPrivate::SSystem first here
if (UCustomizableObjectSystemPrivate::SSystem && UCustomizableObjectSystem::GetInstance() && UCustomizableObjectSystem::GetInstance()->GetPrivate())
{
UCustomizableObjectSystem::GetInstance()->GetPrivate()->InitInstanceIDRelease(PrivateData->LiveUpdateModeInstanceID);
PrivateData->LiveUpdateModeInstanceID = 0;
}
}
}
void UCustomizableInstancePrivate::ReleaseMutableResources(bool bCalledFromBeginDestroy, const UCustomizableObjectInstance& Instance)
{
GeneratedMaterials.Empty();
if (UCustomizableObjectSystem::IsCreated()) // Need to check this because the object might be destroyed after the CustomizableObjectSystem at shutdown
{
UCustomizableObjectSystemPrivate* CustomizableObjectSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate();
// Get the cache of resources of all live instances of this object
FMutableResourceCache& Cache = CustomizableObjectSystem->GetObjectCache(Instance.GetCustomizableObject());
for (FGeneratedTexture& Texture : GeneratedTextures)
{
if (CustomizableObjectSystem->RemoveTextureReference(Texture.Key))
{
// Do not release textures when called from BeginDestroy, it would produce a texture artifact in the
// instance's remaining sk meshes and GC is being performed anyway so it will free the textures if needed
if (!bCalledFromBeginDestroy && CustomizableObjectSystem->bReleaseTexturesImmediately)
{
ReleaseMutableTexture(Texture.Key, Cast<UTexture2D>(Texture.Texture), Cache);
}
}
}
}
GeneratedTextures.Empty();
}
bool UCustomizableObjectInstance::IsReadyForFinishDestroy()
{
//return ReleaseResourcesFence.IsFenceComplete();
return true;
}
void UCustomizableObjectInstance::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FCustomizableObjectCustomVersion::GUID);
const int32 CustomizableObjectCustomVersion = GetLinkerCustomVersion(FCustomizableObjectCustomVersion::GUID);
if (CustomizableObjectCustomVersion < FCustomizableObjectCustomVersion::GroupProjectorIntToScalarIndex)
{
TArray<int32> IntParametersToMove;
// Find the num layer parameters that were int enums
for (int32 i = 0; i < IntParameters_DEPRECATED.Num(); ++i)
{
if (IntParameters_DEPRECATED[i].ParameterName.EndsWith(NUM_LAYERS_PARAMETER_POSTFIX, ESearchCase::CaseSensitive))
{
FString ParameterNamePrefix, Aux;
const bool bSplit = IntParameters_DEPRECATED[i].ParameterName.Split(NUM_LAYERS_PARAMETER_POSTFIX, &ParameterNamePrefix, &Aux);
check(bSplit);
// Confirm this is actually a multilayer param by finding the corresponding pose param
for (int32 j = 0; j < IntParameters_DEPRECATED.Num(); ++j)
{
if (i != j)
{
if (IntParameters_DEPRECATED[j].ParameterName.StartsWith(ParameterNamePrefix, ESearchCase::CaseSensitive) &&
IntParameters_DEPRECATED[j].ParameterName.EndsWith(POSE_PARAMETER_POSTFIX, ESearchCase::CaseSensitive))
{
IntParametersToMove.Add(i);
break;
}
}
}
}
}
// Convert them to float params
for (int32 i = 0; i < IntParametersToMove.Num(); ++i)
{
FloatParameters_DEPRECATED.AddDefaulted();
FloatParameters_DEPRECATED.Last().ParameterName = IntParameters_DEPRECATED[IntParametersToMove[i]].ParameterName;
FloatParameters_DEPRECATED.Last().ParameterValue = FCString::Atoi(*IntParameters_DEPRECATED[IntParametersToMove[i]].ParameterValueName);
FloatParameters_DEPRECATED.Last().Id = IntParameters_DEPRECATED[IntParametersToMove[i]].Id;
}
// Remove them from the int params in reverse order
for (int32 i = IntParametersToMove.Num() - 1; i >= 0; --i)
{
IntParameters_DEPRECATED.RemoveAt(IntParametersToMove[i]);
}
}
if (CustomizableObjectCustomVersion < FCustomizableObjectCustomVersion::CustomizableObjectInstanceDescriptor)
{
Descriptor.CustomizableObject = CustomizableObject_DEPRECATED;
Descriptor.BoolParameters = BoolParameters_DEPRECATED;
Descriptor.IntParameters = IntParameters_DEPRECATED;
Descriptor.FloatParameters = FloatParameters_DEPRECATED;
Descriptor.TextureParameters = TextureParameters_DEPRECATED;
Descriptor.VectorParameters = VectorParameters_DEPRECATED;
Descriptor.ProjectorParameters = ProjectorParameters_DEPRECATED;
}
}
void UCustomizableObjectInstance::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
PrivateData->BindObjectDelegates(nullptr, GetCustomizableObject());
#endif
// Skip the cost of ReloadParameters in the cook commandlet; it will be reloaded during PreSave. For cooked runtime
// and editor UI, reload on load because it will not otherwise reload unless the CustomizableObject recompiles.
Descriptor.ReloadParameters();
PrivateData->InitCustomizableObjectData(GetCustomizableObject());
}
FString UCustomizableObjectInstance::GetDesc()
{
FString ObjectName = "Missing Object";
if (UCustomizableObject* CustomizableObject = GetCustomizableObject())
{
ObjectName = CustomizableObject->GetName();
}
return FString::Printf(TEXT("Instance of [%s]"), *ObjectName);
}
int32 UCustomizableObjectInstance::GetProjectorValueRange(const FString& ParamName) const
{
return Descriptor.GetProjectorValueRange(ParamName);
}
int32 UCustomizableObjectInstance::GetIntValueRange(const FString& ParamName) const
{
return Descriptor.GetIntValueRange(ParamName);
}
int32 UCustomizableObjectInstance::GetFloatValueRange(const FString& ParamName) const
{
return Descriptor.GetFloatValueRange(ParamName);
}
int32 UCustomizableObjectInstance::GetTextureValueRange(const FString& ParamName) const
{
return Descriptor.GetTextureValueRange(ParamName);
}
// Only safe to call if the Mutable texture ref count system returns 0 and absolutely sure nobody holds a reference to the texture
void UCustomizableInstancePrivate::ReleaseMutableTexture(const FMutableImageCacheKey& MutableTextureKey, UTexture2D* Texture, FMutableResourceCache& Cache)
{
if (ensure(Texture) && Texture->IsValidLowLevel())
{
Texture->ConditionalBeginDestroy();
for (FTexture2DMipMap& Mip : Texture->GetPlatformData()->Mips)
{
Mip.BulkData.RemoveBulkData();
}
}
// Must remove texture from cache since it has been released
Cache.Images.Remove(MutableTextureKey);
}
void UCustomizableObjectInstance::SetObject(UCustomizableObject* InObject)
{
#if WITH_EDITOR
// Bind a lambda to the PostCompileDelegate and unbind from the previous object if any.
PrivateData->BindObjectDelegates(GetCustomizableObject(), InObject);
#endif
Descriptor.SetCustomizableObject(InObject);
PrivateData->InitCustomizableObjectData(InObject);
}
UCustomizableObject* UCustomizableObjectInstance::GetCustomizableObject() const
{
return Descriptor.CustomizableObject;
}
bool UCustomizableObjectInstance::GetBuildParameterRelevancy() const
{
return Descriptor.GetBuildParameterRelevancy();
}
void UCustomizableObjectInstance::SetBuildParameterRelevancy(bool Value)
{
Descriptor.SetBuildParameterRelevancy(Value);
}
int32 UCustomizableInstancePrivate::GetState() const
{
return GetPublic()->Descriptor.GetState();
}
void UCustomizableInstancePrivate::SetState(const int32 InState)
{
const int32 OldState = GetState();
GetPublic()->Descriptor.SetState(InState);
if (OldState != InState)
{
// State may change texture properties, so invalidate the texture reuse cache
TextureReuseCache.Empty();
}
}
FString UCustomizableObjectInstance::GetCurrentState() const
{
return Descriptor.GetCurrentState();
}
void UCustomizableObjectInstance::SetCurrentState(const FString& StateName)
{
Descriptor.SetCurrentState(StateName);
}
bool UCustomizableObjectInstance::IsParameterRelevant(int32 ParameterIndex) const
{
// This should have been precalculated in the last update if the appropriate flag in the instance was set.
return GetPrivate()->RelevantParameters.Contains(ParameterIndex);
}
bool UCustomizableObjectInstance::IsParameterRelevant(const FString& ParamName) const
{
UCustomizableObject* CustomizableObject = GetCustomizableObject();
if (!CustomizableObject)
{
return false;
}
// This should have been precalculated in the last update if the appropriate flag in the instance was set.
int32 ParameterIndexInObject = CustomizableObject->GetPrivate()->FindParameter(ParamName);
return GetPrivate()->RelevantParameters.Contains(ParameterIndexInObject);
}
bool UCustomizableObjectInstance::IsParameterDirty(const FString& ParamName, const int32 RangeIndex) const
{
switch (Descriptor.CustomizableObject->GetParameterTypeByName(ParamName))
{
case EMutableParameterType::None:
return false;
case EMutableParameterType::Projector:
{
const FCustomizableObjectProjectorParameterValue* Result = Descriptor.ProjectorParameters.FindByPredicate([&](const FCustomizableObjectProjectorParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectProjectorParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.ProjectorParameters.FindByPredicate([&](const FCustomizableObjectProjectorParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->Value == ResultCommited->Value;
}
else
{
if (Result->RangeValues.IsValidIndex(RangeIndex) && ResultCommited->RangeValues.IsValidIndex(RangeIndex))
{
return Result->RangeValues[RangeIndex] == ResultCommited->RangeValues[RangeIndex];
}
else
{
return Result->RangeValues.Num() != ResultCommited->RangeValues.Num();
}
}
}
else
{
return Result != ResultCommited;
}
}
case EMutableParameterType::Texture:
{
const FCustomizableObjectTextureParameterValue* Result = Descriptor.TextureParameters.FindByPredicate([&](const FCustomizableObjectTextureParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectTextureParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.TextureParameters.FindByPredicate([&](const FCustomizableObjectTextureParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->ParameterValue == ResultCommited->ParameterValue;
}
else
{
if (Result->ParameterRangeValues.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValues.IsValidIndex(RangeIndex))
{
return Result->ParameterRangeValues[RangeIndex] == ResultCommited->ParameterRangeValues[RangeIndex];
}
else
{
return Result->ParameterRangeValues.Num() != ResultCommited->ParameterRangeValues.Num();
}
}
}
else
{
return Result != ResultCommited;
}
}
case EMutableParameterType::Bool:
{
const FCustomizableObjectBoolParameterValue* Result = Descriptor.BoolParameters.FindByPredicate([&](const FCustomizableObjectBoolParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectBoolParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.BoolParameters.FindByPredicate([&](const FCustomizableObjectBoolParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->ParameterValue == ResultCommited->ParameterValue;
}
else
{
return false;
}
}
else
{
return Result != ResultCommited;
}
}
case EMutableParameterType::Int:
{
const FCustomizableObjectIntParameterValue* Result = Descriptor.IntParameters.FindByPredicate([&](const FCustomizableObjectIntParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectIntParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.IntParameters.FindByPredicate([&](const FCustomizableObjectIntParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->ParameterValueName == ResultCommited->ParameterValueName;
}
else
{
if (Result->ParameterRangeValueNames.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValueNames.IsValidIndex(RangeIndex))
{
return Result->ParameterRangeValueNames[RangeIndex] == ResultCommited->ParameterRangeValueNames[RangeIndex];
}
else
{
return Result->ParameterRangeValueNames.Num() != ResultCommited->ParameterRangeValueNames.Num();
}
}
}
else
{
return Result != ResultCommited;
}
}
case EMutableParameterType::Float:
{
const FCustomizableObjectFloatParameterValue* Result = Descriptor.FloatParameters.FindByPredicate([&](const FCustomizableObjectFloatParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectFloatParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.FloatParameters.FindByPredicate([&](const FCustomizableObjectFloatParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->ParameterValue == ResultCommited->ParameterValue;
}
else
{
if (Result->ParameterRangeValues.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValues.IsValidIndex(RangeIndex))
{
return Result->ParameterRangeValues[RangeIndex] == ResultCommited->ParameterRangeValues[RangeIndex];
}
else
{
return Result->ParameterRangeValues.Num() != ResultCommited->ParameterRangeValues.Num();
}
}
}
else
{
return Result != ResultCommited;
}
}
case EMutableParameterType::Color:
{
const FCustomizableObjectVectorParameterValue* Result = Descriptor.VectorParameters.FindByPredicate([&](const FCustomizableObjectVectorParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
const FCustomizableObjectVectorParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.VectorParameters.FindByPredicate([&](const FCustomizableObjectVectorParameterValue& Value)
{
return Value.ParameterName == ParamName;
});
if (Result && ResultCommited)
{
if (RangeIndex == INDEX_NONE)
{
return Result->ParameterValue == ResultCommited->ParameterValue;
}
else
{
return false;
}
}
else
{
return Result != ResultCommited;
}
}
default:
unimplemented();
return false;
}
}
void UCustomizableInstancePrivate::PostEditChangePropertyWithoutEditor()
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::PostEditChangePropertyWithoutEditor);
for (TTuple<FName, TObjectPtr<USkeletalMesh>>& Tuple : SkeletalMeshes)
{
USkeletalMesh* SkeletalMesh = Tuple.Get<1>();
if (SkeletalMesh && SkeletalMesh->GetResourceForRendering() && !SkeletalMesh->GetResourceForRendering()->IsInitialized())
{
MUTABLE_CPUPROFILER_SCOPE(InitResources);
// reinitialize resources
SkeletalMesh->InitResources();
}
}
}
bool UCustomizableInstancePrivate::CanUpdateInstance() const
{
UCustomizableObject* Object = GetPublic()->GetCustomizableObject();
if (!Object)
{
return false;
}
#if WITH_EDITOR
if (Object->GetPrivate()->IsLocked())
{
return false;
}
if (!Object->IsCompiled())
{
return false;
}
return true;
#else
return Object->IsCompiled();
#endif
}
void UCustomizableObjectInstance::UpdateSkeletalMeshAsync(bool bIgnoreCloseDist, bool bForceHighPriority)
{
UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const TSharedRef<FUpdateContextPrivate> Context = MakeShared<FUpdateContextPrivate>(*this);
Context->bIgnoreCloseDist = bIgnoreCloseDist;
Context->bForceHighPriority = bForceHighPriority;
SystemPrivate->EnqueueUpdateSkeletalMesh(Context);
}
void UCustomizableObjectInstance::UpdateSkeletalMeshAsyncResult(FInstanceUpdateDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority)
{
UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const TSharedRef<FUpdateContextPrivate> Context = MakeShared<FUpdateContextPrivate>(*this);
Context->bIgnoreCloseDist = bIgnoreCloseDist;
Context->bForceHighPriority = bForceHighPriority;
Context->UpdateCallback = Callback;
SystemPrivate->EnqueueUpdateSkeletalMesh(Context);
}
void UCustomizableObjectInstance::UpdateSkeletalMeshAsyncResult(FInstanceUpdateNativeDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority)
{
UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const TSharedRef<FUpdateContextPrivate> Context = MakeShared<FUpdateContextPrivate>(*this);
Context->bIgnoreCloseDist = bIgnoreCloseDist;
Context->bForceHighPriority = bForceHighPriority;
Context->UpdateNativeCallback = Callback;
SystemPrivate->EnqueueUpdateSkeletalMesh(Context);
}
void UCustomizableInstancePrivate::TickUpdateCloseCustomizableObjects(UCustomizableObjectInstance& Public, FMutableInstanceUpdateMap& InOutRequestedUpdates)
{
UCustomizableObject* Object = Public.GetCustomizableObject();
if (!Object)
{
return;
}
#if WITH_EDITOR
if (!Object->IsCompiled() &&
Object->GetPrivate()->CompilationResult != ECompilationResultPrivate::Errors) // Avoid constantly retry failed compilations.
{
if (ICustomizableObjectEditorModule* EditorModule = ICustomizableObjectEditorModule::Get())
{
EditorModule->CompileCustomizableObject(*Object, nullptr, true, false);
}
}
#endif
if (!CanUpdateInstance())
{
return;
}
const UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const EUpdateRequired UpdateRequired = SystemPrivate->IsUpdateRequired(Public, true, true, false);
if (UpdateRequired != EUpdateRequired::NoUpdate) // Since this is done in the tick, avoid starting an update that we know for sure that would not be performed. Once started it has some performance implications that we want to avoid.
{
if (UpdateRequired == EUpdateRequired::Discard)
{
UCustomizableObjectSystem::GetInstance()->GetPrivate()->InitDiscardResourcesSkeletalMesh(&Public);
InOutRequestedUpdates.Remove(&Public);
}
else if (UpdateRequired == EUpdateRequired::Update)
{
const EQueuePriorityType Priority = SystemPrivate->GetUpdatePriority(Public, false);
FMutableUpdateCandidate* UpdateCandidate = InOutRequestedUpdates.Find(&Public);
if (UpdateCandidate)
{
ensure(HasCOInstanceFlags(PendingLODsUpdate | PendingLODsDowngrade));
UpdateCandidate->Priority = Priority;
UpdateCandidate->Issue();
}
else
{
FMutableUpdateCandidate Candidate(&Public);
Candidate.Priority = Priority;
Candidate.Issue();
InOutRequestedUpdates.Add(&Public, Candidate);
}
}
else
{
check(false);
}
}
else
{
InOutRequestedUpdates.Remove(&Public);
}
ClearCOInstanceFlags(PendingLODsUpdate | PendingLODsDowngrade);
}
void UCustomizableInstancePrivate::UpdateInstanceIfNotGenerated(UCustomizableObjectInstance& Public, FMutableInstanceUpdateMap& InOutRequestedUpdates)
{
if (SkeletalMeshStatus != ESkeletalMeshStatus::NotGenerated)
{
return;
}
if (!CanUpdateInstance())
{
return;
}
UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const TSharedRef<FUpdateContextPrivate> Context = MakeShared<FUpdateContextPrivate>(Public);
Context->bOnlyUpdateIfNotGenerated = true;
SystemPrivate->EnqueueUpdateSkeletalMesh(Context);
EQueuePriorityType Priority = SystemPrivate->GetUpdatePriority(Public, false);
FMutableUpdateCandidate* UpdateCandidate = InOutRequestedUpdates.Find(&Public);
if (UpdateCandidate)
{
UpdateCandidate->Priority = Priority;
UpdateCandidate->Issue();
}
else
{
FMutableUpdateCandidate Candidate(&Public);
Candidate.Priority = Priority;
Candidate.Issue();
InOutRequestedUpdates.Add(&Public, Candidate);
}
}
#if !UE_BUILD_SHIPPING
bool AreSkeletonsCompatible(const TArray<TObjectPtr<USkeleton>>& InSkeletons)
{
MUTABLE_CPUPROFILER_SCOPE(AreSkeletonsCompatible);
if (InSkeletons.IsEmpty())
{
return true;
}
bool bCompatible = true;
struct FBoneToMergeInfo
{
FBoneToMergeInfo(const uint32 InBonePathHash, const uint32 InSkeletonIndex, const uint32 InParentBoneSkeletonIndex) :
BonePathHash(InBonePathHash), SkeletonIndex(InSkeletonIndex), ParentBoneSkeletonIndex(InParentBoneSkeletonIndex)
{}
uint32 BonePathHash = 0;
uint32 SkeletonIndex = 0;
uint32 ParentBoneSkeletonIndex = 0;
};
// Accumulated hierarchy hash from parent-bone to root bone
TMap<FName, FBoneToMergeInfo> BoneNamesToBoneInfo;
BoneNamesToBoneInfo.Reserve(InSkeletons[0] ? InSkeletons[0]->GetReferenceSkeleton().GetNum() : 0);
for (int32 SkeletonIndex = 0; SkeletonIndex < InSkeletons.Num(); ++SkeletonIndex)
{
const TObjectPtr<USkeleton> Skeleton = InSkeletons[SkeletonIndex];
check(Skeleton);
const FReferenceSkeleton& ReferenceSkeleton = Skeleton->GetReferenceSkeleton();
const TArray<FMeshBoneInfo>& Bones = ReferenceSkeleton.GetRawRefBoneInfo();
const TArray<FTransform>& BonePoses = ReferenceSkeleton.GetRawRefBonePose();
const int32 NumBones = Bones.Num();
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
const FMeshBoneInfo& Bone = Bones[BoneIndex];
// Retrieve parent bone name and respective hash, root-bone is assumed to have a parent hash of 0
const FName ParentName = Bone.ParentIndex != INDEX_NONE ? Bones[Bone.ParentIndex].Name : NAME_None;
const uint32 ParentHash = Bone.ParentIndex != INDEX_NONE ? GetTypeHash(ParentName) : 0;
// Look-up the path-hash from root to the parent bone
const FBoneToMergeInfo* ParentBoneInfo = BoneNamesToBoneInfo.Find(ParentName);
const uint32 ParentBonePathHash = ParentBoneInfo ? ParentBoneInfo->BonePathHash : 0;
const uint32 ParentBoneSkeletonIndex = ParentBoneInfo ? ParentBoneInfo->SkeletonIndex : 0;
// Append parent hash to path to give full path hash to current bone
const uint32 BonePathHash = HashCombine(ParentBonePathHash, ParentHash);
// Check if the bone exists in the hierarchy
const FBoneToMergeInfo* ExistingBoneInfo = BoneNamesToBoneInfo.Find(Bone.Name);
// If the hash differs from the existing one it means skeletons are incompatible
if (!ExistingBoneInfo)
{
// Add path hash to current bone
BoneNamesToBoneInfo.Add(Bone.Name, FBoneToMergeInfo(BonePathHash, SkeletonIndex, ParentBoneSkeletonIndex));
}
else if (ExistingBoneInfo->BonePathHash != BonePathHash)
{
if (bCompatible)
{
// Print the skeletons to merge
FString Msg = TEXT("Failed to merge skeletons. Skeletons to merge: ");
for (int32 AuxSkeletonIndex = 0; AuxSkeletonIndex < InSkeletons.Num(); ++AuxSkeletonIndex)
{
if (InSkeletons[AuxSkeletonIndex] != nullptr)
{
Msg += FString::Printf(TEXT("\n\t- %s"), *InSkeletons[AuxSkeletonIndex].GetName());
}
}
UE_LOG(LogMutable, Error, TEXT("%s"), *Msg);
#if WITH_EDITOR
FNotificationInfo Info(FText::FromString(TEXT("Mutable: Failed to merge skeletons. Invalid parent chain detected. Please check the output log for more information.")));
Info.bFireAndForget = true;
Info.FadeOutDuration = 1.0f;
Info.ExpireDuration = 10.0f;
FSlateNotificationManager::Get().AddNotification(Info);
#endif
bCompatible = false;
}
// Print the first non compatible bone in the bone chain, since all child bones will be incompatible too.
if (ExistingBoneInfo->ParentBoneSkeletonIndex != SkeletonIndex)
{
// Different skeletons can't be used if they are incompatible with the reference skeleton.
UE_LOG(LogMutable, Error, TEXT("[%s] parent bone is different in skeletons [%s] and [%s]."),
*Bone.Name.ToString(),
*InSkeletons[SkeletonIndex]->GetName(),
*InSkeletons[ExistingBoneInfo->ParentBoneSkeletonIndex]->GetName());
}
}
}
}
return bCompatible;
}
#endif
USkeleton* UCustomizableInstancePrivate::MergeSkeletons(UCustomizableObject& CustomizableObject, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, FCustomizableObjectComponentIndex ObjectComponentIndex, bool& bOutCreatedNewSkeleton)
{
MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_MergeSkeletons);
bOutCreatedNewSkeleton = false;
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
check(ComponentData);
FReferencedSkeletons& ReferencedSkeletons = ComponentData->Skeletons;
// Merged skeleton found in the cache
if (ReferencedSkeletons.Skeleton)
{
USkeleton* MergedSkeleton = ReferencedSkeletons.Skeleton;
ReferencedSkeletons.Skeleton = nullptr;
return MergedSkeleton;
}
// No need to merge skeletons
if(ReferencedSkeletons.SkeletonsToMerge.Num() == 1)
{
const TObjectPtr<USkeleton> RefSkeleton = ReferencedSkeletons.SkeletonsToMerge[0];
ReferencedSkeletons.SkeletonIds.Empty();
ReferencedSkeletons.SkeletonsToMerge.Empty();
return RefSkeleton;
}
#if !UE_BUILD_SHIPPING
// Test Skeleton compatibility before attempting the merge to avoid a crash.
if (!AreSkeletonsCompatible(ReferencedSkeletons.SkeletonsToMerge))
{
return nullptr;
}
#endif
FSkeletonMergeParams Params;
Params.SkeletonsToMerge = ReferencedSkeletons.SkeletonsToMerge;
USkeleton* FinalSkeleton = USkeletalMergingLibrary::MergeSkeletons(Params);
if (!FinalSkeleton)
{
FString Msg = FString::Printf(TEXT("MergeSkeletons failed for Customizable Object [%s], Instance [%s]. Skeletons involved: "),
*CustomizableObject.GetName(),
*GetOuter()->GetName());
const int32 SkeletonCount = Params.SkeletonsToMerge.Num();
for (int32 SkeletonIndex = 0; SkeletonIndex < SkeletonCount; ++SkeletonIndex)
{
Msg += FString::Printf(TEXT(" [%s]"), *Params.SkeletonsToMerge[SkeletonIndex]->GetName());
}
UE_LOG(LogMutable, Error, TEXT("%s"), *Msg);
}
else
{
#if WITH_EDITOR
uint32 CombinedSkeletonHash = INDEX_NONE;
#endif
// Make the final skeleton compatible with all the merged skeletons and their compatible skeletons.
for (USkeleton* Skeleton : Params.SkeletonsToMerge)
{
if (Skeleton)
{
FinalSkeleton->AddCompatibleSkeleton(Skeleton);
const TArray<TSoftObjectPtr<USkeleton>>& CompatibleSkeletons = Skeleton->GetCompatibleSkeletons();
for (const TSoftObjectPtr<USkeleton>& CompatibleSkeleton : CompatibleSkeletons)
{
FinalSkeleton->AddCompatibleSkeletonSoft(CompatibleSkeleton);
}
#if WITH_EDITOR
const uint32 SkeletonHash = GetTypeHash(Skeleton->GetName());
CombinedSkeletonHash = HashCombine(CombinedSkeletonHash, SkeletonHash);
#endif
}
}
// Add the hash based on the sources for the merged skeleton to make its name unique
#if WITH_EDITOR
FinalSkeleton->Rename(*FString::Printf(TEXT("%s_%lu"), *FinalSkeleton->GetName(), CombinedSkeletonHash));
#endif
// Add Skeleton to the cache
CustomizableObject.GetPrivate()->SkeletonCache.Add(ReferencedSkeletons.SkeletonIds, FinalSkeleton);
ReferencedSkeletons.SkeletonIds.Empty();
bOutCreatedNewSkeleton = true;
}
return FinalSkeleton;
}
namespace
{
FORCEINLINE TObjectPtr<UPhysicsConstraintTemplate> ClonePhysicsConstraintTemplate(
const TObjectPtr<UPhysicsConstraintTemplate>& From,
const TObjectPtr<UObject>& Outer,
FName Name = NAME_None)
{
// We don't use DuplicateObject here beacuse it is too slow.
TObjectPtr<UPhysicsConstraintTemplate> Result = NewObject<UPhysicsConstraintTemplate>(Outer, Name);
Result->DefaultInstance = From->DefaultInstance;
Result->ProfileHandles = From->ProfileHandles;
#if WITH_EDITOR
Result->SetDefaultProfile(From->DefaultInstance);
#endif
return Result;
}
FKAggregateGeom MakeAggGeomFromMutablePhysics(int32 BodyIndex, const mu::FPhysicsBody* MutablePhysicsBody)
{
FKAggregateGeom BodyAggGeom;
auto GetCollisionEnabledFormFlags = [](uint32 Flags) -> ECollisionEnabled::Type
{
return ECollisionEnabled::Type(Flags & 0xFF);
};
auto GetContributeToMassFromFlags = [](uint32 Flags) -> bool
{
return static_cast<bool>((Flags >> 8) & 1);
};
const int32 NumSpheres = MutablePhysicsBody->GetSphereCount(BodyIndex);
TArray<FKSphereElem>& AggSpheres = BodyAggGeom.SphereElems;
AggSpheres.Empty(NumSpheres);
for (int32 I = 0; I < NumSpheres; ++I)
{
uint32 Flags = MutablePhysicsBody->GetSphereFlags(BodyIndex, I);
FString Name = MutablePhysicsBody->GetSphereName(BodyIndex, I);
FVector3f Position;
float Radius;
MutablePhysicsBody->GetSphere(BodyIndex, I, Position, Radius);
FKSphereElem& NewElem = AggSpheres.AddDefaulted_GetRef();
NewElem.Center = FVector(Position);
NewElem.Radius = Radius;
NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags));
NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags));
NewElem.SetName(FName(*Name));
}
const int32 NumBoxes = MutablePhysicsBody->GetBoxCount(BodyIndex);
TArray<FKBoxElem>& AggBoxes = BodyAggGeom.BoxElems;
AggBoxes.Empty(NumBoxes);
for (int32 I = 0; I < NumBoxes; ++I)
{
uint32 Flags = MutablePhysicsBody->GetBoxFlags(BodyIndex, I);
FString Name = MutablePhysicsBody->GetBoxName(BodyIndex, I);
FVector3f Position;
FQuat4f Orientation
;
FVector3f Size;
MutablePhysicsBody->GetBox(BodyIndex, I, Position, Orientation, Size);
FKBoxElem& NewElem = AggBoxes.AddDefaulted_GetRef();
NewElem.Center = FVector(Position);
NewElem.Rotation = FRotator(Orientation.Rotator());
NewElem.X = Size.X;
NewElem.Y = Size.Y;
NewElem.Z = Size.Z;
NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags));
NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags));
NewElem.SetName(FName(*Name));
}
//const int32 NumConvexes = MutablePhysicsBody->GetConvexCount( BodyIndex );
//TArray<FKConvexElem>& AggConvexes = BodyAggGeom.ConvexElems;
//AggConvexes.Empty();
//for (int32 I = 0; I < NumConvexes; ++I)
//{
// uint32 Flags = MutablePhysicsBody->GetConvexFlags( BodyIndex, I );
// FString Name = MutablePhysicsBody->GetConvexName( BodyIndex, I );
// const FVector3f* Vertices;
// const int32* Indices;
// int32 NumVertices;
// int32 NumIndices;
// FTransform3f Transform;
// MutablePhysicsBody->GetConvex( BodyIndex, I, Vertices, NumVertices, Indices, NumIndices, Transform );
//
// TArrayView<const FVector3f> VerticesView( Vertices, NumVertices );
// TArrayView<const int32> IndicesView( Indices, NumIndices );
//}
TArray<FKSphylElem>& AggSphyls = BodyAggGeom.SphylElems;
const int32 NumSphyls = MutablePhysicsBody->GetSphylCount(BodyIndex);
AggSphyls.Empty(NumSphyls);
for (int32 I = 0; I < NumSphyls; ++I)
{
uint32 Flags = MutablePhysicsBody->GetSphylFlags(BodyIndex, I);
FString Name = MutablePhysicsBody->GetSphylName(BodyIndex, I);
FVector3f Position;
FQuat4f Orientation;
float Radius;
float Length;
MutablePhysicsBody->GetSphyl(BodyIndex, I, Position, Orientation, Radius, Length);
FKSphylElem& NewElem = AggSphyls.AddDefaulted_GetRef();
NewElem.Center = FVector(Position);
NewElem.Rotation = FRotator(Orientation.Rotator());
NewElem.Radius = Radius;
NewElem.Length = Length;
NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags));
NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags));
NewElem.SetName(FName(*Name));
}
TArray<FKTaperedCapsuleElem>& AggTaperedCapsules = BodyAggGeom.TaperedCapsuleElems;
const int32 NumTaperedCapsules = MutablePhysicsBody->GetTaperedCapsuleCount(BodyIndex);
AggTaperedCapsules.Empty(NumTaperedCapsules);
for (int32 I = 0; I < NumTaperedCapsules; ++I)
{
uint32 Flags = MutablePhysicsBody->GetTaperedCapsuleFlags(BodyIndex, I);
FString Name = MutablePhysicsBody->GetTaperedCapsuleName(BodyIndex, I);
FVector3f Position;
FQuat4f Orientation;
float Radius0;
float Radius1;
float Length;
MutablePhysicsBody->GetTaperedCapsule(BodyIndex, I, Position, Orientation, Radius0, Radius1, Length);
FKTaperedCapsuleElem& NewElem = AggTaperedCapsules.AddDefaulted_GetRef();
NewElem.Center = FVector(Position);
NewElem.Rotation = FRotator(Orientation.Rotator());
NewElem.Radius0 = Radius0;
NewElem.Radius1 = Radius1;
NewElem.Length = Length;
NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags));
NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags));
NewElem.SetName(FName(*Name));
}
return BodyAggGeom;
};
TObjectPtr<UPhysicsAsset> MakePhysicsAssetFromTemplateAndMutableBody(
const TSharedRef<FUpdateContextPrivate>& OperationData,
TObjectPtr<UPhysicsAsset> TemplateAsset,
const mu::FPhysicsBody* MutablePhysics,
FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
check(TemplateAsset);
TObjectPtr<UPhysicsAsset> Result = NewObject<UPhysicsAsset>();
if (!Result)
{
return nullptr;
}
Result->SolverSettings = TemplateAsset->SolverSettings;
Result->SolverType = TemplateAsset->SolverType;
Result->bNotForDedicatedServer = TemplateAsset->bNotForDedicatedServer;
const TMap<mu::FBoneName, TPair<FName, uint16>>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap;
TMap<FName, int32> BonesInUse;
const int32 MutablePhysicsBodyCount = MutablePhysics->GetBodyCount();
BonesInUse.Reserve(MutablePhysicsBodyCount);
for ( int32 I = 0; I < MutablePhysicsBodyCount; ++I )
{
if (const TPair<FName, uint16>* BoneInfo = BoneInfoMap.Find(MutablePhysics->GetBodyBoneId(I)))
{
BonesInUse.Add(BoneInfo->Key, I);
}
}
const int32 PhysicsAssetBodySetupNum = TemplateAsset->SkeletalBodySetups.Num();
bool bTemplateBodyNotUsedFound = false;
TArray<uint8> UsageMap;
UsageMap.Init(1, PhysicsAssetBodySetupNum);
for (int32 BodySetupIndex = 0; BodySetupIndex < PhysicsAssetBodySetupNum; ++BodySetupIndex)
{
const TObjectPtr<USkeletalBodySetup>& BodySetup = TemplateAsset->SkeletalBodySetups[BodySetupIndex];
int32* MutableBodyIndex = BonesInUse.Find(BodySetup->BoneName);
if (!MutableBodyIndex)
{
bTemplateBodyNotUsedFound = true;
UsageMap[BodySetupIndex] = 0;
continue;
}
TObjectPtr<USkeletalBodySetup> NewBodySetup = NewObject<USkeletalBodySetup>(Result);
NewBodySetup->BodySetupGuid = FGuid::NewGuid();
// Copy Body properties
NewBodySetup->BoneName = BodySetup->BoneName;
NewBodySetup->PhysicsType = BodySetup->PhysicsType;
NewBodySetup->bConsiderForBounds = BodySetup->bConsiderForBounds;
NewBodySetup->bMeshCollideAll = BodySetup->bMeshCollideAll;
NewBodySetup->bDoubleSidedGeometry = BodySetup->bDoubleSidedGeometry;
NewBodySetup->bGenerateNonMirroredCollision = BodySetup->bGenerateNonMirroredCollision;
NewBodySetup->bSharedCookedData = BodySetup->bSharedCookedData;
NewBodySetup->bGenerateMirroredCollision = BodySetup->bGenerateMirroredCollision;
NewBodySetup->PhysMaterial = BodySetup->PhysMaterial;
NewBodySetup->CollisionReponse = BodySetup->CollisionReponse;
NewBodySetup->CollisionTraceFlag = BodySetup->CollisionTraceFlag;
NewBodySetup->DefaultInstance = BodySetup->DefaultInstance;
NewBodySetup->WalkableSlopeOverride = BodySetup->WalkableSlopeOverride;
NewBodySetup->BuildScale3D = BodySetup->BuildScale3D;
NewBodySetup->bSkipScaleFromAnimation = BodySetup->bSkipScaleFromAnimation;
// PhysicalAnimationProfiles can't be added with the current UPhysicsAsset API outside the editor.
// Don't pouplate them for now.
//NewBodySetup->PhysicalAnimationData = BodySetup->PhysicalAnimationData;
NewBodySetup->AggGeom = MakeAggGeomFromMutablePhysics(*MutableBodyIndex, MutablePhysics);
Result->SkeletalBodySetups.Add(NewBodySetup);
}
if (!bTemplateBodyNotUsedFound)
{
Result->CollisionDisableTable = TemplateAsset->CollisionDisableTable;
const int32 NumConstraints = TemplateAsset->ConstraintSetup.Num();
Result->ConstraintSetup.SetNum(NumConstraints);
for (int32 ConstraintIndex = 0; ConstraintIndex < NumConstraints; ++ConstraintIndex)
{
const TObjectPtr<UPhysicsConstraintTemplate>& TemplateConstraint = TemplateAsset->ConstraintSetup[ConstraintIndex];
if (!TemplateConstraint)
{
continue;
}
Result->ConstraintSetup[ConstraintIndex] = ClonePhysicsConstraintTemplate(TemplateConstraint, Result);
}
}
else
{
// Recreate the collision disable entry
Result->CollisionDisableTable.Reserve(TemplateAsset->CollisionDisableTable.Num());
for (const TPair<FRigidBodyIndexPair, bool>& CollisionDisableEntry : TemplateAsset->CollisionDisableTable)
{
const bool bIndex0Used = UsageMap[CollisionDisableEntry.Key.Indices[0]] > 0;
const bool bIndex1Used = UsageMap[CollisionDisableEntry.Key.Indices[1]] > 0;
if (bIndex0Used && bIndex1Used)
{
Result->CollisionDisableTable.Add(CollisionDisableEntry);
}
}
// Only add constraints that are part of the bones used for the mutable physics volumes description.
Result->ConstraintSetup.Reserve(TemplateAsset->ConstraintSetup.Num());
for (const TObjectPtr<UPhysicsConstraintTemplate>& Constraint : TemplateAsset->ConstraintSetup)
{
if (!Constraint)
{
continue;
}
const FName BoneA = Constraint->DefaultInstance.ConstraintBone1;
const FName BoneB = Constraint->DefaultInstance.ConstraintBone2;
if (BonesInUse.Find(BoneA) && BonesInUse.Find(BoneB))
{
Result->ConstraintSetup.AddDefaulted_GetRef() = ClonePhysicsConstraintTemplate(Constraint, Result);
}
}
}
Result->UpdateBodySetupIndexMap();
Result->UpdateBoundsBodiesArray();
#if WITH_EDITORONLY_DATA
Result->ConstraintProfiles = TemplateAsset->ConstraintProfiles;
#endif
return Result;
}
}
UPhysicsAsset* UCustomizableInstancePrivate::GetOrBuildMainPhysicsAsset(
const TSharedRef<FUpdateContextPrivate>& OperationData,
TObjectPtr<UPhysicsAsset> TemplateAsset,
const mu::FPhysicsBody* MutablePhysics,
bool bDisableCollisionsBetweenDifferentAssets,
FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(MergePhysicsAssets);
check(MutablePhysics);
UPhysicsAsset* Result = nullptr;
const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex);
if (!Component)
{
return nullptr;
}
FCustomizableObjectComponentIndex ObjectComponentIndex = Component->Id;
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
check(ComponentData);
TArray<TObjectPtr<UPhysicsAsset>>& PhysicsAssets = ComponentData->PhysicsAssets.PhysicsAssetsToMerge;
TArray<TObjectPtr<UPhysicsAsset>> ValidAssets;
const int32 NumPhysicsAssets = ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Num();
for (int32 I = 0; I < NumPhysicsAssets; ++I)
{
const TObjectPtr<UPhysicsAsset>& PhysicsAsset = ComponentData->PhysicsAssets.PhysicsAssetsToMerge[I];
if (PhysicsAsset)
{
ValidAssets.AddUnique(PhysicsAsset);
}
}
if (!ValidAssets.Num())
{
return Result;
}
// Just get the referenced asset if no recontrution or merge is needed.
if (ValidAssets.Num() == 1 && !MutablePhysics->bBodiesModified)
{
return ValidAssets[0];
}
TemplateAsset = TemplateAsset ? TemplateAsset : ValidAssets[0];
check(TemplateAsset);
Result = NewObject<UPhysicsAsset>();
if (!Result)
{
return nullptr;
}
Result->SolverSettings = TemplateAsset->SolverSettings;
Result->SolverType = TemplateAsset->SolverType;
Result->bNotForDedicatedServer = TemplateAsset->bNotForDedicatedServer;
const TMap<mu::FBoneName, TPair<FName, uint16>>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap;
TMap<FName, int32> BonesInUse;
const int32 MutablePhysicsBodyCount = MutablePhysics->GetBodyCount();
BonesInUse.Reserve(MutablePhysicsBodyCount);
for ( int32 I = 0; I < MutablePhysicsBodyCount; ++I )
{
if (const TPair<FName, uint16>* BoneInfo = BoneInfoMap.Find(MutablePhysics->GetBodyBoneId(I)))
{
BonesInUse.Add(BoneInfo->Key, I);
}
}
// Each array is a set of elements that can collide
TArray<TArray<int32, TInlineAllocator<8>>> CollisionSets;
// {SetIndex, ElementInSetIndex, BodyIndex}
using CollisionSetEntryType = TTuple<int32, int32, int32>;
// Map from BodyName/BoneName to set and index in set.
TMap<FName, CollisionSetEntryType> BodySetupSetMap;
// {SetIndex0, SetIndex1, BodyIndex}
using MultiSetCollisionEnableType = TTuple<int32, int32, int32>;
// Only for elements that belong to two or more differnet sets.
// Contains in which set the elements belong.
using MultiSetArrayType = TArray<int32, TInlineAllocator<4>>;
TMap<int32, MultiSetArrayType> MultiCollisionSets;
TArray<TArray<int32>> SetsIndexMap;
CollisionSets.SetNum(ValidAssets.Num());
SetsIndexMap.SetNum(CollisionSets.Num());
TMap<FRigidBodyIndexPair, bool> CollisionDisableTable;
// New body index
int32 CurrentBodyIndex = 0;
for (int32 CollisionSetIndex = 0; CollisionSetIndex < ValidAssets.Num(); ++CollisionSetIndex)
{
const int32 PhysicsAssetBodySetupNum = ValidAssets[CollisionSetIndex]->SkeletalBodySetups.Num();
SetsIndexMap[CollisionSetIndex].Init(-1, PhysicsAssetBodySetupNum);
for (int32 BodySetupIndex = 0; BodySetupIndex < PhysicsAssetBodySetupNum; ++BodySetupIndex)
{
const TObjectPtr<USkeletalBodySetup>& BodySetup = ValidAssets[CollisionSetIndex]->SkeletalBodySetups[BodySetupIndex];
int32* MutableBodyIndex = BonesInUse.Find(BodySetup->BoneName);
if (!MutableBodyIndex)
{
continue;
}
CollisionSetEntryType* Found = BodySetupSetMap.Find(BodySetup->BoneName);
if (!Found)
{
TObjectPtr<USkeletalBodySetup> NewBodySetup = NewObject<USkeletalBodySetup>(Result);
NewBodySetup->BodySetupGuid = FGuid::NewGuid();
// Copy Body properties
NewBodySetup->BoneName = BodySetup->BoneName;
NewBodySetup->PhysicsType = BodySetup->PhysicsType;
NewBodySetup->bConsiderForBounds = BodySetup->bConsiderForBounds;
NewBodySetup->bMeshCollideAll = BodySetup->bMeshCollideAll;
NewBodySetup->bDoubleSidedGeometry = BodySetup->bDoubleSidedGeometry;
NewBodySetup->bGenerateNonMirroredCollision = BodySetup->bGenerateNonMirroredCollision;
NewBodySetup->bSharedCookedData = BodySetup->bSharedCookedData;
NewBodySetup->bGenerateMirroredCollision = BodySetup->bGenerateMirroredCollision;
NewBodySetup->PhysMaterial = BodySetup->PhysMaterial;
NewBodySetup->CollisionReponse = BodySetup->CollisionReponse;
NewBodySetup->CollisionTraceFlag = BodySetup->CollisionTraceFlag;
NewBodySetup->DefaultInstance = BodySetup->DefaultInstance;
NewBodySetup->WalkableSlopeOverride = BodySetup->WalkableSlopeOverride;
NewBodySetup->BuildScale3D = BodySetup->BuildScale3D;
NewBodySetup->bSkipScaleFromAnimation = BodySetup->bSkipScaleFromAnimation;
// PhysicalAnimationProfiles can't be added with the current UPhysicsAsset API outside the editor.
// Don't poulate them for now.
//NewBodySetup->PhysicalAnimationData = BodySetup->PhysicalAnimationData;
NewBodySetup->AggGeom = MakeAggGeomFromMutablePhysics(*MutableBodyIndex, MutablePhysics);
Result->SkeletalBodySetups.Add(NewBodySetup);
int32 IndexInSet = CollisionSets[CollisionSetIndex].Add(CurrentBodyIndex);
BodySetupSetMap.Add(BodySetup->BoneName, {CollisionSetIndex, IndexInSet, CurrentBodyIndex});
SetsIndexMap[CollisionSetIndex][IndexInSet] = CurrentBodyIndex;
++CurrentBodyIndex;
}
else
{
int32 FoundCollisionSetIndex = Found->Get<0>();
int32 FoundCollisionSetElemIndex = Found->Get<1>();
int32 FoundBodyIndex = Found->Get<2>();
// No need to add the body again. Volumes that come form mutable are already merged.
// here we only need to merge properies.
// TODO: check if there is other properties worth merging. In case of conflict select the more restrivtive one?
Result->SkeletalBodySetups[FoundBodyIndex]->bConsiderForBounds |= BodySetup->bConsiderForBounds;
// Mark as removed so no indices are invalidated.
CollisionSets[FoundCollisionSetIndex][FoundCollisionSetElemIndex] = INDEX_NONE;
// Add Elem to the set but mark it as removed so we have an index for remapping.
int32 IndexInSet = CollisionSets[CollisionSetIndex].Add(INDEX_NONE);
SetsIndexMap[CollisionSetIndex][IndexInSet] = FoundBodyIndex;
MultiSetArrayType& Sets = MultiCollisionSets.FindOrAdd(FoundBodyIndex);
// The first time there is a collision (MultSet is empty), add the colliding element set
// as well as the current set.
if (!Sets.Num())
{
Sets.Add(FoundCollisionSetIndex);
}
Sets.Add(CollisionSetIndex);
}
}
// Remap collision indices removing invalid ones.
CollisionDisableTable.Reserve(CollisionDisableTable.Num() + ValidAssets[CollisionSetIndex]->CollisionDisableTable.Num());
for (const TPair<FRigidBodyIndexPair, bool>& DisabledCollision : ValidAssets[CollisionSetIndex]->CollisionDisableTable)
{
int32 MappedIdx0 = SetsIndexMap[CollisionSetIndex][DisabledCollision.Key.Indices[0]];
int32 MappedIdx1 = SetsIndexMap[CollisionSetIndex][DisabledCollision.Key.Indices[1]];
// This will generate correct disables for the case when two shapes from different sets
// are meged to the same setup. Will introduce repeated pairs, but this is not a problem.
// Currenly if two bodies / bones have disabled collision in one of the merged assets, the collision
// will remain disabled even if other merges allow it.
if ( MappedIdx0 != INDEX_NONE && MappedIdx1 != INDEX_NONE )
{
CollisionDisableTable.Add({MappedIdx0, MappedIdx1}, DisabledCollision.Value);
}
}
// Only add constraints that are part of the bones used for the mutable physics volumes description.
Result->ConstraintSetup.Reserve(Result->ConstraintSetup.Num() + ValidAssets[CollisionSetIndex]->ConstraintSetup.Num());
for (const TObjectPtr<UPhysicsConstraintTemplate>& Constraint : ValidAssets[CollisionSetIndex]->ConstraintSetup)
{
if (!Constraint)
{
continue;
}
FName BoneA = Constraint->DefaultInstance.ConstraintBone1;
FName BoneB = Constraint->DefaultInstance.ConstraintBone2;
if (BonesInUse.Find(BoneA) && BonesInUse.Find(BoneB))
{
Result->ConstraintSetup.AddDefaulted_GetRef() = ClonePhysicsConstraintTemplate(Constraint, Result);
}
}
#if WITH_EDITORONLY_DATA
Result->ConstraintProfiles.Append(ValidAssets[CollisionSetIndex]->ConstraintProfiles);
#endif
}
if (bDisableCollisionsBetweenDifferentAssets)
{
// Compute collision disable table size upperbound to reduce number of alloactions.
int32 CollisionDisableTableSize = 0;
for (int32 S0 = 1; S0 < CollisionSets.Num(); ++S0)
{
for (int32 S1 = 0; S1 < S0; ++S1)
{
CollisionDisableTableSize += CollisionSets[S1].Num() * CollisionSets[S0].Num();
}
}
// We already may have elements in the table, but at the moment of
// addition we don't know yet the final number of elements.
// Now a good number of elements will be added and because we know the final number of elements
// an upperbound to the number of interactions can be computed and reserved.
CollisionDisableTable.Reserve(CollisionDisableTableSize);
// Generate disable collision entry for every element in Set S0 for every element in Set S1
// that are not in multiple sets.
for (int32 S0 = 1; S0 < CollisionSets.Num(); ++S0)
{
for (int32 S1 = 0; S1 < S0; ++S1)
{
for (int32 Set0Elem : CollisionSets[S0])
{
// Element present in more than one set, will be treated later.
if (Set0Elem == INDEX_NONE)
{
continue;
}
for (int32 Set1Elem : CollisionSets[S1])
{
// Element present in more than one set, will be treated later.
if (Set1Elem == INDEX_NONE)
{
continue;
}
CollisionDisableTable.Add(FRigidBodyIndexPair{Set0Elem, Set1Elem}, false);
}
}
}
}
// Process elements that belong to multiple sets that have been merged to the same element.
for ( const TPair<int32, MultiSetArrayType>& Sets : MultiCollisionSets )
{
for (int32 S = 0; S < CollisionSets.Num(); ++S)
{
if (!Sets.Value.Contains(S))
{
for (int32 SetElem : CollisionSets[S])
{
if (SetElem != INDEX_NONE)
{
CollisionDisableTable.Add(FRigidBodyIndexPair{Sets.Key, SetElem}, false);
}
}
}
}
}
CollisionDisableTable.Shrink();
}
Result->CollisionDisableTable = MoveTemp(CollisionDisableTable);
Result->UpdateBodySetupIndexMap();
Result->UpdateBoundsBodiesArray();
ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Empty();
return Result;
}
static float MutableMeshesMinUVChannelDensity = 100.f;
FAutoConsoleVariableRef CVarMutableMeshesMinUVChannelDensity(
TEXT("Mutable.MinUVChannelDensity"),
MutableMeshesMinUVChannelDensity,
TEXT("Min UV density to set on generated meshes. This value will influence the requested texture mip to stream in. Higher values will result in higher quality mips being streamed in earlier."));
void SetMeshUVChannelDensity(FMeshUVChannelInfo& UVChannelInfo, float Density = 0.f)
{
Density = Density > 0.f ? Density : 150.f;
Density = FMath::Max(MutableMeshesMinUVChannelDensity, Density);
UVChannelInfo.bInitialized = true;
UVChannelInfo.bOverrideDensities = false;
for (int32 i = 0; i < TEXSTREAM_MAX_NUM_UVCHANNELS; ++i)
{
UVChannelInfo.LocalUVDensities[i] = Density;
}
}
bool UCustomizableInstancePrivate::DoComponentsNeedUpdate(UCustomizableObjectInstance* Public, const TSharedRef<FUpdateContextPrivate>& OperationData, bool& bHasInvalidMesh)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::DoComponentsNeedUpdate);
UCustomizableObject* CustomizableObject = Public->GetCustomizableObject();
if (!CustomizableObject)
{
return false;
}
const UModelResources& ModelResources = *CustomizableObject->GetPrivate()->GetModelResources();
const int32 NumInstanceComponents = OperationData->InstanceUpdateData.Components.Num();
// To be indexed with instance component index
TArray<bool> ComponentWithMesh;
ComponentWithMesh.Init(false, NumInstanceComponents);
TArray<mu::FResourceID> MeshIDs;
MeshIDs.Init(MAX_uint64, NumInstanceComponents * MAX_MESH_LOD_COUNT);
// Gather the Mesh Ids of all components, and validate the integrity of the meshes to generate.
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex)
{
const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex];
const FName ComponentName = OperationData->ComponentNames[Component.Id.GetValue()];
for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
if (!LOD.Mesh)
{
continue;
}
if (LOD.SurfaceCount == 0 && !LOD.Mesh->IsReference())
{
continue;
}
// Unreal does not support empty sections.
if (!LOD.Mesh->IsReference() && LOD.Mesh->GetVertexCount() == 0) // else
{
UE_LOG(LogMutable, Error, TEXT("Failed to generate SkeletalMesh for CO Instance [%s]. CO [%s] has invalid geometry for LOD [%d] Component [%d]."),
*Public->GetName(), *CustomizableObject->GetName(),
LODIndex, NumInstanceComponents);
bHasInvalidMesh = true;
continue;
}
ComponentWithMesh[InstanceComponentIndex] = true;
MeshIDs[InstanceComponentIndex * MAX_MESH_LOD_COUNT + LODIndex] = LOD.MeshID;
}
}
// Find which components need an update
OperationData->MeshChangedPerInstanceComponent.Init(false, NumInstanceComponents);
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex)
{
const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex];
const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id;
if (!ObjectComponentIndex.IsValid())
{
continue;
}
const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()];
if (OperationData->bUseMeshCache)
{
USkeletalMesh* CachedMesh = CustomizableObject->GetPrivate()->MeshCache.Get(*OperationData->GetMeshDescriptors(ObjectComponentIndex));
if (CachedMesh)
{
TObjectPtr<USkeletalMesh>* SkeletalMesh = SkeletalMeshes.Find(ComponentName);
const bool bMeshNeedsUpdate = !SkeletalMesh || (*SkeletalMesh != CachedMesh);
OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = bMeshNeedsUpdate;
ComponentWithMesh[InstanceComponentIndex] = true;
continue;
}
}
// Components with mesh must have valid geometry at CurrentMaxLOD
TObjectPtr<USkeletalMesh>* SkeletalMeshPtr = SkeletalMeshes.Find(ComponentName);
const bool bHadSkeletalMesh = SkeletalMeshPtr && *SkeletalMeshPtr;
if (Component.LODCount == 0)
{
// We don't have a mesh in the component, so it has changed if we had one before.
OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = bHadSkeletalMesh;
continue;
}
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD];
bool bIsReferenced = LOD.Mesh && LOD.Mesh->IsReference();
if (!bIsReferenced)
{
if (ComponentWithMesh[InstanceComponentIndex] && MeshIDs[InstanceComponentIndex * MAX_MESH_LOD_COUNT + OperationData->NumLODsAvailable[ComponentName] - 1] == MAX_uint64)
{
UE_LOG(LogMutable, Error, TEXT("Failed to generate SkeletalMesh for CO Instance [%s]. CO [%s] is missing geometry for LOD [%d] Object Component [%d]."),
*Public->GetName(), *CustomizableObject->GetName(),
OperationData->NumLODsAvailable[ComponentName] - 1, ObjectComponentIndex.GetValue());
bHasInvalidMesh = true;
continue;
}
}
// If the component wasn't there and now is there, we need to update it.
OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = !bHadSkeletalMesh && LOD.Mesh && (LOD.Mesh->GetFaceCount()>0);
const FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
if (!ComponentData) // Could be nullptr if the component has not been generated.
{
continue;
}
// Update if MeshIDs are different
const int32 ComponentOffset = InstanceComponentIndex * MAX_MESH_LOD_COUNT;
for (int32 MeshIndex = 0; !OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] && MeshIndex < MAX_MESH_LOD_COUNT; ++MeshIndex)
{
OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = MeshIDs[ComponentOffset + MeshIndex] != ComponentData->LastMeshIdPerLOD[MeshIndex];
}
// If the component SkeletalMesh was build with scalability settings different than the current, we need to update.
// The mismatch can happen when the scalability setting is changed and an update is requested with a MinLOD lower
// than the new FirstLODAvailable. This is an artifact of our LOD management system setting MinLOD.
if (!OperationData->bStreamMeshLODs &&
!OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] &&
bHadSkeletalMesh)
{
const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex)
{
TIndirectArray<FSkeletalMeshLODRenderData>& LODRenderData = (*SkeletalMeshPtr)->GetResourceForRendering()->LODRenderData;
if (LODRenderData[LODIndex].RenderSections.Num() != LODRenderData[FirstRequestedLOD].RenderSections.Num())
{
OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = true;
break;
}
}
}
}
bool bChanged =
OperationData->MeshChangedPerInstanceComponent.Num() != SkeletalMeshes.Num()
||
OperationData->MeshChangedPerInstanceComponent.Find(true) != INDEX_NONE;
// It also changed if we removed a component that we did have before
if (!bChanged)
{
for (const TPair<FName, TObjectPtr<USkeletalMesh>>& OldMesh : SkeletalMeshes)
{
bool bFound = false;
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex)
{
const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex];
const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id;
const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()];
if (ComponentName == OldMesh.Key)
{
bFound = true;
break;
}
}
if (!bFound)
{
bChanged = true;
break;
}
}
}
return !bHasInvalidMesh && bChanged;
}
bool UCustomizableInstancePrivate::UpdateSkeletalMesh_PostBeginUpdate0(UCustomizableObjectInstance* Public, const TSharedRef<FUpdateContextPrivate>& OperationData)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::UpdateSkeletalMesh_PostBeginUpdate0)
bool bHasInvalidMesh = false;
bool bUpdateMeshes = DoComponentsNeedUpdate(Public, OperationData, bHasInvalidMesh);
UCustomizableObject* CustomizableObject = Public->GetCustomizableObject();
if (!CustomizableObject)
{
UE_LOG(LogMutable, Warning, TEXT("Failed to generate SkeletalMesh for CO Instance %s. It does not have a CO."), *Public->GetName());
InvalidateGeneratedData();
OperationData->UpdateResult = EUpdateResult::Error;
return false;
}
// We can not handle empty meshes, clear any generated mesh and return
if (bHasInvalidMesh)
{
UE_LOG(LogMutable, Warning, TEXT("Failed to generate SkeletalMesh for CO Instance %s. CO [%s]"), *Public->GetName(), *GetNameSafe(CustomizableObject));
InvalidateGeneratedData();
OperationData->UpdateResult = EUpdateResult::Error;
return false;
}
TextureReuseCache.Empty(); // Sections may have changed, so invalidate the texture reuse cache because it's indexed by section
TMap<FName, TObjectPtr<USkeletalMesh>> OldSkeletalMeshes = SkeletalMeshes;
const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked();
// Collate the Extension Data on the instance into groups based on the extension that produced
// it, so that we only need to call extension functions such as OnSkeletalMeshCreated once for
// each extension.
TMap<const UCustomizableObjectExtension*, TArray<FInputPinDataContainer>> ExtensionToExtensionData;
{
const TArrayView<const UCustomizableObjectExtension* const> AllExtensions = ICustomizableObjectModule::Get().GetRegisteredExtensions();
// Pre-populate ExtensionToExtensionData with empty entries for all extensions.
//
// This ensures that extension functions such as OnSkeletalMeshCreated are called for each
// extension, even if they didn't produce any extension data.
Public->GetPrivate()->ExtensionInstanceData.Empty(AllExtensions.Num());
for (const UCustomizableObjectExtension* Extension : AllExtensions)
{
ExtensionToExtensionData.Add(Extension);
}
const TArrayView<const FRegisteredObjectNodeInputPin> ExtensionPins = ICustomizableObjectModule::Get().GetAdditionalObjectNodePins();
for (FInstanceUpdateData::FNamedExtensionData& ExtensionOutput : OperationData->InstanceUpdateData.ExtendedInputPins)
{
const FRegisteredObjectNodeInputPin* FoundPin =
Algo::FindBy(ExtensionPins, ExtensionOutput.Name, &FRegisteredObjectNodeInputPin::GlobalPinName);
if (!FoundPin)
{
// Failed to find the corresponding pin for this output
//
// This may indicate that a plugin has been removed or renamed since the CO was compiled
UE_LOG(LogMutable, Error, TEXT("Failed to find Object node input pin with name %s"), *ExtensionOutput.Name.ToString());
continue;
}
const UCustomizableObjectExtension* Extension = FoundPin->Extension.Get();
if (!Extension)
{
// Extension is not loaded or not found
UE_LOG(LogMutable, Error, TEXT("Extension for Object node input pin %s is no longer valid"), *ExtensionOutput.Name.ToString());
continue;
}
if (ExtensionOutput.Data->Origin == mu::FExtensionData::EOrigin::Invalid)
{
// Null data was produced
//
// This can happen if a node produces an FExtensionData but doesn't initialize it
UE_LOG(LogMutable, Error, TEXT("Invalid data sent to Object node input pin %s"), *ExtensionOutput.Name.ToString());
continue;
}
// All registered extensions were added to the map above, so if the extension is still
// registered it should be found.
TArray<FInputPinDataContainer>* ContainerArray = ExtensionToExtensionData.Find(Extension);
if (!ContainerArray)
{
UE_LOG(LogMutable, Error, TEXT("Object node input pin %s received data for unregistered extension %s"),
*ExtensionOutput.Name.ToString(), *Extension->GetPathName());
continue;
}
const FCustomizableObjectResourceData* ReferencedExtensionData = nullptr;
switch (ExtensionOutput.Data->Origin)
{
case mu::FExtensionData::EOrigin::ConstantAlwaysLoaded:
{
check(ModelResources.AlwaysLoadedExtensionData.IsValidIndex(ExtensionOutput.Data->Index));
ReferencedExtensionData = &ModelResources.AlwaysLoadedExtensionData[ExtensionOutput.Data->Index];
}
break;
case mu::FExtensionData::EOrigin::ConstantStreamed:
{
#if WITH_EDITOR
check(ModelResources.StreamedExtensionDataEditor.IsValidIndex(ExtensionOutput.Data->Index));
ReferencedExtensionData = &ModelResources.StreamedExtensionDataEditor[ExtensionOutput.Data->Index];
#else
check(ModelResources.StreamedExtensionData.IsValidIndex(ExtensionOutput.Data->Index));
const FCustomizableObjectStreamedResourceData& StreamedData = ModelResources.StreamedExtensionData[ExtensionOutput.Data->Index];
if (!StreamedData.IsLoaded())
{
// The data should have been loaded as part of executing the CO program.
//
// This could indicate a bug in the streaming logic.
UE_LOG(LogMutable, Error, TEXT("Customizable Object produced a streamed extension data that is not loaded: %s"),
*StreamedData.GetPath().ToString());
continue;
}
ReferencedExtensionData = &StreamedData.GetLoadedData();
#endif
}
break;
default:
unimplemented();
}
check(ReferencedExtensionData);
ContainerArray->Emplace(FoundPin->InputPin, ReferencedExtensionData->Data);
}
}
// Give each extension the chance to generate Extension Instance Data
for (const TPair<const UCustomizableObjectExtension*, TArray<FInputPinDataContainer>>& Pair : ExtensionToExtensionData)
{
FInstancedStruct NewExtensionInstanceData = Pair.Key->GenerateExtensionInstanceData(Pair.Value);
if (NewExtensionInstanceData.IsValid())
{
FExtensionInstanceData& NewData = Public->GetPrivate()->ExtensionInstanceData.AddDefaulted_GetRef();
NewData.Extension = Pair.Key;
NewData.Data = MoveTemp(NewExtensionInstanceData);
}
}
// None of the current meshes requires a mesh update. Continue to BuildMaterials
if (!bUpdateMeshes)
{
return true;
}
SkeletalMeshes.Reset();
const int32 NumInstanceComponents = OperationData->InstanceUpdateData.Components.Num();
for (int32 InstanceComponentIndexValue = 0; InstanceComponentIndexValue < NumInstanceComponents; ++InstanceComponentIndexValue)
{
FCustomizableObjectInstanceComponentIndex InstanceComponentIndex(InstanceComponentIndexValue);
const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData( InstanceComponentIndex );
if (!Component)
{
continue;
}
const FCustomizableObjectComponentIndex ObjectComponentIndex = Component->Id;
if (!ModelResources.ComponentNamesPerObjectComponent.IsValidIndex(ObjectComponentIndex.GetValue()))
{
continue;
}
const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()];
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ComponentName);
if (!ComponentData)
{
ensure(false);
InvalidateGeneratedData();
return false;
}
// If the component doesn't need an update copy the previously generated mesh.
if (!OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex.GetValue()])
{
if (TObjectPtr<USkeletalMesh>* Result = OldSkeletalMeshes.Find(ComponentName))
{
SkeletalMeshes.Add(ComponentName, *Result);
}
continue;
}
if (OperationData->bUseMeshCache)
{
TArray<mu::FResourceID>* MeshDescriptors = OperationData->GetMeshDescriptors(ObjectComponentIndex);
if (!MeshDescriptors)
{
continue;
}
if (USkeletalMesh* CachedMesh = CustomizableObject->GetPrivate()->MeshCache.Get(*MeshDescriptors))
{
check(MeshDescriptors->Num() == MAX_MESH_LOD_COUNT);
ComponentData->LastMeshIdPerLOD = *MeshDescriptors;
SkeletalMeshes.Add(ComponentName, CachedMesh);
continue;
}
}
// Reset last mesh IDs.
ComponentData->LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT);
// We need the first valid mesh. get it from the component, considering that some LOSs may have been skipped.
TSharedPtr<const mu::FMesh> ComponentMesh;
uint32 MeshHash = INDEX_NONE;
for (int32 FirstValidLODIndex = Component->FirstLOD; FirstValidLODIndex < OperationData->InstanceUpdateData.LODs.Num() && !ComponentMesh; ++FirstValidLODIndex)
{
ComponentMesh = OperationData->InstanceUpdateData.LODs[FirstValidLODIndex].Mesh;
MeshHash = HashCombine(MeshHash,GetTypeHash(OperationData->InstanceUpdateData.LODs[FirstValidLODIndex].MeshID));
}
if (!ComponentMesh)
{
continue;
}
if (ComponentMesh->GetSurfaceCount() == 0 && !ComponentMesh->IsReference())
{
continue;
}
// If it is a referenced resource, only the first LOD is relevant.
if (ComponentMesh->IsReference())
{
int32 ReferenceID = ComponentMesh->GetReferencedMesh();
TSoftObjectPtr<UStreamableRenderAsset> Ref = ModelResources.PassThroughMeshes[ReferenceID];
if (!Ref.IsValid())
{
// This shouldn't happen here synchronosuly. It should have been requested as an async load.
UE_LOG(LogMutable, Error, TEXT("Referenced mesh [%s] was not pre-loaded. It will be sync-loaded probably causing a hitch. CO [%s]"), *Ref.ToString(), *GetNameSafe(CustomizableObject));
}
UStreamableRenderAsset* Asset = MutablePrivate::LoadObject(Ref);
USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(Asset);
if (SkeletalMesh)
{
SkeletalMeshes.Add(ComponentName, SkeletalMesh);
}
else
{
// Pass-through static meshes not implemented yet.
UE_LOG(LogMutable, Error, TEXT("Referenced static meshes [%s] are not supported yet. CO [%s]"), *Ref.ToString(), *GetNameSafe(CustomizableObject));
}
continue;
}
if (!ModelResources.ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex.GetValue()))
{
InvalidateGeneratedData();
return false;
}
// Create and initialize the SkeletalMesh for this component
MUTABLE_CPUPROFILER_SCOPE(ConstructMesh);
USkeletalMesh* SkeletalMesh = nullptr;
FString SkeletalMeshName = FString::Printf (TEXT("SK_%s_%s_%lu"), *Public->GetCustomizableObject()->GetName(), *ComponentName.ToString(), MeshHash);
SkeletalMeshName = MakeUniqueObjectName(GetTransientPackage(), USkeletalMesh::StaticClass(), *SkeletalMeshName, EUniqueObjectNameOptions::GloballyUnique).ToString();
if (OperationData->bStreamMeshLODs)
{
SkeletalMesh = NewObject<UCustomizableObjectSkeletalMesh>(GetTransientPackage(), FName(*SkeletalMeshName), RF_Transient);
}
else
{
SkeletalMesh = NewObject<USkeletalMesh>(GetTransientPackage(), FName(*SkeletalMeshName), RF_Transient);
}
check(SkeletalMesh);
SkeletalMeshes.Add(ComponentName, SkeletalMesh);
const FMutableRefSkeletalMeshData& RefSkeletalMeshData = ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()];
// Set up the default information any mesh from this component will have (LODArrayInfos, RenderData, Mesh settings, etc).
InitSkeletalMeshData(OperationData, SkeletalMesh, RefSkeletalMeshData, *CustomizableObject, ObjectComponentIndex);
// Construct a new skeleton, fix up ActiveBones and Bonemap arrays and recompute the RefInvMatrices
const bool bBuildSkeletonDataSuccess = BuildSkeletonData(OperationData, *SkeletalMesh, RefSkeletalMeshData, *CustomizableObject, FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex) );
if (!bBuildSkeletonDataSuccess)
{
InvalidateGeneratedData();
return false;
}
// Build PhysicsAsset merging physics assets coming from SubMeshes of the newly generated Mesh
if (TSharedPtr<const mu::FPhysicsBody> MutablePhysics = ComponentMesh->GetPhysicsBody())
{
constexpr bool bDisallowCollisionBetweenAssets = true;
UPhysicsAsset* PhysicsAssetResult = GetOrBuildMainPhysicsAsset(
OperationData, RefSkeletalMeshData.PhysicsAsset, MutablePhysics.Get(), bDisallowCollisionBetweenAssets, FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex) );
SkeletalMesh->SetPhysicsAsset(PhysicsAssetResult);
#if WITH_EDITORONLY_DATA
if (PhysicsAssetResult && PhysicsAssetResult->GetPackage() == GetTransientPackage())
{
constexpr bool bMarkAsDirty = false;
PhysicsAssetResult->SetPreviewMesh(SkeletalMesh, bMarkAsDirty);
}
#endif
}
const int32 NumAdditionalPhysicsNum = ComponentMesh->AdditionalPhysicsBodies.Num();
for (int32 I = 0; I < NumAdditionalPhysicsNum; ++I)
{
TSharedPtr<const mu::FPhysicsBody> AdditionalPhysiscBody = ComponentMesh->AdditionalPhysicsBodies[I];
check(AdditionalPhysiscBody);
if (!AdditionalPhysiscBody->bBodiesModified)
{
continue;
}
const int32 PhysicsBodyExternalId = ComponentMesh->AdditionalPhysicsBodies[I]->CustomId;
const FAnimBpOverridePhysicsAssetsInfo& Info = ModelResources.AnimBpOverridePhysiscAssetsInfo[PhysicsBodyExternalId];
// Make sure the AnimInstance class is loaded. It is expected to be already loaded at this point though.
UClass* AnimInstanceClassLoaded = MutablePrivate::LoadClass(Info.AnimInstanceClass);
TSubclassOf<UAnimInstance> AnimInstanceClass = TSubclassOf<UAnimInstance>(AnimInstanceClassLoaded);
if (!ensureAlways(AnimInstanceClass))
{
continue;
}
FAnimBpGeneratedPhysicsAssets& PhysicsAssetsUsedByAnimBp = AnimBpPhysicsAssets.FindOrAdd(AnimInstanceClass);
TObjectPtr<UPhysicsAsset> PhysicsAssetTemplate = TObjectPtr<UPhysicsAsset>(Info.SourceAsset.Get());
check(PhysicsAssetTemplate);
FAnimInstanceOverridePhysicsAsset& Entry =
PhysicsAssetsUsedByAnimBp.AnimInstancePropertyIndexAndPhysicsAssets.Emplace_GetRef();
Entry.PropertyIndex = Info.PropertyIndex;
Entry.PhysicsAsset = MakePhysicsAssetFromTemplateAndMutableBody(
OperationData,PhysicsAssetTemplate, AdditionalPhysiscBody.Get(), InstanceComponentIndex);
}
// Add sockets from the SkeletalMesh of reference and from the MutableMesh
BuildMeshSockets(OperationData, SkeletalMesh, ModelResources, RefSkeletalMeshData, ComponentMesh);
for (const TPair<const UCustomizableObjectExtension*, TArray<FInputPinDataContainer>>& Pair : ExtensionToExtensionData)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
Pair.Key->OnSkeletalMeshCreated(Pair.Value, ObjectComponentIndex.GetValue(), SkeletalMesh);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
Pair.Key->OnSkeletalMeshCreated(Pair.Value, ComponentName, SkeletalMesh);
}
// Mesh to copy data from if possible.
TObjectPtr<USkeletalMesh>* OldSkeletalMeshPtr = OldSkeletalMeshes.Find(ComponentName);
USkeletalMesh* OldSkeletalMesh = OldSkeletalMeshPtr ? *OldSkeletalMeshPtr : nullptr;
BuildOrCopyElementData(OperationData, SkeletalMesh, *CustomizableObject, InstanceComponentIndex);
bool const bCopyRenderDataSuccess = BuildOrCopyRenderData(OperationData, SkeletalMesh, OldSkeletalMesh, Public, InstanceComponentIndex);
if (!bCopyRenderDataSuccess)
{
InvalidateGeneratedData();
return false;
}
BuildOrCopyMorphTargetsData(OperationData, SkeletalMesh, OldSkeletalMesh, *CustomizableObject, InstanceComponentIndex);
BuildOrCopyClothingData(OperationData, SkeletalMesh, ModelResources, InstanceComponentIndex, ClothingPhysicsAssets);
FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering();
ensure(RenderData && RenderData->LODRenderData.Num() > 0);
ensure(SkeletalMesh->GetLODNum() > 0);
if(RenderData)
{
for (FSkeletalMeshLODRenderData& LODResource : RenderData->LODRenderData)
{
UnrealConversionUtils::UpdateSkeletalMeshLODRenderDataBuffersSize(LODResource);
}
}
if (OperationData->bUseMeshCache)
{
const TArray<mu::FResourceID>* MeshId = OperationData->GetMeshDescriptors(ObjectComponentIndex);
if (MeshId)
{
CustomizableObject->GetPrivate()->MeshCache.Add(*MeshId, SkeletalMesh);
}
}
if (UCustomizableObjectSkeletalMesh* StreamableMesh = Cast<UCustomizableObjectSkeletalMesh>(SkeletalMesh))
{
StreamableMesh->InitMutableStreamingData(OperationData, ComponentName, Component->FirstLOD, Component->LODCount);
}
}
return true;
}
UCustomizableObjectInstance* UCustomizableObjectInstance::Clone()
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableObjectInstance::Clone);
// Default Outer is the transient package.
UCustomizableObjectInstance* NewInstance = NewObject<UCustomizableObjectInstance>();
check(NewInstance->PrivateData);
NewInstance->CopyParametersFromInstance(this);
return NewInstance;
}
UCustomizableObjectInstance* UCustomizableObjectInstance::CloneStatic(UObject* Outer)
{
UCustomizableObjectInstance* NewInstance = NewObject<UCustomizableObjectInstance>(Outer, GetClass());
NewInstance->CopyParametersFromInstance(this);
NewInstance->GetPrivate()->bShowOnlyRuntimeParameters = false;
return NewInstance;
}
void UCustomizableObjectInstance::CopyParametersFromInstance(UCustomizableObjectInstance* Instance)
{
GetPrivate()->SetDescriptor(Instance->GetPrivate()->GetDescriptor());
}
int32 UCustomizableObjectInstance::AddValueToIntRange(const FString& ParamName)
{
return Descriptor.AddValueToIntRange(ParamName);
}
int32 UCustomizableObjectInstance::AddValueToFloatRange(const FString& ParamName)
{
return Descriptor.AddValueToFloatRange(ParamName);
}
int32 UCustomizableObjectInstance::AddValueToProjectorRange(const FString& ParamName)
{
return Descriptor.AddValueToProjectorRange(ParamName);
}
int32 UCustomizableObjectInstance::RemoveValueFromIntRange(const FString& ParamName, int32 RangeIndex)
{
return Descriptor.RemoveValueFromIntRange(ParamName, RangeIndex);
}
int32 UCustomizableObjectInstance::RemoveValueFromFloatRange(const FString& ParamName, const int32 RangeIndex)
{
return Descriptor.RemoveValueFromFloatRange(ParamName, RangeIndex);
}
int32 UCustomizableObjectInstance::RemoveValueFromProjectorRange(const FString& ParamName, const int32 RangeIndex)
{
return Descriptor.RemoveValueFromProjectorRange(ParamName, RangeIndex);
}
int32 UCustomizableObjectInstance::MultilayerProjectorNumLayers(const FName& ProjectorParamName) const
{
return Descriptor.NumProjectorLayers(ProjectorParamName);
}
void UCustomizableObjectInstance::MultilayerProjectorCreateLayer(const FName& ProjectorParamName, int32 Index)
{
Descriptor.CreateLayer(ProjectorParamName, Index);
}
void UCustomizableObjectInstance::MultilayerProjectorRemoveLayerAt(const FName& ProjectorParamName, int32 Index)
{
Descriptor.RemoveLayerAt(ProjectorParamName, Index);
}
FMultilayerProjectorLayer UCustomizableObjectInstance::MultilayerProjectorGetLayer(const FName& ProjectorParamName, int32 Index) const
{
return Descriptor.GetLayer(ProjectorParamName, Index);
}
void UCustomizableObjectInstance::MultilayerProjectorUpdateLayer(const FName& ProjectorParamName, int32 Index, const FMultilayerProjectorLayer& Layer)
{
Descriptor.UpdateLayer(ProjectorParamName, Index, Layer);
}
void UCustomizableObjectInstance::SaveDescriptor(FArchive &Ar, bool bUseCompactDescriptor)
{
Descriptor.SaveDescriptor(Ar, bUseCompactDescriptor);
}
void UCustomizableObjectInstance::LoadDescriptor(FArchive &Ar)
{
Descriptor.LoadDescriptor(Ar);
}
const FString& UCustomizableObjectInstance::GetIntParameterSelectedOption(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetIntParameterSelectedOption(ParamName, RangeIndex);
}
void UCustomizableObjectInstance::SetIntParameterSelectedOption(int32 IntParamIndex, const FString& SelectedOption, const int32 RangeIndex)
{
Descriptor.SetIntParameterSelectedOption(IntParamIndex, SelectedOption, RangeIndex);
}
void UCustomizableObjectInstance::SetIntParameterSelectedOption(const FString& ParamName, const FString& SelectedOptionName, const int32 RangeIndex)
{
Descriptor.SetIntParameterSelectedOption(ParamName, SelectedOptionName, RangeIndex);
}
float UCustomizableObjectInstance::GetFloatParameterSelectedOption(const FString& FloatParamName, const int32 RangeIndex) const
{
return Descriptor.GetFloatParameterSelectedOption(FloatParamName, RangeIndex);
}
void UCustomizableObjectInstance::SetFloatParameterSelectedOption(const FString& FloatParamName, const float FloatValue, const int32 RangeIndex)
{
return Descriptor.SetFloatParameterSelectedOption(FloatParamName, FloatValue, RangeIndex);
}
UTexture* UCustomizableObjectInstance::GetTextureParameterSelectedOption(const FString& TextureParamName, const int32 RangeIndex) const
{
return Descriptor.GetTextureParameterSelectedOption(TextureParamName, RangeIndex);
}
void UCustomizableObjectInstance::SetTextureParameterSelectedOption(const FString& TextureParamName, UTexture* TextureValue, const int32 RangeIndex)
{
Descriptor.SetTextureParameterSelectedOption(TextureParamName, TextureValue, RangeIndex);
}
USkeletalMesh* UCustomizableObjectInstance::GetSkeletalMeshParameterSelectedOption(const FString& SkeletalMeshParamName, int32 RangeIndex)
{
return Descriptor.GetSkeletalMeshParameterSelectedOption(SkeletalMeshParamName, RangeIndex);
}
void UCustomizableObjectInstance::SetSkeletalMeshParameterSelectedOption(const FString& SkeletalMeshParamName, USkeletalMesh* SkeletalMeshValue, int32 RangeIndex)
{
Descriptor.SetSkeletalMeshParameterSelectedOption(SkeletalMeshParamName, SkeletalMeshValue, RangeIndex);
}
FLinearColor UCustomizableObjectInstance::GetColorParameterSelectedOption(const FString& ColorParamName) const
{
return Descriptor.GetColorParameterSelectedOption(ColorParamName);
}
void UCustomizableObjectInstance::SetColorParameterSelectedOption(const FString & ColorParamName, const FLinearColor& ColorValue)
{
Descriptor.SetColorParameterSelectedOption(ColorParamName, ColorValue);
}
bool UCustomizableObjectInstance::GetBoolParameterSelectedOption(const FString& BoolParamName) const
{
return Descriptor.GetBoolParameterSelectedOption(BoolParamName);
}
void UCustomizableObjectInstance::SetBoolParameterSelectedOption(const FString& BoolParamName, const bool BoolValue)
{
Descriptor.SetBoolParameterSelectedOption(BoolParamName, BoolValue);
}
void UCustomizableObjectInstance::SetVectorParameterSelectedOption(const FString& VectorParamName, const FLinearColor& VectorValue)
{
Descriptor.SetVectorParameterSelectedOption(VectorParamName, VectorValue);
}
FTransform UCustomizableObjectInstance::GetTransformParameterSelectedOption(const FString& TransformParamName) const
{
return Descriptor.GetTransformParameterSelectedOption(TransformParamName);
}
void UCustomizableObjectInstance::SetTransformParameterSelectedOption(const FString& TransformParamName, const FTransform& TransformValue)
{
Descriptor.SetTransformParameterSelectedOption(TransformParamName, TransformValue);
}
void UCustomizableObjectInstance::SetProjectorValue(const FString& ProjectorParamName,
const FVector& Pos, const FVector& Direction, const FVector& Up, const FVector& Scale,
const float Angle,
const int32 RangeIndex)
{
Descriptor.SetProjectorValue(ProjectorParamName, Pos, Direction, Up, Scale, Angle, RangeIndex);
}
void UCustomizableObjectInstance::SetProjectorPosition(const FString& ProjectorParamName, const FVector& Pos, const int32 RangeIndex)
{
Descriptor.SetProjectorPosition(ProjectorParamName, Pos, RangeIndex);
}
void UCustomizableObjectInstance::SetProjectorDirection(const FString& ProjectorParamName, const FVector& Direction, int32 RangeIndex)
{
Descriptor.SetProjectorDirection(ProjectorParamName, Direction, RangeIndex);
}
void UCustomizableObjectInstance::SetProjectorUp(const FString& ProjectorParamName, const FVector& Up, int32 RangeIndex)
{
Descriptor.SetProjectorUp(ProjectorParamName, Up, RangeIndex);
}
void UCustomizableObjectInstance::SetProjectorScale(const FString& ProjectorParamName, const FVector& Scale, int32 RangeIndex)
{
Descriptor.SetProjectorScale(ProjectorParamName, Scale, RangeIndex);
}
void UCustomizableObjectInstance::SetProjectorAngle(const FString& ProjectorParamName, float Angle, int32 RangeIndex)
{
Descriptor.SetProjectorAngle(ProjectorParamName, Angle, RangeIndex);
}
void UCustomizableObjectInstance::GetProjectorValue(const FString& ProjectorParamName,
FVector& OutPos, FVector& OutDir, FVector& OutUp, FVector& OutScale,
float& OutAngle, ECustomizableObjectProjectorType& OutType,
const int32 RangeIndex) const
{
Descriptor.GetProjectorValue(ProjectorParamName, OutPos, OutDir, OutUp, OutScale, OutAngle, OutType, RangeIndex);
}
void UCustomizableObjectInstance::GetProjectorValueF(const FString& ProjectorParamName,
FVector3f& OutPos, FVector3f& OutDir, FVector3f& OutUp, FVector3f& OutScale,
float& OutAngle, ECustomizableObjectProjectorType& OutType,
int32 RangeIndex) const
{
Descriptor.GetProjectorValueF(ProjectorParamName, OutPos, OutDir, OutUp, OutScale, OutAngle, OutType, RangeIndex);
}
FVector UCustomizableObjectInstance::GetProjectorPosition(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjectorPosition(ParamName, RangeIndex);
}
FVector UCustomizableObjectInstance::GetProjectorDirection(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjectorDirection(ParamName, RangeIndex);
}
FVector UCustomizableObjectInstance::GetProjectorUp(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjectorUp(ParamName, RangeIndex);
}
FVector UCustomizableObjectInstance::GetProjectorScale(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjectorScale(ParamName, RangeIndex);
}
float UCustomizableObjectInstance::GetProjectorAngle(const FString& ParamName, int32 RangeIndex) const
{
return Descriptor.GetProjectorAngle(ParamName, RangeIndex);
}
ECustomizableObjectProjectorType UCustomizableObjectInstance::GetProjectorParameterType(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjectorParameterType(ParamName, RangeIndex);
}
FCustomizableObjectProjector UCustomizableObjectInstance::GetProjector(const FString& ParamName, const int32 RangeIndex) const
{
return Descriptor.GetProjector(ParamName, RangeIndex);
}
bool UCustomizableObjectInstance::ContainsIntParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Int) != INDEX_NONE;
}
bool UCustomizableObjectInstance::ContainsFloatParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Float) != INDEX_NONE;
}
bool UCustomizableObjectInstance::ContainsBoolParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Bool) != INDEX_NONE;
}
bool UCustomizableObjectInstance::ContainsVectorParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Color) != INDEX_NONE;
}
bool UCustomizableObjectInstance::ContainsProjectorParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Projector) != INDEX_NONE;
}
bool UCustomizableObjectInstance::ContainsTransformParameter(const FString& ParameterName) const
{
return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Transform) != INDEX_NONE;
}
int32 UCustomizableObjectInstance::FindIntParameterNameIndex(const FString& ParamName) const
{
return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Int);
}
int32 UCustomizableObjectInstance::FindFloatParameterNameIndex(const FString& ParamName) const
{
return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Float);
}
int32 UCustomizableObjectInstance::FindBoolParameterNameIndex(const FString& ParamName) const
{
return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Bool);
}
int32 UCustomizableObjectInstance::FindVectorParameterNameIndex(const FString& ParamName) const
{
return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Color);
}
int32 UCustomizableObjectInstance::FindProjectorParameterNameIndex(const FString& ParamName) const
{
return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Projector);
}
int32 UCustomizableInstancePrivate::FindIntParameterNameIndex(const FString& ParamName) const
{
return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Int);
}
int32 UCustomizableInstancePrivate::FindFloatParameterNameIndex(const FString& ParamName) const
{
return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Float);
}
int32 UCustomizableInstancePrivate::FindBoolParameterNameIndex(const FString& ParamName) const
{
return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Bool);
}
int32 UCustomizableInstancePrivate::FindVectorParameterNameIndex(const FString& ParamName) const
{
return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Color);
}
int32 UCustomizableInstancePrivate::FindProjectorParameterNameIndex(const FString& ParamName) const
{
return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Projector);
}
#if WITH_EDITOR
void UCustomizableInstancePrivate::UpdateSkeletalMeshAsyncResult(FInstanceUpdateNativeDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority, TSharedPtr<FMutableSystemSettingsOverrides> MutableSystemSettingsOverride)
{
UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate();
const TSharedRef<FUpdateContextPrivate> Context = MakeShared<FUpdateContextPrivate>(*GetPublic());
Context->bIgnoreCloseDist = bIgnoreCloseDist;
Context->bForceHighPriority = bForceHighPriority;
Context->UpdateNativeCallback = Callback;
Context->UpdateSettingsOverride = MutableSystemSettingsOverride;
SystemPrivate->EnqueueUpdateSkeletalMesh(Context);
}
#endif
void UCustomizableObjectInstance::SetRandomValues()
{
Descriptor.SetRandomValues();
}
void UCustomizableObjectInstance::SetRandomValuesFromStream(const FRandomStream& InStream)
{
Descriptor.SetRandomValuesFromStream(InStream);
}
void UCustomizableObjectInstance::SetDefaultValue(const FString& ParamName)
{
UCustomizableObject* CustomizableObject = GetCustomizableObject();
if (!CustomizableObject)
{
return;
}
Descriptor.SetDefaultValue(CustomizableObject->GetPrivate()->FindParameter(ParamName));
}
void UCustomizableObjectInstance::SetDefaultValues()
{
Descriptor.SetDefaultValues();
}
bool UCustomizableInstancePrivate::LoadParametersFromProfile(int32 ProfileIndex)
{
UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject();
if (!CustomizableObject)
{
return false;
}
#if WITH_EDITOR
if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num() )
{
return false;
}
// This could be done only when the instance changes.
MigrateProfileParametersToCurrentInstance(ProfileIndex);
const FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex];
GetPublic()->Descriptor.BoolParameters = Profile.BoolParameters;
GetPublic()->Descriptor.IntParameters = Profile.IntParameters;
GetPublic()->Descriptor.FloatParameters = Profile.FloatParameters;
GetPublic()->Descriptor.TextureParameters = Profile.TextureParameters;
GetPublic()->Descriptor.ProjectorParameters = Profile.ProjectorParameters;
GetPublic()->Descriptor.VectorParameters = Profile.VectorParameters;
GetPublic()->Descriptor.TransformParameters = Profile.TransformParameters;
#endif
return true;
}
bool UCustomizableInstancePrivate::SaveParametersToProfile(int32 ProfileIndex)
{
UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject();
if (!CustomizableObject)
{
return false;
}
#if WITH_EDITOR
bSelectedProfileDirty = ProfileIndex != SelectedProfileIndex;
if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num())
{
return false;
}
FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex];
Profile.BoolParameters = GetPublic()->Descriptor.BoolParameters;
Profile.IntParameters = GetPublic()->Descriptor.IntParameters;
Profile.FloatParameters = GetPublic()->Descriptor.FloatParameters;
Profile.TextureParameters = GetPublic()->Descriptor.TextureParameters;
Profile.ProjectorParameters = GetPublic()->Descriptor.ProjectorParameters;
Profile.VectorParameters = GetPublic()->Descriptor.VectorParameters;
Profile.TransformParameters = GetPublic()->Descriptor.TransformParameters;
#endif
return true;
}
bool UCustomizableInstancePrivate::MigrateProfileParametersToCurrentInstance(int32 ProfileIndex)
{
UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject();
if (!CustomizableObject)
{
return false;
}
#if WITH_EDITOR
if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num())
{
return false;
}
FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex];
FProfileParameterDat TempProfile;
TempProfile.ProfileName = Profile.ProfileName;
TempProfile.BoolParameters = GetPublic()->Descriptor.BoolParameters;
TempProfile.FloatParameters = GetPublic()->Descriptor.FloatParameters;
TempProfile.IntParameters = GetPublic()->Descriptor.IntParameters;
TempProfile.ProjectorParameters = GetPublic()->Descriptor.ProjectorParameters;
TempProfile.TextureParameters = GetPublic()->Descriptor.TextureParameters;
TempProfile.VectorParameters = GetPublic()->Descriptor.VectorParameters;
TempProfile.TransformParameters = GetPublic()->Descriptor.TransformParameters;
// Populate TempProfile with the parameters found in the profile.
// Any profile parameter missing will be discarded.
for (FCustomizableObjectBoolParameterValue& Parameter : TempProfile.BoolParameters)
{
using ParamValType = FCustomizableObjectBoolParameterValue;
ParamValType* Found = Profile.BoolParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValue = Found->ParameterValue;
}
}
for (FCustomizableObjectIntParameterValue& Parameter : TempProfile.IntParameters)
{
using ParamValType = FCustomizableObjectIntParameterValue;
ParamValType* Found = Profile.IntParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValueName = Found->ParameterValueName;
}
}
for (FCustomizableObjectFloatParameterValue& Parameter : TempProfile.FloatParameters)
{
using ParamValType = FCustomizableObjectFloatParameterValue;
ParamValType* Found = Profile.FloatParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValue = Found->ParameterValue;
Parameter.ParameterRangeValues = Found->ParameterRangeValues;
}
}
for (FCustomizableObjectTextureParameterValue& Parameter : TempProfile.TextureParameters)
{
using ParamValType = FCustomizableObjectTextureParameterValue;
ParamValType* Found = Profile.TextureParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValue = Found->ParameterValue;
}
}
for (FCustomizableObjectSkeletalMeshParameterValue& Parameter : TempProfile.SkeletalMeshParameters)
{
using ParamValType = FCustomizableObjectSkeletalMeshParameterValue;
ParamValType* Found = Profile.SkeletalMeshParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValue = Found->ParameterValue;
}
}
for (FCustomizableObjectVectorParameterValue& Parameter : TempProfile.VectorParameters)
{
using ParamValType = FCustomizableObjectVectorParameterValue;
ParamValType* Found = Profile.VectorParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.ParameterValue = Found->ParameterValue;
}
}
for (FCustomizableObjectProjectorParameterValue& Parameter : TempProfile.ProjectorParameters)
{
using ParamValType = FCustomizableObjectProjectorParameterValue;
ParamValType* Found = Profile.ProjectorParameters.FindByPredicate(
[&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; });
if (Found)
{
Parameter.RangeValues = Found->RangeValues;
Parameter.Value = Found->Value;
}
}
Profile = TempProfile;
//CustomizableObject->Modify();
#endif
return true;
}
UCustomizableObjectInstance* UCustomizableInstancePrivate::GetPublic() const
{
UCustomizableObjectInstance* Public = StaticCast<UCustomizableObjectInstance*>(GetOuter());
check(Public);
return Public;
}
void UCustomizableInstancePrivate::SetSelectedParameterProfileDirty()
{
UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject();
if (!CustomizableObject)
{
return;
}
#if WITH_EDITOR
bSelectedProfileDirty = SelectedProfileIndex != INDEX_NONE;
if (bSelectedProfileDirty)
{
CustomizableObject->Modify();
}
#endif
}
bool UCustomizableInstancePrivate::IsSelectedParameterProfileDirty() const
{
#if WITH_EDITOR
return bSelectedProfileDirty && SelectedProfileIndex != INDEX_NONE;
#else
return false;
#endif
}
void UCustomizableInstancePrivate::DiscardResources()
{
check(IsInGameThread());
UCustomizableObjectInstance* Instance = Cast<UCustomizableObjectInstance>(GetOuter());
if (!Instance)
{
return;
}
if (SkeletalMeshStatus == ESkeletalMeshStatus::Success)
{
if (CVarEnableReleaseMeshResources.GetValueOnGameThread())
{
for (const TTuple<FName, TObjectPtr<USkeletalMesh>>& Tuple : SkeletalMeshes)
{
USkeletalMesh* SkeletalMesh = Tuple.Get<1>();
if (SkeletalMesh->IsValidLowLevel() && !SkeletalMesh->HasPendingInitOrStreaming())
{
SkeletalMesh->ReleaseResources();
}
}
}
SkeletalMeshes.Empty();
ReleaseMutableResources(false, *Instance);
}
InvalidateGeneratedData();
}
void UCustomizableInstancePrivate::SetReferenceSkeletalMesh() const
{
UCustomizableObjectInstance* Instance = Cast<UCustomizableObjectInstance>(GetOuter());
if (!Instance)
{
return;
}
UCustomizableObject* CustomizableObject = Instance->GetCustomizableObject();
if (!CustomizableObject)
{
return;
}
const UModelResources* ModelResources = CustomizableObject->GetPrivate()->GetModelResources();
if (!ModelResources)
{
return;
}
for (TObjectIterator<UCustomizableObjectInstanceUsage> It; It; ++It)
{
UCustomizableObjectInstanceUsage* CustomizableObjectInstanceUsage = *It;
if (!IsValid(CustomizableObjectInstanceUsage) || CustomizableObjectInstanceUsage->GetCustomizableObjectInstance() != Instance)
{
continue;
}
#if WITH_EDITOR
if (CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
const FName& ComponentName = CustomizableObjectInstanceUsage->GetComponentName();
const int32 ObjectComponentIndex = ModelResources->ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName);
if (!ModelResources->ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex))
{
continue;
}
if (USkeletalMeshComponent* Parent = CustomizableObjectInstanceUsage->GetAttachParent())
{
Parent->EmptyOverrideMaterials();
TSoftObjectPtr<USkeletalMesh> SoftObjectPtr = ModelResources->ReferenceSkeletalMeshesData[ObjectComponentIndex].SoftSkeletalMesh;
USkeletalMesh* SkeletalMesh = MutablePrivate::LoadObject(SoftObjectPtr);
Parent->SetSkeletalMesh(SkeletalMesh);
}
}
}
namespace
{
inline float unpack_uint8(uint8 i)
{
float res = i;
res -= 127.5f;
res /= 127.5f;
return res;
}
}
bool MutableTextureUsesOfflineProcessedData()
{
#if PLATFORM_DESKTOP || PLATFORM_ANDROID || PLATFORM_IOS
return true;
#else
return false;
#endif
}
void SetTexturePropertiesFromMutableImageProps(UTexture2D* Texture, const FMutableModelImageProperties& Props, bool bNeverStream)
{
#if !PLATFORM_DESKTOP
if (UCustomizableObjectSystem::GetInstance()->GetPrivate()->EnableMutableProgressiveMipStreaming <= 0)
{
Texture->NeverStream = true;
}
else
{
Texture->NeverStream = bNeverStream;
}
#else
Texture->NeverStream = bNeverStream;
#endif
Texture->bNotOfflineProcessed = !MutableTextureUsesOfflineProcessedData();
Texture->SRGB = Props.SRGB;
Texture->Filter = Props.Filter;
Texture->LODBias = Props.LODBias;
if (Props.MipGenSettings == TextureMipGenSettings::TMGS_NoMipmaps)
{
Texture->NeverStream = true;
}
#if WITH_EDITORONLY_DATA
Texture->MipGenSettings = Props.MipGenSettings;
Texture->bFlipGreenChannel = Props.FlipGreenChannel;
#endif
Texture->LODGroup = Props.LODGroup;
Texture->AddressX = Props.AddressX;
Texture->AddressY = Props.AddressY;
}
UCustomizableInstancePrivate* UCustomizableObjectInstance::GetPrivate() const
{
check(PrivateData); // Currently this is initialized in the constructor so we expect it always to exist.
return PrivateData;
}
FMutableUpdateCandidate::FMutableUpdateCandidate(UCustomizableObjectInstance* InCustomizableObjectInstance): CustomizableObjectInstance(InCustomizableObjectInstance)
{
const FCustomizableObjectInstanceDescriptor& Descriptor = InCustomizableObjectInstance->GetPrivate()->GetDescriptor();
MinLOD = Descriptor.MinLOD;
QualitySettingMinLODs = Descriptor.QualitySettingMinLODs;
FirstRequestedLOD = Descriptor.GetFirstRequestedLOD();
}
bool FMutableUpdateCandidate::HasBeenIssued() const
{
return bHasBeenIssued;
}
void FMutableUpdateCandidate::Issue()
{
bHasBeenIssued = true;
}
void FMutableUpdateCandidate::ApplyLODUpdateParamsToInstance(FUpdateContextPrivate& Context)
{
CustomizableObjectInstance->Descriptor.MinLOD = MinLOD;
CustomizableObjectInstance->Descriptor.QualitySettingMinLODs = QualitySettingMinLODs;
CustomizableObjectInstance->Descriptor.FirstRequestedLOD = FirstRequestedLOD;
Context.SetMinLOD(MinLOD);
Context.SetQualitySettingMinLODs(QualitySettingMinLODs);
Context.SetFirstRequestedLOD(FirstRequestedLOD);
}
// The memory allocated in the function and pointed by the returned pointer is owned by the caller and must be freed.
// If assigned to a UTexture2D, it will be freed by that UTexture2D
FTexturePlatformData* MutableCreateImagePlatformData(TSharedPtr<const mu::FImage> MutableImage, int32 OnlyLOD, uint16 FullSizeX, uint16 FullSizeY)
{
int32 SizeX = FMath::Max(MutableImage->GetSize()[0], FullSizeX);
int32 SizeY = FMath::Max(MutableImage->GetSize()[1], FullSizeY);
if (SizeX <= 0 || SizeY <= 0)
{
UE_LOG(LogMutable, Warning, TEXT("Invalid parameters specified for UCustomizableInstancePrivate::MutableCreateImagePlatformData()"));
return nullptr;
}
int32 FirstLOD = 0;
for (int32 l = 0; l < OnlyLOD; ++l)
{
if (SizeX <= 4 || SizeY <= 4)
{
break;
}
SizeX = FMath::Max(SizeX / 2, 1);
SizeY = FMath::Max(SizeY / 2, 1);
++FirstLOD;
}
int32 MaxSize = FMath::Max(SizeX, SizeY);
int32 FullLODCount = 1;
int32 MipsToSkip = 0;
if (OnlyLOD < 0)
{
FullLODCount = FMath::CeilLogTwo(MaxSize) + 1;
MipsToSkip = FullLODCount - MutableImage->GetLODCount();
check(MipsToSkip >= 0);
}
// Reduce final texture size if we surpass the max size we can generate.
UCustomizableObjectSystem* System = UCustomizableObjectSystem::GetInstance();
UCustomizableObjectSystemPrivate* SystemPrivate = System ? System->GetPrivate() : nullptr;
int32 MaxTextureSizeToGenerate = SystemPrivate ? SystemPrivate->MaxTextureSizeToGenerate : 0;
if (MaxTextureSizeToGenerate > 0)
{
// Skip mips only if texture streaming is disabled
const bool bIsStreamingEnabled = MipsToSkip > 0;
// Skip mips if the texture surpasses a certain size
if (MaxSize > MaxTextureSizeToGenerate && !bIsStreamingEnabled && OnlyLOD < 0)
{
// Skip mips until MaxSize is equal or less than MaxTextureSizeToGenerate or there aren't more mips to skip
while (MaxSize > MaxTextureSizeToGenerate && FirstLOD < (FullLODCount - 1))
{
MaxSize = MaxSize >> 1;
FirstLOD++;
}
// Update SizeX and SizeY
SizeX = SizeX >> FirstLOD;
SizeY = SizeY >> FirstLOD;
}
}
if (MutableImage->GetLODCount() == 1)
{
MipsToSkip = 0;
FullLODCount = 1;
FirstLOD = 0;
}
int32 EndLOD = OnlyLOD < 0 ? FullLODCount : FirstLOD + 1;
mu::EImageFormat MutableFormat = MutableImage->GetFormat();
int32 MaxPossibleSize = 0;
if (MaxTextureSizeToGenerate > 0)
{
MaxPossibleSize = int32(FMath::Pow(2.f, float(FullLODCount - FirstLOD - 1)));
}
else
{
MaxPossibleSize = int32(FMath::Pow(2.f, float(FullLODCount - 1)));
}
// This could happen with non-power-of-two images.
//check(SizeX == MaxPossibleSize || SizeY == MaxPossibleSize || FullLODCount == 1);
if (!(SizeX == MaxPossibleSize || SizeY == MaxPossibleSize || FullLODCount == 1))
{
UE_LOG(LogMutable, Warning, TEXT("Building instance: unsupported texture size %d x %d."), SizeX, SizeY);
//return nullptr;
}
mu::FImageOperator ImOp = mu::FImageOperator::GetDefault(mu::FImageOperator::FImagePixelFormatFunc());
EPixelFormat PlatformFormat = PF_Unknown;
switch (MutableFormat)
{
case mu::EImageFormat::RGB_UByte:
// performance penalty. can happen in states that remove compression.
PlatformFormat = PF_R8G8B8A8;
UE_LOG(LogMutable, Display, TEXT("Building instance: a texture was generated in a format not supported by the hardware (RGB), this results in an additional conversion, so a performance penalty."));
break;
case mu::EImageFormat::BGRA_UByte:
// performance penalty. can happen with texture parameter images.
PlatformFormat = PF_R8G8B8A8;
UE_LOG(LogMutable, Display, TEXT("Building instance: a texture was generated in a format not supported by the hardware (BGRA), this results in an additional conversion, so a performance penalty."));
break;
// Good cases:
case mu::EImageFormat::RGBA_UByte: PlatformFormat = PF_R8G8B8A8; break;
case mu::EImageFormat::BC1: PlatformFormat = PF_DXT1; break;
case mu::EImageFormat::BC2: PlatformFormat = PF_DXT3; break;
case mu::EImageFormat::BC3: PlatformFormat = PF_DXT5; break;
case mu::EImageFormat::BC4: PlatformFormat = PF_BC4; break;
case mu::EImageFormat::BC5: PlatformFormat = PF_BC5; break;
case mu::EImageFormat::L_UByte: PlatformFormat = PF_G8; break;
case mu::EImageFormat::ASTC_4x4_RGB_LDR: PlatformFormat = PF_ASTC_4x4; break;
case mu::EImageFormat::ASTC_4x4_RGBA_LDR:PlatformFormat = PF_ASTC_4x4; break;
case mu::EImageFormat::ASTC_4x4_RG_LDR: PlatformFormat = PF_ASTC_4x4; break;
default:
// Cannot prepare texture if it's not in the right format, this can happen if mutable is in debug mode or in case of bugs
UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in an unsupported format, it will be converted to Unreal with a performance penalty."));
switch (mu::GetImageFormatData(MutableFormat).Channels)
{
case 1:
PlatformFormat = PF_R8;
MutableImage = ImOp.ImagePixelFormat(0, MutableImage.Get(), mu::EImageFormat::L_UByte);
break;
case 2:
case 3:
case 4:
PlatformFormat = PF_R8G8B8A8;
MutableImage = ImOp.ImagePixelFormat(0, MutableImage.Get(), mu::EImageFormat::RGBA_UByte);
break;
default:
// Absolutely worst case
return nullptr;
}
}
FTexturePlatformData* PlatformData = new FTexturePlatformData();
PlatformData->SizeX = SizeX;
PlatformData->SizeY = SizeY;
PlatformData->PixelFormat = PlatformFormat;
// Allocate mipmaps.
if (!FMath::IsPowerOfTwo(SizeX) || !FMath::IsPowerOfTwo(SizeY))
{
EndLOD = FirstLOD + 1;
MipsToSkip = 0;
FullLODCount = 1;
}
for (int32 MipLevelUE = FirstLOD; MipLevelUE < EndLOD; ++MipLevelUE)
{
int32 MipLevelMutable = MipLevelUE - MipsToSkip;
// Unlike Mutable, UE expects MIPs sizes to be at least the size of the compression block.
// For example, a 8x8 PF_DXT1 texture will have the following MIPs:
// Mutable Unreal Engine
// 8x8 8x8
// 4x4 4x4
// 2x2 4x4
// 1x1 4x4
//
// Notice that even though Mutable reports MIP smaller than the block size, the actual data contains at least a block.
FTexture2DMipMap* Mip = new FTexture2DMipMap( FMath::Max(SizeX, GPixelFormats[PlatformFormat].BlockSizeX)
, FMath::Max(SizeY, GPixelFormats[PlatformFormat].BlockSizeY));
PlatformData->Mips.Add(Mip);
if(MipLevelUE >= MipsToSkip || OnlyLOD>=0)
{
check(MipLevelMutable >= 0);
check(MipLevelMutable < MutableImage->GetLODCount());
Mip->BulkData.Lock(LOCK_READ_WRITE);
Mip->BulkData.ClearBulkDataFlags(BULKDATA_SingleUse);
const uint8* MutableData = MutableImage->GetLODData(MipLevelMutable);
const uint32 SourceDataSize = MutableImage->GetLODDataSize(MipLevelMutable);
uint32 DestDataSize = (MutableFormat == mu::EImageFormat::RGB_UByte)
? (SourceDataSize/3) * 4
: SourceDataSize;
void* pData = Mip->BulkData.Realloc(DestDataSize);
// Special inefficient cases
if (MutableFormat== mu::EImageFormat::BGRA_UByte)
{
check(SourceDataSize==DestDataSize);
MUTABLE_CPUPROFILER_SCOPE(Innefficent_BGRA_Format_Conversion);
uint8_t* pDest = reinterpret_cast<uint8_t*>(pData);
for (size_t p = 0; p < SourceDataSize / 4; ++p)
{
pDest[p * 4 + 0] = MutableData[p * 4 + 2];
pDest[p * 4 + 1] = MutableData[p * 4 + 1];
pDest[p * 4 + 2] = MutableData[p * 4 + 0];
pDest[p * 4 + 3] = MutableData[p * 4 + 3];
}
}
else if (MutableFormat == mu::EImageFormat::RGB_UByte)
{
MUTABLE_CPUPROFILER_SCOPE(Innefficent_RGB_Format_Conversion);
uint8_t* pDest = reinterpret_cast<uint8_t*>(pData);
for (size_t p = 0; p < SourceDataSize / 3; ++p)
{
pDest[p * 4 + 0] = MutableData[p * 3 + 0];
pDest[p * 4 + 1] = MutableData[p * 3 + 1];
pDest[p * 4 + 2] = MutableData[p * 3 + 2];
pDest[p * 4 + 3] = 255;
}
}
// Normal case
else
{
check(SourceDataSize == DestDataSize);
FMemory::Memcpy(pData, MutableData, SourceDataSize);
}
Mip->BulkData.Unlock();
}
else
{
Mip->BulkData.SetBulkDataFlags(BULKDATA_PayloadInSeperateFile);
Mip->BulkData.ClearBulkDataFlags(BULKDATA_PayloadAtEndOfFile);
}
SizeX /= 2;
SizeY /= 2;
SizeX = SizeX > 0 ? SizeX : 1;
SizeY = SizeY > 0 ? SizeY : 1;
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Some consistency checks for dev builds
int32 BulkDataCount = 0;
for (int32 i = 0; i < PlatformData->Mips.Num(); ++i)
{
if (i > 0)
{
check(PlatformData->Mips[i].SizeX == PlatformData->Mips[i - 1].SizeX / 2 || PlatformData->Mips[i].SizeX == GPixelFormats[PlatformFormat].BlockSizeX);
check(PlatformData->Mips[i].SizeY == PlatformData->Mips[i - 1].SizeY / 2 || PlatformData->Mips[i].SizeY == GPixelFormats[PlatformFormat].BlockSizeY);
}
if (PlatformData->Mips[i].BulkData.GetBulkDataSize() > 0)
{
BulkDataCount++;
}
}
if (MaxTextureSizeToGenerate > 0)
{
check(FullLODCount == 1 || OnlyLOD >= 0 || (BulkDataCount == (MutableImage->GetLODCount() - FirstLOD)));
}
else
{
check(FullLODCount == 1 || OnlyLOD >= 0 || (BulkDataCount == MutableImage->GetLODCount()));
}
#endif
return PlatformData;
}
void ConvertImage(UTexture2D* Texture, TSharedPtr<const mu::FImage> MutableImage, const FMutableModelImageProperties& Props, int OnlyLOD, int32 ExtractChannel)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::ConvertImage);
SetTexturePropertiesFromMutableImageProps(Texture, Props, false);
mu::EImageFormat MutableFormat = MutableImage->GetFormat();
// Extract a single channel, if requested.
if (ExtractChannel >= 0)
{
mu::FImageOperator ImOp = mu::FImageOperator::GetDefault(mu::FImageOperator::FImagePixelFormatFunc());
MutableImage = ImOp.ImagePixelFormat( 4, MutableImage.Get(), mu::EImageFormat::RGBA_UByte );
uint8_t Channel = uint8_t( FMath::Clamp(ExtractChannel,0,3) );
MutableImage = ImOp.ImageSwizzle( mu::EImageFormat::L_UByte, &MutableImage, &Channel );
MutableFormat = mu::EImageFormat::L_UByte;
}
// Hack: This format is unsupported in UE, but it shouldn't happen in production.
if (MutableFormat == mu::EImageFormat::RGB_UByte)
{
UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in RGB format, which is slow to convert to Unreal."));
// Expand the image.
TSharedPtr<mu::FImage> Converted = MakeShared<mu::FImage>(MutableImage->GetSizeX(), MutableImage->GetSizeY(), MutableImage->GetLODCount(), mu::EImageFormat::RGBA_UByte, mu::EInitializationType::NotInitialized);
for (int32 LODIndex = 0; LODIndex < Converted->GetLODCount(); ++LODIndex)
{
int32 PixelCount = MutableImage->GetLODDataSize(LODIndex)/3;
const uint8* pSource = MutableImage->GetMipData(LODIndex);
uint8* pTarget = Converted->GetMipData(LODIndex);
for (int32 p = 0; p < PixelCount; ++p)
{
pTarget[4 * p + 0] = pSource[3 * p + 0];
pTarget[4 * p + 1] = pSource[3 * p + 1];
pTarget[4 * p + 2] = pSource[3 * p + 2];
pTarget[4 * p + 3] = 255;
}
}
MutableImage = Converted;
MutableFormat = mu::EImageFormat::RGBA_UByte;
}
else if (MutableFormat == mu::EImageFormat::BGRA_UByte)
{
UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in BGRA format, which is slow to convert to Unreal."));
MUTABLE_CPUPROFILER_SCOPE(Swizzle);
// Swizzle the image.
// \TODO: Raise a warning?
TSharedPtr<mu::FImage> Converted = MakeShared<mu::FImage>(MutableImage->GetSizeX(), MutableImage->GetSizeY(), 1, mu::EImageFormat::RGBA_UByte, mu::EInitializationType::NotInitialized);
int32 PixelCount = MutableImage->GetSizeX() * MutableImage->GetSizeY();
const uint8* pSource = MutableImage->GetLODData(0);
uint8* pTarget = Converted->GetLODData(0);
for (int32 p = 0; p < PixelCount; ++p)
{
pTarget[4 * p + 0] = pSource[4 * p + 2];
pTarget[4 * p + 1] = pSource[4 * p + 1];
pTarget[4 * p + 2] = pSource[4 * p + 0];
pTarget[4 * p + 3] = pSource[4 * p + 3];
}
MutableImage = Converted;
MutableFormat = mu::EImageFormat::RGBA_UByte;
}
if (OnlyLOD >= 0)
{
OnlyLOD = FMath::Min( OnlyLOD, MutableImage->GetLODCount()-1 );
}
Texture->SetPlatformData(MutableCreateImagePlatformData(MutableImage,OnlyLOD,0,0) );
}
static int32 EnableRayTracingFix = 0;
FAutoConsoleVariableRef CVarMutableEnableRayTracingFix(
TEXT("mutable.EnableRayTracingFix"),
EnableRayTracingFix,
TEXT("If 0, Disabled. Generated meshes will have ray tracing enabled.")
TEXT("If 1, Enable fix for meshes with mesh LOD streaming. Meshes will have ray tracing disabled.")
TEXT("If 2, Enable fix for all generated meshes. Meshes will have ray tracing disabled.")
);
void UCustomizableInstancePrivate::InitSkeletalMeshData(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh* SkeletalMesh, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, const UCustomizableObject& CustomizableObject, FCustomizableObjectComponentIndex ObjectComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::InitSkeletalMesh);
check(SkeletalMesh);
const FName ComponentName = OperationData->ComponentNames[ObjectComponentIndex.GetValue()];
SkeletalMesh->NeverStream = !OperationData->bStreamMeshLODs;
SkeletalMesh->SetImportedBounds(RefSkeletalMeshData.Bounds);
SkeletalMesh->SetPostProcessAnimBlueprint(RefSkeletalMeshData.PostProcessAnimInst.Get());
SkeletalMesh->SetShadowPhysicsAsset(RefSkeletalMeshData.ShadowPhysicsAsset.Get());
const bool bEnableRayTracingFix = EnableRayTracingFix == 2 || (EnableRayTracingFix == 1 && OperationData->bStreamMeshLODs);
if (bEnableRayTracingFix)
{
SkeletalMesh->SetSupportRayTracing(false);
}
SkeletalMesh->SetHasVertexColors(false);
// Set the default Physics Assets
SkeletalMesh->SetPhysicsAsset(RefSkeletalMeshData.PhysicsAsset.Get());
SkeletalMesh->SetEnablePerPolyCollision(RefSkeletalMeshData.Settings.bEnablePerPolyCollision);
// Asset User Data
{
const FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
check(ComponentData);
for (TObjectPtr<UAssetUserData> AssetUserData : ComponentData->AssetUserDataArray)
{
SkeletalMesh->AddAssetUserData(AssetUserData);
}
//Custom Asset User Data
if (OperationData->Instance->GetAnimationGameplayTags().Num() ||
ComponentData->AnimSlotToBP.Num())
{
UCustomizableObjectInstanceUserData* InstanceData = NewObject<UCustomizableObjectInstanceUserData>(SkeletalMesh, NAME_None, RF_Public | RF_Transactional);
InstanceData->AnimationGameplayTag = OperationData->Instance->GetAnimationGameplayTags();
for (const TTuple<FName, TSoftClassPtr<UAnimInstance>>& AnimSlot : ComponentData->AnimSlotToBP)
{
FCustomizableObjectAnimationSlot AnimationSlot;
AnimationSlot.Name = AnimSlot.Key;
AnimationSlot.AnimInstance = AnimSlot.Value;
InstanceData->AnimationSlots.Add(AnimationSlot);
}
SkeletalMesh->AddAssetUserData(InstanceData);
}
}
// Allocate resources for rendering and add LOD Info
{
MUTABLE_CPUPROFILER_SCOPE(InitSkeletalMesh_AddLODData);
SkeletalMesh->AllocateResourceForRendering();
FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering();
int32 NumLODsAvailablePerComponent = OperationData->NumLODsAvailable[ComponentName];
RenderData->NumInlinedLODs = NumLODsAvailablePerComponent - OperationData->FirstResidentLOD[ComponentName];
RenderData->NumNonOptionalLODs = NumLODsAvailablePerComponent - OperationData->FirstLODAvailable[ComponentName];
RenderData->CurrentFirstLODIdx = OperationData->FirstResidentLOD[ComponentName];
RenderData->PendingFirstLODIdx = RenderData->CurrentFirstLODIdx;
RenderData->LODBiasModifier = OperationData->FirstLODAvailable[ComponentName];
if (bEnableRayTracingFix)
{
RenderData->bSupportRayTracing = false;
}
for (int32 LODIndex = 0; LODIndex < NumLODsAvailablePerComponent; ++LODIndex)
{
RenderData->LODRenderData.Add(new FSkeletalMeshLODRenderData());
FSkeletalMeshLODRenderData& LODRenderData = RenderData->LODRenderData[LODIndex];
LODRenderData.bIsLODOptional = LODIndex < OperationData->FirstLODAvailable[ComponentName];
LODRenderData.bStreamedDataInlined = LODIndex >= OperationData->FirstResidentLOD[ComponentName];
const FMutableRefLODData& LODData = RefSkeletalMeshData.LODData[LODIndex];
FSkeletalMeshLODInfo& LODInfo = SkeletalMesh->AddLODInfo();
LODInfo.ScreenSize = LODData.LODInfo.ScreenSize;
LODInfo.LODHysteresis = LODData.LODInfo.LODHysteresis;
LODInfo.bSupportUniformlyDistributedSampling = LODData.LODInfo.bSupportUniformlyDistributedSampling;
LODInfo.bAllowCPUAccess = LODData.LODInfo.bAllowCPUAccess;
if (bEnableRayTracingFix)
{
LODInfo.SkinCacheUsage = ESkinCacheUsage::Disabled;
}
// Disable LOD simplification when baking instances
LODInfo.ReductionSettings.NumOfTrianglesPercentage = 1.f;
LODInfo.ReductionSettings.NumOfVertPercentage = 1.f;
LODInfo.ReductionSettings.MaxNumOfTriangles = TNumericLimits<uint32>::Max();
LODInfo.ReductionSettings.MaxNumOfVerts = TNumericLimits<uint32>::Max();
LODInfo.ReductionSettings.bRecalcNormals = 0;
LODInfo.ReductionSettings.WeldingThreshold = TNumericLimits<float>::Min();
LODInfo.ReductionSettings.bMergeCoincidentVertBones = 0;
LODInfo.ReductionSettings.bImproveTrianglesForCloth = 0;
#if WITH_EDITORONLY_DATA
LODInfo.ReductionSettings.MaxNumOfTrianglesPercentage = TNumericLimits<uint32>::Max();
LODInfo.ReductionSettings.MaxNumOfVertsPercentage = TNumericLimits<uint32>::Max();
LODInfo.BuildSettings.bRecomputeNormals = false;
LODInfo.BuildSettings.bRecomputeTangents = false;
LODInfo.BuildSettings.bUseMikkTSpace = false;
LODInfo.BuildSettings.bComputeWeightedNormals = false;
LODInfo.BuildSettings.bRemoveDegenerates = false;
LODInfo.BuildSettings.bUseHighPrecisionTangentBasis = false;
LODInfo.BuildSettings.bUseHighPrecisionSkinWeights = false;
LODInfo.BuildSettings.bUseFullPrecisionUVs = true;
LODInfo.BuildSettings.bUseBackwardsCompatibleF16TruncUVs = false;
LODInfo.BuildSettings.ThresholdPosition = TNumericLimits<float>::Min();
LODInfo.BuildSettings.ThresholdTangentNormal = TNumericLimits<float>::Min();
LODInfo.BuildSettings.ThresholdUV = TNumericLimits<float>::Min();
LODInfo.BuildSettings.MorphThresholdPosition = TNumericLimits<float>::Min();
LODInfo.BuildSettings.BoneInfluenceLimit = 0;
#endif
LODInfo.LODMaterialMap.SetNumZeroed(1);
}
}
if (RefSkeletalMeshData.SkeletalMeshLODSettings)
{
#if WITH_EDITORONLY_DATA
SkeletalMesh->SetLODSettings(RefSkeletalMeshData.SkeletalMeshLODSettings);
#else
// This is the part from the above SkeletalMesh->SetLODSettings that's available in-game
RefSkeletalMeshData.SkeletalMeshLODSettings->SetLODSettingsToMesh(SkeletalMesh);
#endif
}
// Set Min LOD (Override the Reference Skeletal Mesh LOD Settings)
const UModelResources& ModelResources = CustomizableObject.GetPrivate()->GetModelResourcesChecked();
SkeletalMesh->SetMinLod(FMath::Max(ModelResources.MinLODPerComponent.FindChecked(ComponentName).GetDefault(), static_cast<int32>(OperationData->FirstLODAvailable[ComponentName])));
SkeletalMesh->SetQualityLevelMinLod(ModelResources.MinQualityLevelLODPerComponent.FindChecked(ComponentName));
// Set up unreal's default material, will be replaced when building materials
{
MUTABLE_CPUPROFILER_SCOPE(InitSkeletalMesh_AddDefaultMaterial);
UMaterialInterface* UnrealMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
SkeletalMesh->GetMaterials().SetNum(1);
SkeletalMesh->GetMaterials()[0] = UnrealMaterial;
// Default density
SetMeshUVChannelDensity(SkeletalMesh->GetMaterials()[0].UVChannelData);
}
}
bool UCustomizableInstancePrivate::BuildSkeletonData(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh& SkeletalMesh, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, UCustomizableObject& CustomizableObject, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildSkeletonData);
FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(InstanceComponentIndex);
bool bCreatedNewSkeleton = false;
const TObjectPtr<USkeleton> Skeleton = MergeSkeletons(CustomizableObject, RefSkeletalMeshData, ObjectComponentIndex, bCreatedNewSkeleton);
if (!Skeleton)
{
return false;
}
SkeletalMesh.SetSkeleton(Skeleton);
SkeletalMesh.SetRefSkeleton(Skeleton->GetReferenceSkeleton());
FReferenceSkeleton& ReferenceSkeleton = SkeletalMesh.GetRefSkeleton();
const TArray<FMeshBoneInfo>& RawRefBoneInfo = ReferenceSkeleton.GetRawRefBoneInfo();
const int32 RawRefBoneCount = ReferenceSkeleton.GetRawBoneNum();
const TArray<FInstanceUpdateData::FBone>& BonePose = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BonePose;
TMap<mu::FBoneName, TPair<FName,uint16>>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap;
{
MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_BuildBoneInfoMap);
BoneInfoMap.Reserve(RawRefBoneCount);
const UModelResources& ModelResources = CustomizableObject.GetPrivate()->GetModelResourcesChecked();
for (int32 Index = 0; Index < RawRefBoneCount; ++Index)
{
const FName BoneName = RawRefBoneInfo[Index].Name;
const FString BoneNameString = BoneName.ToString().ToLower();
if(const uint32* Hash = ModelResources.BoneNamesMap.Find(BoneNameString))
{
const mu::FBoneName Bone(*Hash);
TPair<FName, uint16>& BoneInfo = BoneInfoMap.Add(Bone);
BoneInfo.Key = BoneName;
BoneInfo.Value = Index;
}
}
}
{
MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_EnsureBonesExist);
// Ensure all required bones are present in the skeleton
for (const FInstanceUpdateData::FBone& Bone : BonePose)
{
if (!BoneInfoMap.Find(Bone.Name))
{
UE_LOG(LogMutable, Warning, TEXT("The skeleton of skeletal mesh [%s] is missing a bone with ID [%d], which the mesh requires."),
*SkeletalMesh.GetName(), Bone.Name.Id);
return false;
}
}
}
{
MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_ApplyPose);
TArray<FMatrix44f>& RefBasesInvMatrix = SkeletalMesh.GetRefBasesInvMatrix();
RefBasesInvMatrix.Empty(RawRefBoneCount);
// Calculate the InvRefMatrices to ensure all transforms are there for the second step
SkeletalMesh.CalculateInvRefMatrices();
// First step is to update the RefBasesInvMatrix for the bones.
for (const FInstanceUpdateData::FBone& Bone : BonePose)
{
const int32 BoneIndex = BoneInfoMap[Bone.Name].Value;
RefBasesInvMatrix[BoneIndex] = Bone.MatrixWithScale;
}
// The second step is to update the pose transforms in the ref skeleton from the BasesInvMatrix
FReferenceSkeletonModifier SkeletonModifier(ReferenceSkeleton, Skeleton);
for (int32 RefSkelBoneIndex = 0; RefSkelBoneIndex < RawRefBoneCount; ++RefSkelBoneIndex)
{
int32 ParentBoneIndex = ReferenceSkeleton.GetParentIndex(RefSkelBoneIndex);
if (ParentBoneIndex >= 0)
{
const FTransform3f BonePoseTransform(
RefBasesInvMatrix[RefSkelBoneIndex].Inverse() * RefBasesInvMatrix[ParentBoneIndex]);
SkeletonModifier.UpdateRefPoseTransform(RefSkelBoneIndex, (FTransform)BonePoseTransform);
}
}
// Force a CalculateInvRefMatrices
RefBasesInvMatrix.Empty(RawRefBoneCount);
}
{
MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_CalcInvRefMatrices);
SkeletalMesh.CalculateInvRefMatrices();
}
USkeleton* GeneratedSkeleton = SkeletalMesh.GetSkeleton();
if(GeneratedSkeleton && bCreatedNewSkeleton)
{
// If the skeleton is new, it means it has just been merged and the retargeting modes need merging too as the
// MergeSkeletons function doesn't do it. Only do it for newly generated ones, not for cached or non-transient ones.
GeneratedSkeleton->RecreateBoneTree(&SkeletalMesh);
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
check(ComponentData);
TArray<TObjectPtr<USkeleton>>& SkeletonsToMerge = ComponentData->Skeletons.SkeletonsToMerge;
check(SkeletonsToMerge.Num() > 1);
TMap<FName, EBoneTranslationRetargetingMode::Type> BoneNamesToRetargetingMode;
const int32 NumberOfSkeletons = SkeletonsToMerge.Num();
for (int32 SkeletonIndex = 0; SkeletonIndex < NumberOfSkeletons; ++SkeletonIndex)
{
const USkeleton* ToMergeSkeleton = SkeletonsToMerge[SkeletonIndex];
const FReferenceSkeleton& ToMergeReferenceSkeleton = ToMergeSkeleton->GetReferenceSkeleton();
const TArray<FMeshBoneInfo>& Bones = ToMergeReferenceSkeleton.GetRawRefBoneInfo();
const int32 NumBones = Bones.Num();
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
const FMeshBoneInfo& Bone = Bones[BoneIndex];
EBoneTranslationRetargetingMode::Type RetargetingMode = ToMergeSkeleton->GetBoneTranslationRetargetingMode(BoneIndex, false);
BoneNamesToRetargetingMode.Add(Bone.Name, RetargetingMode);
}
}
for (const auto& Pair : BoneNamesToRetargetingMode)
{
const FName& BoneName = Pair.Key;
const EBoneTranslationRetargetingMode::Type& RetargetingMode = Pair.Value;
const int32 BoneIndex = GeneratedSkeleton->GetReferenceSkeleton().FindRawBoneIndex(BoneName);
if (BoneIndex >= 0)
{
GeneratedSkeleton->SetBoneTranslationRetargetingMode(BoneIndex, RetargetingMode);
}
}
}
return true;
}
void UCustomizableInstancePrivate::BuildMeshSockets(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh* SkeletalMesh, const UModelResources& ModelResources, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, TSharedPtr<const mu::FMesh> MutableMesh)
{
// Build mesh sockets.
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildMeshSockets);
check(SkeletalMesh);
const uint32 SocketCount = RefSkeletalMeshData.Sockets.Num();
TArray<TObjectPtr<USkeletalMeshSocket>>& Sockets = SkeletalMesh->GetMeshOnlySocketList();
Sockets.Empty(SocketCount);
TMap<FName, TTuple<int32, int32>> SocketMap; // Maps Socket name to Sockets Array index and priority
// Add sockets used by the SkeletalMesh of reference.
{
MUTABLE_CPUPROFILER_SCOPE(BuildMeshSockets_RefMeshSockets);
for (uint32 SocketIndex = 0; SocketIndex < SocketCount; ++SocketIndex)
{
const FMutableRefSocket& RefSocket = RefSkeletalMeshData.Sockets[SocketIndex];
USkeletalMeshSocket* Socket = NewObject<USkeletalMeshSocket>(SkeletalMesh, RefSocket.SocketName);
Socket->SocketName = RefSocket.SocketName;
Socket->BoneName = RefSocket.BoneName;
Socket->RelativeLocation = RefSocket.RelativeLocation;
Socket->RelativeRotation = RefSocket.RelativeRotation;
Socket->RelativeScale = RefSocket.RelativeScale;
Socket->bForceAlwaysAnimated = RefSocket.bForceAlwaysAnimated;
const int32 LastIndex = Sockets.Add(Socket);
SocketMap.Add(Socket->SocketName, TTuple<int32, int32>(LastIndex, RefSocket.Priority));
}
}
// Add or update sockets modified by Mutable.
if (MutableMesh)
{
MUTABLE_CPUPROFILER_SCOPE(BuildMeshSockets_MutableSockets);
for (int32 TagIndex = 0; TagIndex < MutableMesh->GetTagCount(); ++TagIndex)
{
FString Tag = MutableMesh->GetTag(TagIndex);
if (Tag.RemoveFromStart("__Socket:"))
{
check(Tag.IsNumeric());
const int32 MutableSocketIndex = FCString::Atoi(*Tag);
if (ModelResources.SocketArray.IsValidIndex(MutableSocketIndex))
{
const FMutableRefSocket& MutableSocket = ModelResources.SocketArray[MutableSocketIndex];
int32 IndexToWriteSocket = -1;
if (TTuple<int32, int32>* FoundSocket = SocketMap.Find(MutableSocket.SocketName))
{
if (FoundSocket->Value < MutableSocket.Priority)
{
// Overwrite the existing socket because the new mesh part one is higher priority
IndexToWriteSocket = FoundSocket->Key;
FoundSocket->Value = MutableSocket.Priority;
}
}
else
{
// New Socket
USkeletalMeshSocket* Socket = NewObject<USkeletalMeshSocket>(SkeletalMesh, MutableSocket.SocketName);
IndexToWriteSocket = Sockets.Add(Socket);
SocketMap.Add(MutableSocket.SocketName, TTuple<int32, int32>(IndexToWriteSocket, MutableSocket.Priority));
}
if (IndexToWriteSocket >= 0)
{
check(Sockets.IsValidIndex(IndexToWriteSocket));
USkeletalMeshSocket* SocketToWrite = Sockets[IndexToWriteSocket];
SocketToWrite->SocketName = MutableSocket.SocketName;
SocketToWrite->BoneName = MutableSocket.BoneName;
SocketToWrite->RelativeLocation = MutableSocket.RelativeLocation;
SocketToWrite->RelativeRotation = MutableSocket.RelativeRotation;
SocketToWrite->RelativeScale = MutableSocket.RelativeScale;
SocketToWrite->bForceAlwaysAnimated = MutableSocket.bForceAlwaysAnimated;
}
}
}
}
}
#if !WITH_EDITOR
SkeletalMesh->RebuildSocketMap();
#endif // !WITH_EDITOR
}
void UCustomizableInstancePrivate::BuildOrCopyElementData(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh* SkeletalMesh, UCustomizableObject& CustomizableObject,
FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyElementData);
const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex);
if (!Component)
{
return;
}
const FName ComponentName = OperationData->ComponentNames[Component->Id.GetValue()];
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex];
if (!LOD.SurfaceCount)
{
continue;
}
for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex)
{
new(SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections) FSkelMeshRenderSection();
}
}
}
void UCustomizableInstancePrivate::BuildOrCopyMorphTargetsData(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh* SkeletalMesh, const USkeletalMesh* LastUpdateSkeletalMesh, UCustomizableObject& CustomizableObject, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyMorphTargetsData);
// This is a bit redundant as ComponentMorphTargets should not be generated.
if (!CVarEnableRealTimeMorphTargets.GetValueOnAnyThread())
{
return;
}
if (!SkeletalMesh)
{
return;
}
FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(InstanceComponentIndex);
const FName ComponentName = OperationData->ComponentNames[ObjectComponentIndex.GetValue()];
FSkeletalMeshMorphTargets* ComponentMorphTargets = OperationData->InstanceUpdateData.RealTimeMorphTargets.Find(ComponentName);
if (!ComponentMorphTargets)
{
return;
}
const int32 NumMorphTargets = ComponentMorphTargets->RealTimeMorphTargetNames.Num();
TArray<TObjectPtr<UMorphTarget>>& MorphTargets = SkeletalMesh->GetMorphTargets();
MorphTargets.Empty(NumMorphTargets);
for (int32 MorphTargetIndex = 0; MorphTargetIndex < NumMorphTargets; ++MorphTargetIndex)
{
TArray<FMorphTargetLODModel>& MorphTargetData = ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex];
if (MorphTargetData.IsEmpty())
{
continue;
}
const FName& MorphTargetName = ComponentMorphTargets->RealTimeMorphTargetNames[MorphTargetIndex];
UMorphTarget* NewMorphTarget = NewObject<UMorphTarget>(SkeletalMesh, MorphTargetName);
NewMorphTarget->BaseSkelMesh = SkeletalMesh;
TArray<FMorphTargetLODModel>& MorphLODModels = NewMorphTarget->GetMorphLODModels();
if (OperationData->bStreamMeshLODs)
{
MorphLODModels.SetNum(ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex].Num());
// Streamed LODs
const int32 FirstLODAvailable = OperationData->FirstLODAvailable[ComponentName];
for (int32 LODIndex = FirstLODAvailable; LODIndex < OperationData->FirstResidentLOD[ComponentName]; ++LODIndex)
{
// Copy data required for streaming
MorphLODModels[LODIndex].NumVertices = 1; // Trick the engine
MorphLODModels[LODIndex].SectionIndices = MoveTemp(MorphTargetData[LODIndex].SectionIndices);
}
// Residents LODs
for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < OperationData->NumLODsAvailable[ComponentName]; ++LODIndex)
{
MorphLODModels[LODIndex] = ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex][LODIndex];
}
}
else
{
MorphLODModels = MoveTemp(ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex]);
}
MorphTargets.Add(NewMorphTarget);
}
// Mutable hacky LOD Streaming
if (!OperationData->bStreamMeshLODs)
{
// Copy MorphTargets from the FirstGeneratedLOD to the LODs below
const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex)
{
MUTABLE_CPUPROFILER_SCOPE(CopyMorphTargetsData);
for (int32 MorphTargetIndex = 0; MorphTargetIndex < MorphTargets.Num(); ++MorphTargetIndex)
{
MorphTargets[MorphTargetIndex]->GetMorphLODModels()[LODIndex] = MorphTargets[MorphTargetIndex]->GetMorphLODModels()[FirstRequestedLOD];
}
}
}
const bool bInKeepEmptyMorphTargets = OperationData->bStreamMeshLODs;
SkeletalMesh->InitMorphTargets(bInKeepEmptyMorphTargets); // True to avoid removing streamed Morph Targets.
}
namespace
{
// Only used to be able to create new clothing assets and assign a new guid to them without the factory.
class UCustomizableObjectClothingAsset : public UClothingAssetCommon
{
public:
void AssignNewGuid()
{
AssetGuid = FGuid::NewGuid();
}
};
}
void UCustomizableInstancePrivate::BuildOrCopyClothingData(
const TSharedRef<FUpdateContextPrivate>& OperationData,
USkeletalMesh* SkeletalMesh,
const UModelResources& ModelResources,
FCustomizableObjectInstanceComponentIndex InstanceComponentIndex,
const TArray<TObjectPtr<UPhysicsAsset>>& ClothingPhysicsAssets)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyClothingData);
struct FSectionClothMetadata
{
int32 SectionIndex;
int32 LODIndex;
int32 ClothAssetIndex;
int32 ClothAssetLodIndex;
uint32 NumVertices; // Upper bound
};
struct FPerClothAssetData
{
int32 MinLOD = 0;
TArray<TArray<int32, TInlineAllocator<8>>, TInlineAllocator<8>> AttachedSections; // Indices in SectionsWithCloth for render sections attached to this ClothAsset.
FName Name;
UPhysicsAsset* PhysicsAsset = nullptr;
UClothingAssetCommon* ClothingAsset = nullptr;
};
const TArray<FCustomizableObjectClothingAssetData>& ClothingAssetsData = ModelResources.ClothingAssetsData;
const TArray<FCustomizableObjectClothConfigData>& ClothSharedConfigsData = ModelResources.ClothSharedConfigsData;
if (!(ClothingAssetsData.Num() && OperationData->InstanceUpdateData.ClothingMeshData.Num()))
{
return;
}
const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex);
if (!Component)
{
return;
}
const FName& ComponentName = OperationData->ComponentNames[Component->Id.GetValue()];
TArray<FSectionClothMetadata> SectionClothMetadata; // Sections must be sorted ascending
SectionClothMetadata.Reserve(32);
TBitArray<> LODsWithClothing;
LODsWithClothing.Init(false, Component->LODCount);
// Keep in mind that clothing does not do the Hacky Mutable Streaming Copy. This is because LOD data can not be shared between LODs.
// This means that LOD loops are a bit different form usual. With the hacky Mutable streaming, we must generate the requested and the hacky copied ones.
// Metadata
{
int32 NumClothingDataNotFound = 0;
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex];
if (TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh)
{
for (int32 SectionIndex = 0; SectionIndex < MutableMesh->GetSurfaceCount(); ++SectionIndex)
{
if (!MutableMesh->Surfaces[SectionIndex].SubMeshes.IsValidIndex(0))
{
continue;
}
const mu::FSurfaceSubMesh& SubMesh = MutableMesh->Surfaces[SectionIndex].SubMeshes[0];
const uint32 ClothResourceId = ModelResources.MeshMetadata[SubMesh.ExternalId].ClothingMetadataId;
if (ClothResourceId == 0)
{
continue;
}
if (MutableMesh->Surfaces[SectionIndex].SubMeshes.Num() > 1)
{
UE_LOG(LogMutable, Error, TEXT("Section %d has more than one submesh! Skipping section."), SectionIndex);
continue;
}
FClothingMeshData* SectionClothingData = OperationData->InstanceUpdateData.ClothingMeshData.Find(ClothResourceId);
if (!SectionClothingData)
{
++NumClothingDataNotFound;
continue;
}
check(SectionClothingData->ClothingAssetIndex != INDEX_NONE);
check(SectionClothingData->ClothingAssetLOD != INDEX_NONE);
const int32 ClothAssetIndex = SectionClothingData->ClothingAssetIndex;
const int32 ClothAssetLodIndex = SectionClothingData->ClothingAssetLOD;
check(SectionClothingData->ClothingAssetIndex == ClothAssetIndex);
// Defensive check, this indicates the clothing data might be stale and needs to be recompiled.
// Should never happen.
if (!ensure(ClothAssetIndex >= 0 && ClothAssetIndex < ClothingAssetsData.Num() &&
ClothingAssetsData[ClothAssetIndex].LodData.Num()))
{
continue;
}
const uint32 NumVertices = SubMesh.VertexEnd - SubMesh.VertexBegin;
SectionClothMetadata.Add(FSectionClothMetadata { SectionIndex, LODIndex, ClothAssetIndex, ClothAssetLodIndex, NumVertices });
LODsWithClothing[LODIndex] = true;
}
}
}
if (NumClothingDataNotFound > 0)
{
UE_LOG(LogMutable, Error, TEXT("Some clothing data could not be loaded properly, clothing assets may not behave as expected."));
}
}
// No clothing, early out.
if (!SectionClothMetadata.Num())
{
return;
}
TMap<int32, FPerClothAssetData> PerClothAssetData;
PerClothAssetData.Reserve(32);
// Per Cloth Asset data
{
// Gather attached sections clothing asset LOD.
for (int32 MetadataIndex = 0; MetadataIndex < SectionClothMetadata.Num(); ++MetadataIndex)
{
const FSectionClothMetadata& SectionClothing = SectionClothMetadata[MetadataIndex];
FPerClothAssetData& AssetData = PerClothAssetData.FindOrAdd(SectionClothing.ClothAssetIndex);
AssetData.MinLOD = FMath::Min(AssetData.MinLOD, SectionClothing.ClothAssetLodIndex);
int32 MaxLOD = FMath::Max<int32>(AssetData.AttachedSections.Num() - 1, SectionClothing.ClothAssetLodIndex);
AssetData.AttachedSections.SetNum(MaxLOD + 1);
AssetData.AttachedSections[SectionClothing.ClothAssetLodIndex].Add(MetadataIndex);
}
for (TPair<int32, FPerClothAssetData>& Data : PerClothAssetData)
{
int32 ClothAssetIndex = Data.Key;
FPerClothAssetData& ClothAssetData = Data.Value;
ClothAssetData.Name = ClothingAssetsData[ClothAssetIndex].Name;
ClothAssetData.PhysicsAsset = ClothingPhysicsAssets[ClothAssetIndex];
}
}
TArray<UnrealConversionUtils::FSectionClothData> SectionsClothData; // Sorted by LOD, Section
SectionsClothData.Reserve(32);
// Data
{
MUTABLE_CPUPROFILER_SCOPE(DiscoverSectionsWithCloth);
int32 NumClothingDataNotFound = 0;
for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < Component->LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex];
if (TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh)
{
GetSectionClothData(*MutableMesh, LODIndex, OperationData->InstanceUpdateData.ClothingMeshData, SectionsClothData, NumClothingDataNotFound);
}
}
if (NumClothingDataNotFound > 0)
{
UE_LOG(LogMutable, Error, TEXT("Some clothing data could not be loaded properly, clothing assets may not behave as expected."));
}
CopyMeshToMeshClothData(SectionsClothData);
}
// Create Clothing Assets
{
MUTABLE_CPUPROFILER_SCOPE(CreateClothingAssets)
auto CreateNewClothConfigFromData = [](UObject* Outer, const FCustomizableObjectClothConfigData& ConfigData) -> UClothConfigCommon*
{
UClass* ClothConfigClass = FindObject<UClass>(nullptr, *ConfigData.ClassPath);
if (ClothConfigClass)
{
UClothConfigCommon* ClothConfig = NewObject<UClothConfigCommon>(Outer, ClothConfigClass);
if (ClothConfig)
{
FMemoryReaderView MemoryReader(ConfigData.ConfigBytes);
ClothConfig->Serialize(MemoryReader);
return ClothConfig;
}
}
return nullptr;
};
TArray<TTuple<FName, UClothConfigCommon*>> SharedConfigs;
SharedConfigs.Reserve(ClothSharedConfigsData.Num());
for (const FCustomizableObjectClothConfigData& ConfigData : ClothSharedConfigsData)
{
UClothConfigCommon* ClothConfig = CreateNewClothConfigFromData(SkeletalMesh, ConfigData);
if (ClothConfig)
{
SharedConfigs.Emplace(ConfigData.ConfigName, ClothConfig);
}
}
bool bAllNamesUnique = true;
TArray<FName, TInlineAllocator<8>> UniqueAssetNames;
for (TPair<int32, FPerClothAssetData>& AssetData : PerClothAssetData)
{
const int32 PrevNumUniqueElems = UniqueAssetNames.Num();
const int32 ElemIndex = UniqueAssetNames.AddUnique(AssetData.Value.Name);
if (ElemIndex < PrevNumUniqueElems)
{
bAllNamesUnique = false;
break;
}
}
for (TPair<int32, FPerClothAssetData>& AssetData : PerClothAssetData)
{
int32 AssetIndex = AssetData.Key;
FPerClothAssetData& ClothAssetData = AssetData.Value;
FName ClothingAssetObjectName = bAllNamesUnique
? ClothAssetData.Name
: FName(FString::Printf(TEXT("%s_%d"), *ClothAssetData.Name.ToString(), AssetIndex));
UCustomizableObjectClothingAsset* NewClothingAsset = NewObject<UCustomizableObjectClothingAsset>(SkeletalMesh, ClothingAssetObjectName);
NewClothingAsset->AssignNewGuid();
const int32 NumClothLODs = ClothingAssetsData[AssetIndex].LodData.Num() - ClothAssetData.MinLOD;
NewClothingAsset->LodData.SetNum(NumClothLODs);
for (int32 LODIndex = 0; LODIndex < NumClothLODs; ++LODIndex)
{
NewClothingAsset->LodData[LODIndex] = ClothingAssetsData[AssetIndex].LodData[LODIndex + ClothAssetData.MinLOD];
}
// Reconstruct clothing asset lod map.
NewClothingAsset->LodMap.Init(INDEX_NONE, Component->LODCount);
for (int32 LODIndex = 0; LODIndex < NumClothLODs; ++LODIndex)
{
for (int32 SectionWithClothIndex : ClothAssetData.AttachedSections[LODIndex])
{
NewClothingAsset->LodMap[SectionClothMetadata[SectionWithClothIndex].LODIndex] = LODIndex;
}
}
NewClothingAsset->UsedBoneIndices = ClothingAssetsData[AssetIndex].UsedBoneIndices;
NewClothingAsset->UsedBoneNames = ClothingAssetsData[AssetIndex].UsedBoneNames;
NewClothingAsset->ReferenceBoneIndex = ClothingAssetsData[AssetIndex].ReferenceBoneIndex;
NewClothingAsset->RefreshBoneMapping(SkeletalMesh);
NewClothingAsset->CalculateReferenceBoneIndex();
NewClothingAsset->PhysicsAsset = ClothAssetData.PhysicsAsset;
for (const FCustomizableObjectClothConfigData& ConfigData : ClothingAssetsData[AssetIndex].ConfigsData)
{
UClothConfigCommon* ClothConfig = CreateNewClothConfigFromData(NewClothingAsset, ConfigData);
if (ClothConfig)
{
NewClothingAsset->ClothConfigs.Add(ConfigData.ConfigName, ClothConfig);
}
}
for (const TTuple<FName, UClothConfigCommon*>& SharedConfig : SharedConfigs)
{
NewClothingAsset->ClothConfigs.Add(SharedConfig);
}
ClothAssetData.ClothingAsset = NewClothingAsset;
SkeletalMesh->GetMeshClothingAssets().AddUnique(NewClothingAsset);
}
}
const bool bAllowClothingPhysicsEdits = !bDisableClothingPhysicsEditsPropagation &&
ModelResources.bAllowClothingPhysicsEditsPropagation &&
!OperationData->bStreamMeshLODs;
if (bAllowClothingPhysicsEdits)
{
if (IMutableClothingModule* MutableClothingModule = FModuleManager::GetModulePtr<IMutableClothingModule>(MUTABLE_CLOTHING_MODULE_NAME))
{
for (TPair<int32, FPerClothAssetData>& Data : PerClothAssetData)
{
FPerClothAssetData& ClothAssetData = Data.Value;
UClothingAssetCommon* ClothingAsset = ClothAssetData.ClothingAsset;
if (!ClothingAsset)
{
continue;
}
bool bNeedsLodTransitionUpdate = false;
for (int32 LODIndex = 0; LODIndex < ClothingAsset->LodData.Num(); ++LODIndex)
{
TArray<TArrayView<FMeshToMeshVertData>, TInlineAllocator<8>> MeshToMeshDataViews;
for (int32 AttachedSectionIndex : ClothAssetData.AttachedSections[LODIndex])
{
MeshToMeshDataViews.Add(MakeArrayView(SectionsClothData[AttachedSectionIndex].MappingData));
}
bool bModified = MutableClothingModule->UpdateClothSimulationLOD(
LODIndex, *ClothingAsset, MakeConstArrayView(MeshToMeshDataViews));
bNeedsLodTransitionUpdate = bNeedsLodTransitionUpdate || bModified;
}
if (bNeedsLodTransitionUpdate)
{
// This needs to happen after all LODs have been processed.
for (int32 LODIndex = 0; LODIndex < ClothingAsset->LodData.Num(); ++LODIndex)
{
MutableClothingModule->FixLODTransitionMappings(LODIndex, *ClothingAsset);
}
}
}
}
else
{
UE_LOG(LogMutable, Warning, TEXT("MutableClothing plugin could not be found. Make sure the plugin is enabled if you want to use advanced clothing features."));
}
}
TArray<TArray<FMeshToMeshVertData>> ResidentLODMappingData;
ResidentLODMappingData.SetNum(Component->LODCount);
TArray<TArray<FClothBufferIndexMapping>> ResidentLODClothIndexMapping;
ResidentLODClothIndexMapping.SetNum(Component->LODCount);
// Zero all LODs (even those which do not use cloth).
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
ResidentLODClothIndexMapping[LODIndex].SetNumZeroed(MutableMesh->GetSurfaceCount());
}
// Create the mapping of cloth LODs.
for (const UnrealConversionUtils::FSectionClothData& Data : SectionsClothData)
{
CreateClothMapping(Data, ResidentLODMappingData[Data.LODIndex], ResidentLODClothIndexMapping[Data.LODIndex]);
}
FSkeletalMeshRenderData* RenderResource = SkeletalMesh->GetResourceForRendering();
{
MUTABLE_CPUPROFILER_SCOPE(InitClothRenderData)
// Streamed
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < OperationData->FirstResidentLOD[ComponentName]; ++LODIndex)
{
FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[LODIndex];
if (LODsWithClothing[LODIndex])
{
TArray<FClothBufferIndexMapping> ClothIndexMapping;
ClothIndexMapping.SetNumZeroed(LODModel.RenderSections.Num());
int32 Stride = sizeof(FMeshToMeshVertData);
int32 NumVertices = 0; // Upper bound
for (const FSectionClothMetadata& Metadata : SectionClothMetadata)
{
if (Metadata.LODIndex == LODIndex)
{
// Based on FSkeletalMeshLODModel::GetClothMappingData().
FSkelMeshRenderSection& RenderSection = LODModel.RenderSections[Metadata.SectionIndex];
check(Metadata.NumVertices == RenderSection.NumVertices); // Both values are upper bounds since we can not know the exact number of vertices without executing the code.
ClothIndexMapping[Metadata.SectionIndex].BaseVertexIndex = RenderSection.BaseVertexIndex;
ClothIndexMapping[Metadata.SectionIndex].MappingOffset = NumVertices;
ClothIndexMapping[Metadata.SectionIndex].LODBiasStride = Metadata.NumVertices;
NumVertices += Metadata.NumVertices;
}
}
LODModel.ClothVertexBuffer.SetMetadata(ClothIndexMapping, Stride, NumVertices);
}
}
// Resident
for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < OperationData->NumLODsAvailable[ComponentName]; ++LODIndex)
{
FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[LODIndex];
if (LODsWithClothing[LODIndex])
{
LODModel.ClothVertexBuffer.Init(ResidentLODMappingData[LODIndex], ResidentLODClothIndexMapping[LODIndex]);
}
}
}
for (const FSectionClothMetadata& Metadata : SectionClothMetadata)
{
FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[Metadata.LODIndex];
FSkelMeshRenderSection& SectionData = LODModel.RenderSections[Metadata.SectionIndex];
// Ideally we would copy the data of all LODs, but we do not have this information in the initial generation. In any case,
// ClothMappingDataLODs is only used for CPU Skinning, and some engine checks (they only check the array size).
// The size must be a multiple of SectionData.NumVertices. Currently Mutable only supports one influence per vertex (NumVertices * 1).
SectionData.ClothMappingDataLODs.AddDefaulted(1);
SectionData.ClothMappingDataLODs[0].SetNum(SectionData.NumVertices); // = MoveTemp(SectionWithCloth.MappingData);
FPerClothAssetData& AssetData = PerClothAssetData.FindChecked(Metadata.ClothAssetIndex);
SectionData.CorrespondClothAssetIndex = SkeletalMesh->GetClothingAssetIndex(AssetData.ClothingAsset);
SectionData.ClothingData.AssetGuid = AssetData.ClothingAsset->GetAssetGuid();
SectionData.ClothingData.AssetLodIndex = AssetData.ClothingAsset->LodMap[Metadata.LODIndex];
}
SkeletalMesh->SetHasActiveClothingAssets(!SectionClothMetadata.IsEmpty());
}
bool UCustomizableInstancePrivate::BuildOrCopyRenderData(const TSharedRef<FUpdateContextPrivate>& OperationData, USkeletalMesh* SkeletalMesh, const USkeletalMesh* LastUpdateSkeletalMesh,
UCustomizableObjectInstance* Public, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyRenderData);
FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering();
check(RenderData);
UCustomizableObject* CustomizableObject = Public->GetCustomizableObject();
// It must be not null as it's checked in the calling function
check(CustomizableObject);
const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex);
if (!Component)
{
return false;
}
const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked();
const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[Component->Id.GetValue()];
for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component->LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex];
if (!LOD.Mesh || LOD.SurfaceCount == 0)
{
continue;
}
SetLastMeshId(Component->Id, LODIndex, LOD.MeshID);
}
const int32 FirstLOD = OperationData->bStreamMeshLODs ?
OperationData->FirstLODAvailable[ComponentName] :
OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = FirstLOD; LODIndex < Component->LODCount; ++LODIndex)
{
MUTABLE_CPUPROFILER_SCOPE(BuildRenderData);
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex];
// There could be components without a mesh in LODs
if (!LOD.Mesh || LOD.SurfaceCount == 0)
{
UE_LOG(LogMutable, Warning, TEXT("Building instance: generated mesh [%s] has LOD [%d] of object component index [%d] with no mesh.")
, *SkeletalMesh->GetName()
, LODIndex
, Component->Id.GetValue());
// End with failure
return false;
}
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("BuildRenderData: Component index %d, LOD %d"), Component->Id.GetValue(), LODIndex));
FSkeletalMeshLODRenderData& LODResource = RenderData->LODRenderData[LODIndex];
const TMap<mu::FBoneName, TPair<FName, uint16>>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap;
// Set active and required bones
{
const TArray<mu::FBoneName>& ActiveBones = OperationData->InstanceUpdateData.ActiveBones;
LODResource.ActiveBoneIndices.Reserve(LOD.ActiveBoneCount);
for (uint32 Index = 0; Index < LOD.ActiveBoneCount; ++Index)
{
const uint16 ActiveBoneIndex = BoneInfoMap[ActiveBones[LOD.FirstActiveBone + Index]].Value;
LODResource.ActiveBoneIndices.Add(ActiveBoneIndex);
}
LODResource.RequiredBones = LODResource.ActiveBoneIndices;
LODResource.RequiredBones.Sort();
}
// Find referenced surface metadata.
const int32 MeshNumSurfaces = LOD.Mesh->Surfaces.Num();
TArray<const FMutableSurfaceMetadata*> MeshSurfacesMetadata;
MeshSurfacesMetadata.Init(nullptr, MeshNumSurfaces);
for (int32 MeshSectionIndex = 0; MeshSectionIndex < MeshNumSurfaces; ++MeshSectionIndex)
{
uint32 MeshSurfaceId = LOD.Mesh->GetSurfaceId(MeshSectionIndex);
int32 InstanceSurfaceIndex = OperationData->MutableInstance->FindSurfaceById(InstanceComponentIndex.GetValue(), LODIndex, MeshSurfaceId);
if (InstanceSurfaceIndex < 0)
{
continue;
}
uint32 SurfaceMetadataId = OperationData->MutableInstance->GetSurfaceCustomId(InstanceComponentIndex.GetValue(), LODIndex, InstanceSurfaceIndex);
uint32 UsedSurfaceMetadataId = 0;
if (SurfaceMetadataId != 0)
{
UsedSurfaceMetadataId = SurfaceMetadataId;
}
else
{
// In case the surface does not have metadata, check if any submesh has surface metadata.
for (const mu::FSurfaceSubMesh& SubMesh : LOD.Mesh->Surfaces[MeshSectionIndex].SubMeshes)
{
const FMutableMeshMetadata* FoundMeshMetadata = ModelResources.MeshMetadata.Find(SubMesh.ExternalId);
if (!FoundMeshMetadata)
{
continue;
}
UsedSurfaceMetadataId = FoundMeshMetadata->SurfaceMetadataId;
if (UsedSurfaceMetadataId != 0)
{
break;
}
}
}
MeshSurfacesMetadata[MeshSectionIndex] = ModelResources.SurfaceMetadata.Find(UsedSurfaceMetadataId);
}
// Set RenderSections
UnrealConversionUtils::SetupRenderSections(
LODResource,
LOD.Mesh.Get(),
OperationData->InstanceUpdateData.BoneMaps,
BoneInfoMap,
LOD.FirstBoneMap,
MeshSurfacesMetadata);
// Set SkinWeightProfiles
LODResource.SkinWeightProfilesData.Init(&LODResource.SkinWeightVertexBuffer);
// Active SkinWeightProfiles ID and Name
TArray<TPair<uint32, FName>> ActiveSkinWeightProfiles;
const mu::FMeshBufferSet& MutableMeshVertexBuffers = LOD.Mesh->GetVertexBuffers();
const int32 NumBuffers = MutableMeshVertexBuffers.GetBufferCount();
for (int32 BufferIndex = 0; BufferIndex < NumBuffers; ++BufferIndex)
{
if (MutableMeshVertexBuffers.Buffers[BufferIndex].Channels.IsEmpty())
{
continue;
}
mu::EMeshBufferSemantic Semantic;
int32 SemanticIndex;
MutableMeshVertexBuffers.GetChannel(BufferIndex, 0, &Semantic, &SemanticIndex, nullptr, nullptr, nullptr);
if (Semantic != mu::EMeshBufferSemantic::AltSkinWeight)
{
continue;
}
const FMutableSkinWeightProfileInfo* ProfileInfo = ModelResources.SkinWeightProfilesInfo.FindByPredicate(
[&SemanticIndex](const FMutableSkinWeightProfileInfo& P) { return P.NameId == SemanticIndex; });
if (ensure(ProfileInfo))
{
const FSkinWeightProfileInfo* ExistingProfile = SkeletalMesh->GetSkinWeightProfiles().FindByPredicate(
[&ProfileInfo](const FSkinWeightProfileInfo& P) { return P.Name == ProfileInfo->Name; });
if (!ExistingProfile)
{
SkeletalMesh->AddSkinWeightProfile({ ProfileInfo->Name, ProfileInfo->DefaultProfile, ProfileInfo->DefaultProfileFromLODIndex });
}
ActiveSkinWeightProfiles.Add({ ProfileInfo->NameId, ProfileInfo->Name });
LODResource.SkinWeightProfilesData.AddOverrideData(ProfileInfo->Name);
}
}
if (LODResource.bStreamedDataInlined) // Non-streamable LOD
{
// Copy Vertices
UnrealConversionUtils::CopyMutableVertexBuffers(
LODResource,
LOD.Mesh.Get(),
SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess);
// SurfaceIDs. Required to copy index buffers with padding
TArray<uint32> SurfaceIDs;
SurfaceIDs.SetNum(LOD.SurfaceCount);
for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex)
{
SurfaceIDs[SurfaceIndex] = LOD.Mesh->GetSurfaceId(SurfaceIndex);
}
// Copy indices.
bool bMarkRenderStateDirty = false;
if (!UnrealConversionUtils::CopyMutableIndexBuffers(LODResource, LOD.Mesh.Get(), SurfaceIDs, bMarkRenderStateDirty))
{
// End with failure
return false;
}
// Copy SkinWeightProfiles
UnrealConversionUtils::CopyMutableSkinWeightProfilesBuffers(
LODResource,
*SkeletalMesh,
LODIndex,
LOD.Mesh.Get(),
ActiveSkinWeightProfiles);
}
else // Streamable LOD.
{
// Init VertexBuffers for streaming
UnrealConversionUtils::InitVertexBuffersWithDummyData(
LODResource,
LOD.Mesh.Get(),
SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess);
// Init IndexBuffers for streaming
UnrealConversionUtils::InitIndexBuffersWithDummyData(LODResource, LOD.Mesh.Get());
}
if (LODResource.StaticVertexBuffers.ColorVertexBuffer.GetNumVertices())
{
SkeletalMesh->SetHasVertexColors(true);
}
if (LODResource.DoesVertexBufferUse16BitBoneIndex() && !UCustomizableObjectSystem::GetInstance()->IsSupport16BitBoneIndexEnabled())
{
OperationData->UpdateResult = EUpdateResult::Error16BitBoneIndex;
const FString Msg = FString::Printf(TEXT("Customizable Object [%s] requires of Skinning - 'Support 16 Bit Bone Index' to be enabled. Please, update the Project Settings."),
*CustomizableObject->GetName());
UE_LOG(LogMutable, Error, TEXT("%s"), *Msg);
#if WITH_EDITOR
FNotificationInfo Info(FText::FromString(Msg));
Info.bFireAndForget = true;
Info.FadeOutDuration = 1.0f;
Info.ExpireDuration = 10.0f;
FSlateNotificationManager::Get().AddNotification(Info);
#endif
}
}
// Mutable hacky LOD Streaming
if (!OperationData->bStreamMeshLODs)
{
// Copy LODRenderData from the FirstRequestedLOD to the LODs below
const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("CopyRenderData: From LOD %d to LOD %d"), FirstRequestedLOD, LODIndex));
// Render Data will be reused from the previously generated component
FSkeletalMeshLODRenderData& SourceLODResource = RenderData->LODRenderData[FirstRequestedLOD];
FSkeletalMeshLODRenderData& LODResource = RenderData->LODRenderData[LODIndex];
UnrealConversionUtils::CopySkeletalMeshLODRenderData(
LODResource,
SourceLODResource,
*SkeletalMesh,
LODIndex,
SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess
);
}
}
return true;
}
UE::Tasks::FTask UCustomizableInstancePrivate::LoadAdditionalAssetsAndData(const TSharedRef<FUpdateContextPrivate>& OperationData)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::LoadAdditionalAssetsAndDataAsync);
UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject();
const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked();
const TSharedPtr<FModelStreamableBulkData>& ModelStreamableBulkData = CustomizableObject->GetPrivate()->GetModelStreamableBulkData();
FMutableStreamRequest StreamRequest(ModelStreamableBulkData);
TArray<FSoftObjectPath> AssetsToStream;
TArray<FInstanceUpdateData::FComponent>& Components = OperationData->InstanceUpdateData.Components;
ObjectToInstanceIndexMap.Empty();
ReferencedMaterials.Empty();
const int32 NumClothingAssets = ModelResources.ClothingAssetsData.Num();
ClothingPhysicsAssets.Reset(NumClothingAssets);
ClothingPhysicsAssets.SetNum(NumClothingAssets);
GatheredAnimBPs.Empty();
AnimBPGameplayTags.Reset();
AnimBpPhysicsAssets.Reset();
for (const FInstanceUpdateData::FSurface& Surface : OperationData->InstanceUpdateData.Surfaces)
{
const int32 MaterialIndex = Surface.MaterialIndex;
if (MaterialIndex<0 || ObjectToInstanceIndexMap.Contains(MaterialIndex))
{
continue;
}
TSoftObjectPtr<UMaterialInterface> AssetPtr = ModelResources.Materials.IsValidIndex(MaterialIndex) ? ModelResources.Materials[MaterialIndex] : nullptr;
UMaterialInterface* LoadedMaterial = AssetPtr.Get();
const int32 ReferencedMaterialsIndex = ReferencedMaterials.Add(LoadedMaterial);
ObjectToInstanceIndexMap.Add(MaterialIndex, ReferencedMaterialsIndex);
if (!LoadedMaterial && !AssetPtr.IsNull())
{
AssetsToStream.Add(AssetPtr.ToSoftObjectPath());
}
}
for (const FInstanceUpdateData::FComponent& Component : OperationData->InstanceUpdateData.Components)
{
if (Component.OverlayMaterial == INDEX_NONE || ObjectToInstanceIndexMap.Contains(Component.OverlayMaterial))
{
continue;
}
TSoftObjectPtr<UMaterialInterface> AssetPtr = ModelResources.Materials.IsValidIndex(Component.OverlayMaterial) ? ModelResources.Materials[Component.OverlayMaterial] : nullptr;
UMaterialInterface* LoadedMaterial = AssetPtr.Get();
const int32 ReferencedMaterialsIndex = ReferencedMaterials.Add(LoadedMaterial);
ObjectToInstanceIndexMap.Add(Component.OverlayMaterial, ReferencedMaterialsIndex);
if (!LoadedMaterial && !AssetPtr.IsNull())
{
AssetsToStream.Add(AssetPtr.ToSoftObjectPath());
}
}
// Load Skeletons required by the SubMeshes of the newly generated Mesh, will be merged later
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->NumInstanceComponents; ++InstanceComponentIndex)
{
FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex));
if (!ObjectComponentIndex.IsValid())
{
continue;
}
const FInstanceUpdateData::FSkeletonData& SkeletonData = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex];
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
if (!ComponentData)
{
continue;
}
// Reuse merged Skeleton if cached
ComponentData->Skeletons.Skeleton = CustomizableObject->GetPrivate()->SkeletonCache.Get(SkeletonData.SkeletonIds);
if (ComponentData->Skeletons.Skeleton)
{
ComponentData->Skeletons.SkeletonIds.Empty();
ComponentData->Skeletons.SkeletonsToMerge.Empty();
continue;
}
// Add Skeletons to merge
for (const uint32 SkeletonId : SkeletonData.SkeletonIds)
{
TSoftObjectPtr<USkeleton> AssetPtr = ModelResources.Skeletons.IsValidIndex(SkeletonId) ? ModelResources.Skeletons[SkeletonId] : nullptr;
if (AssetPtr.IsNull())
{
continue;
}
// Add referenced skeletons to the assets to stream
ComponentData->Skeletons.SkeletonIds.Add(SkeletonId);
if (USkeleton* Skeleton = AssetPtr.Get())
{
ComponentData->Skeletons.SkeletonsToMerge.Add(Skeleton);
}
else
{
AssetsToStream.Add(AssetPtr.ToSoftObjectPath());
}
}
}
bool bHasInvalidMesh = false;
const bool bUpdateMeshes = DoComponentsNeedUpdate(GetPublic(), OperationData, bHasInvalidMesh);
// Load assets coming from SubMeshes of the newly generated Mesh
if (OperationData->InstanceUpdateData.LODs.Num())
{
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->InstanceUpdateData.Components.Num(); ++InstanceComponentIndex)
{
const FInstanceUpdateData::FComponent& Component = Components[InstanceComponentIndex];
FCustomizableInstanceComponentData* ComponentData = GetComponentData(Component.Id);
TSharedPtr<const mu::FMesh> FirstComponentMesh = OperationData->InstanceUpdateData.LODs.IsValidIndex(Component.FirstLOD) ?
OperationData->InstanceUpdateData.LODs[Component.FirstLOD].Mesh :
nullptr;
if (FirstComponentMesh && FirstComponentMesh->IsReference())
{
// Pass-through components don't have a Reference Mesh so don't access it
continue;
}
const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id;
const FMutableRefSkeletalMeshData& RefSkeletalMeshData = ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()];
for (int32 AssetUserDataIndex : RefSkeletalMeshData.AssetUserDataIndices)
{
#if !WITH_EDITOR
OperationData->StreamedResourceIndex.AddUnique(AssetUserDataIndex); // Used to hold/release streamed resources in non-editor builds.
#endif
ComponentData->StreamedResourceIndex.AddUnique(AssetUserDataIndex);
}
const FName ComponentName = OperationData->ComponentNames[Component.Id.GetValue()];
if (bUpdateMeshes)
{
// Morphs
{
// Data
for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
LoadMorphTargetsData(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.RealTimeMorphTargetMeshData);
}
// Metadata
const int32 FirstLOD = OperationData->bStreamMeshLODs ?
OperationData->FirstResidentLOD[ComponentName] :
OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = FirstLOD; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
LoadMorphTargetsMetadata(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.RealTimeMorphTargetMeshData);
}
}
// Cloth
{
// Data
// From FirstResidentLOD instead of FirstRequestedLOD since clothing we generate all LODs, even the hacky streaming copied ones.
for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
if (bUpdateMeshes)
{
LoadClothing(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.ClothingMeshData);
}
}
// Metadata
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
const TArray<uint64>& StreamedResources = MutableMesh->GetStreamedResources();
for (uint64 ResourceId : StreamedResources)
{
FCustomizableObjectStreameableResourceId TypedResourceId = BitCast<FCustomizableObjectStreameableResourceId>(ResourceId);
if (TypedResourceId.Type == (uint8)FCustomizableObjectStreameableResourceId::EType::Clothing)
{
check(TypedResourceId.Id != 0 && TypedResourceId.Id <= TNumericLimits<uint32>::Max());
const TMap<uint32, FClothingStreamable>& ClothingStreamables = ModelStreamableBulkData->ClothingStreamables;
if (const FClothingStreamable* ClothingStreamable = ClothingStreamables.Find(TypedResourceId.Id))
{
FClothingMeshData& ReadDestData = OperationData->InstanceUpdateData.ClothingMeshData.FindOrAdd(TypedResourceId.Id);
ReadDestData.ClothingAssetIndex = ClothingStreamable->ClothingAssetIndex;
ReadDestData.ClothingAssetLOD = ClothingStreamable->ClothingAssetLOD;
// TODO: Add async loading of ClothingAsset Data. This could be loaded as an streamead resource similar to and the asset user data.
int32 ClothingAssetIndex = ClothingStreamable->ClothingAssetIndex;
int32 PhysicsAssetIndex = ClothingStreamable->PhysicsAssetIndex;
const TSoftObjectPtr<UPhysicsAsset>& PhysicsAsset = ModelResources.PhysicsAssets.IsValidIndex(PhysicsAssetIndex)
? ModelResources.PhysicsAssets[PhysicsAssetIndex]
: nullptr;
// The entry should always be in the map
if (!PhysicsAsset.IsNull())
{
if (PhysicsAsset.Get())
{
if (ClothingPhysicsAssets.IsValidIndex(ClothingAssetIndex))
{
ClothingPhysicsAssets[ClothingAssetIndex] = PhysicsAsset.Get();
}
}
else
{
ComponentData->ClothingPhysicsAssetsToStream.Emplace(ClothingAssetIndex, PhysicsAssetIndex);
AssetsToStream.AddUnique(PhysicsAsset.ToSoftObjectPath());
}
}
}
else
{
UE_LOG(LogMutable, Error, TEXT("Invalid streamed clothing data block [%d] found."), TypedResourceId.Id);
}
}
}
}
}
}
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component.LODCount; ++LODIndex)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex];
TSharedPtr<const mu::FMesh> MutableMesh = LOD.Mesh;
if (!MutableMesh)
{
continue;
}
const TArray<uint64>& StreamedResources = MutableMesh->GetStreamedResources();
for (uint64 ResourceId : StreamedResources)
{
FCustomizableObjectStreameableResourceId TypedResourceId = BitCast<FCustomizableObjectStreameableResourceId>(ResourceId);
if (TypedResourceId.Type == (uint8)FCustomizableObjectStreameableResourceId::EType::AssetUserData)
{
const uint32 ResourceIndex = TypedResourceId.Id;
#if !WITH_EDITOR
OperationData->StreamedResourceIndex.AddUnique(ResourceIndex); // Used to hold/release streamed resources in non-editor builds.
#endif
ComponentData->StreamedResourceIndex.AddUnique(ResourceIndex);
}
}
for (int32 TagIndex = 0; TagIndex < MutableMesh->GetTagCount(); ++TagIndex)
{
FString Tag = MutableMesh->GetTag(TagIndex);
if (Tag.RemoveFromStart("__PA:"))
{
const int32 AssetIndex = FCString::Atoi(*Tag);
const TSoftObjectPtr<UPhysicsAsset>& PhysicsAsset = ModelResources.PhysicsAssets.IsValidIndex(AssetIndex) ? ModelResources.PhysicsAssets[AssetIndex] : nullptr;
if (!PhysicsAsset.IsNull())
{
if (PhysicsAsset.Get())
{
ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Add(PhysicsAsset.Get());
}
else
{
ComponentData->PhysicsAssets.PhysicsAssetToLoad.Add(AssetIndex);
AssetsToStream.AddUnique(PhysicsAsset.ToSoftObjectPath());
}
}
}
if (Tag.RemoveFromStart("__AnimBP:"))
{
FString SlotIndexString, AnimBpIndexString;
if (Tag.Split(TEXT("_Slot_"), &SlotIndexString, &AnimBpIndexString))
{
if (SlotIndexString.IsEmpty() || AnimBpIndexString.IsEmpty())
{
continue;
}
const int32 AnimBpIndex = FCString::Atoi(*AnimBpIndexString);
if (!ModelResources.AnimBPs.IsValidIndex(AnimBpIndex))
{
continue;
}
FName SlotIndex = *SlotIndexString;
const TSoftClassPtr<UAnimInstance>& AnimBPAsset = ModelResources.AnimBPs[AnimBpIndex];
if (!AnimBPAsset.IsNull())
{
const TSoftClassPtr<UAnimInstance>* FoundAnimBpSlot = ComponentData->AnimSlotToBP.Find(SlotIndex);
bool bIsSameAnimBp = FoundAnimBpSlot && AnimBPAsset == *FoundAnimBpSlot;
if (!FoundAnimBpSlot)
{
ComponentData->AnimSlotToBP.Add(SlotIndex, AnimBPAsset);
if (AnimBPAsset.Get())
{
GatheredAnimBPs.Add(AnimBPAsset.Get());
}
else
{
AssetsToStream.AddUnique(AnimBPAsset.ToSoftObjectPath());
}
}
else if (!bIsSameAnimBp)
{
// Two submeshes should not have the same animation slot index
OperationData->UpdateResult = EUpdateResult::Warning;
FString WarningMessage = FString::Printf(TEXT("Two submeshes have the same anim slot index [%s] in a Mutable Instance."), *SlotIndex.ToString());
UE_LOG(LogMutable, Warning, TEXT("%s"), *WarningMessage);
#if WITH_EDITOR
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(WarningMessage), EMessageSeverity::Warning, true);
#endif
}
}
}
}
else if (Tag.RemoveFromStart("__AnimBPTag:"))
{
AnimBPGameplayTags.AddTag(FGameplayTag::RequestGameplayTag(*Tag));
}
#if WITH_EDITORONLY_DATA
else if (Tag.RemoveFromStart("__MeshPath:"))
{
ComponentData->MeshPartPaths.Add(Tag);
}
#endif
}
const int32 AdditionalPhysicsNum = MutableMesh->AdditionalPhysicsBodies.Num();
for (int32 I = 0; I < AdditionalPhysicsNum; ++I)
{
const int32 ExternalId = MutableMesh->AdditionalPhysicsBodies[I]->CustomId;
ComponentData->PhysicsAssets.AdditionalPhysicsAssetsToLoad.Add(ExternalId);
AssetsToStream.Add(ModelResources.AnimBpOverridePhysiscAssetsInfo[ExternalId].SourceAsset.ToSoftObjectPath());
}
}
for (int32 ResourceIndex : ComponentData->StreamedResourceIndex)
{
#if WITH_EDITOR
if (!ModelResources.StreamedResourceDataEditor.IsValidIndex(ResourceIndex))
{
UE_LOG(LogMutable, Error, TEXT("Invalid streamed resource index. Max Index [%d]. Resource Index [%d]."), ModelResources.StreamedResourceDataEditor.Num(), ResourceIndex);
continue;
}
if (const FCustomizableObjectAssetUserData* AUDResource = ModelResources.StreamedResourceDataEditor[ResourceIndex].Data.GetPtr<FCustomizableObjectAssetUserData>())
{
AssetsToStream.AddUnique(AUDResource->AssetUserDataEditor.ToSoftObjectPath());
}
#else
if (!ModelResources.StreamedResourceData.IsValidIndex(ResourceIndex))
{
UE_LOG(LogMutable, Error, TEXT("Invalid streamed resource index. Max Index [%d]. Resource Index [%d]."), ModelResources.StreamedResourceData.Num(), ResourceIndex);
continue;
}
const FCustomizableObjectStreamedResourceData& StreamedResource = ModelResources.StreamedResourceData[ResourceIndex];
if (!StreamedResource.IsLoaded())
{
AssetsToStream.AddUnique(StreamedResource.GetPath().ToSoftObjectPath());
}
#endif
}
}
}
for (TSoftObjectPtr<const UTexture>& TextureRef : PassThroughTexturesToLoad)
{
AssetsToStream.Add(TextureRef.ToSoftObjectPath());
}
for (TSoftObjectPtr<const UStreamableRenderAsset>& MeshRef : PassThroughMeshesToLoad)
{
AssetsToStream.Add(MeshRef.ToSoftObjectPath());
}
// Copy FExtensionData Object node input from the Instance to the InstanceUpdateData
for (int32 ExtensionDataIndex = 0; ExtensionDataIndex < OperationData->MutableInstance->GetExtensionDataCount(); ExtensionDataIndex++)
{
TSharedPtr<const mu::FExtensionData> ExtensionData;
FName Name;
OperationData->MutableInstance->GetExtensionData(ExtensionDataIndex, ExtensionData, Name);
check(ExtensionData);
FInstanceUpdateData::FNamedExtensionData& NewEntry = OperationData->InstanceUpdateData.ExtendedInputPins.AddDefaulted_GetRef();
NewEntry.Data = ExtensionData;
NewEntry.Name = Name;
check(NewEntry.Name != NAME_None);
#if WITH_EDITOR
if (!ModelResources.StreamedExtensionDataEditor.IsValidIndex(ExtensionData->Index))
{
// The compiled data appears to be out of sync with the CO's properties
UE_LOG(LogMutable, Error, TEXT("Couldn't find streamed Extension Data with index %d in %s. Compiled data may be stale."),
ExtensionData->Index, *CustomizableObject->GetFullName());
}
#else
if (!ModelResources.StreamedExtensionData.IsValidIndex(ExtensionData->Index))
{
// The compiled data appears to be out of sync with the CO's properties
UE_LOG(LogMutable, Error, TEXT("Couldn't find streamed Extension Data with index %d in %s. Compiled data may be stale."),
ExtensionData->Index, *CustomizableObject->GetFullName());
continue;
}
const FCustomizableObjectStreamedResourceData& StreamedData = ModelResources.StreamedExtensionData[ExtensionData->Index];
if (StreamedData.IsLoaded())
{
continue;
}
// Note that this just checks if the path is non-null, NOT if the object is loaded
check(!StreamedData.GetPath().IsNull());
OperationData->ExtensionStreamedResourceIndex.Add(ExtensionData->Index);
AssetsToStream.Add(StreamedData.GetPath().ToSoftObjectPath());
#endif
}
TArray<UE::Tasks::FTask, TInlineAllocator<2>> Prerequisites;
if (AssetsToStream.Num() > 0)
{
#if WITH_EDITOR
// TODO: Remove with UE-217665 when the underlying bug in the ColorPicker is solved
// Disable the Slate throttling, otherwise the AsyncLoad may not complete until the editor window is clicked on due to a bug in
// some widgets such as the ColorPicker's throttling handling
FSlateThrottleManager::Get().DisableThrottle(true);
#endif
UE::Tasks::FTaskEvent Event(TEXT("AssetsStreamed"));
Prerequisites.Add(Event);
UCustomizableObjectSystemPrivate* PrivateSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate();
PrivateSystem->StreamableManager->RequestAsyncLoad(
AssetsToStream,
FStreamableDelegate::CreateUObject(this, &UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded, OperationData, Event),
CVarMutableHighPriorityLoading.GetValueOnAnyThread() ? FStreamableManager::AsyncLoadHighPriority : FStreamableManager::DefaultAsyncLoadPriority);
}
// Stream files
UE::Tasks::FTask StreamingTask = StreamRequest.Stream();
Prerequisites.Add(StreamingTask);
return UE::Tasks::Launch(TEXT("CaptureOperationData"), [OperationData]() {}, // Keep a reference to make sure allocated memory is always alive.
Prerequisites,
UE::Tasks::ETaskPriority::Inherit);
}
FCustomizableObjectInstanceDescriptor& UCustomizableInstancePrivate::GetDescriptor() const
{
return GetPublic()->Descriptor;
}
const TArray<TObjectPtr<UMaterialInterface>>* UCustomizableObjectInstance::GetOverrideMaterials(FCustomizableObjectComponentIndex ComponentIndex) const
{
UCustomizableObject* Object = GetCustomizableObject();
if (!Object)
{
return nullptr;
}
const UModelResources* ModelResources = Object->GetPrivate()->GetModelResources();
if (!ModelResources)
{
return nullptr;
}
FName ComponentName = ModelResources->ComponentNamesPerObjectComponent[ComponentIndex.GetValue()];
FCustomizableInstanceComponentData* ComponentData = PrivateData->GetComponentData(ComponentName);
return ComponentData ? &ComponentData->OverrideMaterials : nullptr;
}
void UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded(const TSharedRef<FUpdateContextPrivate> OperationData, UE::Tasks::FTaskEvent Event)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded);
check(IsInGameThread())
Event.Trigger();
UCustomizableObjectPrivate* CustomizableObjectPrivate = GetPublic()->GetCustomizableObject()->GetPrivate();
UModelResources& ModelResources = *CustomizableObjectPrivate->GetModelResources();
for (int32 ResourceIndex : OperationData->StreamedResourceIndex)
{
ModelResources.StreamedResourceData[ResourceIndex].Hold();
}
for (int32 ResourceIndex : OperationData->ExtensionStreamedResourceIndex)
{
ModelResources.StreamedExtensionData[ResourceIndex].Hold();
}
// Loaded Materials
check(ObjectToInstanceIndexMap.Num() == ReferencedMaterials.Num());
for (TPair<uint32, uint32> Pair : ObjectToInstanceIndexMap)
{
const TSoftObjectPtr<UMaterialInterface>& AssetPtr = ModelResources.Materials.IsValidIndex(Pair.Key) ? ModelResources.Materials[Pair.Key] : nullptr;
ReferencedMaterials[Pair.Value] = AssetPtr.Get();
#if WITH_EDITOR
if (!ReferencedMaterials[Pair.Value])
{
if (!AssetPtr.IsNull())
{
FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the material [%s] and won't be rendered. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *AssetPtr.GetAssetName());
UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg);
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true);
}
else
{
ensure(false); // Couldn't load the material, and we don't know which material
}
}
#endif
}
for (FCustomizableInstanceComponentData& ComponentData : ComponentsData)
{
for (int32 ResourceIndex : ComponentData.StreamedResourceIndex)
{
#if WITH_EDITOR
if (ModelResources.StreamedResourceDataEditor.IsValidIndex(ResourceIndex))
{
if (const FCustomizableObjectAssetUserData* AUDResource = ModelResources.StreamedResourceDataEditor[ResourceIndex].Data.GetPtr<FCustomizableObjectAssetUserData>())
{
ComponentData.AssetUserDataArray.Add(MutablePrivate::LoadObject(AUDResource->AssetUserDataEditor)); // Already loaded
}
}
#else
if (ModelResources.StreamedResourceData.IsValidIndex(ResourceIndex) && ModelResources.StreamedResourceData[ResourceIndex].IsLoaded())
{
const FCustomizableObjectResourceData& ResourceData = ModelResources.StreamedResourceData[ResourceIndex].GetLoadedData();
if (const FCustomizableObjectAssetUserData* AUDResource = ResourceData.Data.GetPtr<FCustomizableObjectAssetUserData>())
{
ComponentData.AssetUserDataArray.Add(AUDResource->AssetUserData);
}
}
#endif
}
// Loaded Skeletons
FReferencedSkeletons& Skeletons = ComponentData.Skeletons;
for (int32 SkeletonIndex : Skeletons.SkeletonIds)
{
const TSoftObjectPtr<USkeleton>& AssetPtr = ModelResources.Skeletons.IsValidIndex(SkeletonIndex) ? ModelResources.Skeletons[SkeletonIndex] : nullptr;
Skeletons.SkeletonsToMerge.AddUnique(AssetPtr.Get());
}
// Loaded PhysicsAssets
FReferencedPhysicsAssets& PhysicsAssets = ComponentData.PhysicsAssets;
for(const int32 PhysicsAssetIndex : PhysicsAssets.PhysicsAssetToLoad)
{
check(ModelResources.PhysicsAssets.IsValidIndex(PhysicsAssetIndex));
const TSoftObjectPtr<UPhysicsAsset>& PhysicsAsset = ModelResources.PhysicsAssets[PhysicsAssetIndex];
PhysicsAssets.PhysicsAssetsToMerge.Add(PhysicsAsset.Get());
#if WITH_EDITOR
if (!PhysicsAsset.Get())
{
if (!PhysicsAsset.IsNull())
{
FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the PhysicsAsset [%s] and won't be merged. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *PhysicsAsset.GetAssetName());
UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg);
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true);
}
else
{
ensure(false); // Couldn't load the PhysicsAsset, and we don't know which PhysicsAsset
}
}
#endif
}
PhysicsAssets.PhysicsAssetToLoad.Empty();
// Loaded Clothing PhysicsAssets
for ( TPair<int32, int32>& AssetToStream : ComponentData.ClothingPhysicsAssetsToStream )
{
const int32 AssetIndex = AssetToStream.Key;
if (ClothingPhysicsAssets.IsValidIndex(AssetIndex) && ModelResources.PhysicsAssets.IsValidIndex(AssetToStream.Value))
{
const TSoftObjectPtr<UPhysicsAsset>& PhysicsAssetPtr = ModelResources.PhysicsAssets[AssetToStream.Value];
ClothingPhysicsAssets[AssetIndex] = PhysicsAssetPtr.Get();
}
}
ComponentData.ClothingPhysicsAssetsToStream.Empty();
// Loaded anim BPs
for (TPair<FName, TSoftClassPtr<UAnimInstance>>& SlotAnimBP : ComponentData.AnimSlotToBP)
{
if (TSubclassOf<UAnimInstance> AnimBP = SlotAnimBP.Value.Get())
{
if (!GatheredAnimBPs.Contains(AnimBP))
{
GatheredAnimBPs.Add(AnimBP);
}
}
#if WITH_EDITOR
else
{
FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the AnimBlueprint [%s]. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *SlotAnimBP.Value.GetAssetName());
UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg);
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true);
}
#endif
}
const int32 AdditionalPhysicsNum = ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad.Num();
ComponentData.PhysicsAssets.AdditionalPhysicsAssets.Reserve(AdditionalPhysicsNum);
for (int32 I = 0; I < AdditionalPhysicsNum; ++I)
{
// Make the loaded assets references strong.
const int32 AnimBpPhysicsOverrideIndex = ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad[I];
ComponentData.PhysicsAssets.AdditionalPhysicsAssets.Add(
ModelResources.AnimBpOverridePhysiscAssetsInfo[AnimBpPhysicsOverrideIndex].SourceAsset.Get());
}
ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad.Empty();
}
LoadedPassThroughTexturesPendingSetMaterial.Empty(PassThroughTexturesToLoad.Num());
for (TSoftObjectPtr<const UTexture>& TextureRef : PassThroughTexturesToLoad)
{
ensure(TextureRef.IsValid());
LoadedPassThroughTexturesPendingSetMaterial.Add(TextureRef.Get());
}
PassThroughTexturesToLoad.Empty();
LoadedPassThroughMeshesPendingSetMaterial.Empty(PassThroughMeshesToLoad.Num());
for (TSoftObjectPtr<const UStreamableRenderAsset>& MeshRef : PassThroughMeshesToLoad)
{
ensure(MeshRef.IsValid());
LoadedPassThroughMeshesPendingSetMaterial.Add(MeshRef.Get());
}
PassThroughMeshesToLoad.Empty();
#if WITH_EDITOR
// TODO: Remove with UE-217665 when the underlying bug in the ColorPicker is solved
// Reenable the throttling which disabled when launching the Async Load
FSlateThrottleManager::Get().DisableThrottle(false);
#endif
}
void UpdateTextureRegionsMutable(UTexture2D* Texture, int32 MipIndex, uint32 NumMips, const FUpdateTextureRegion2D& Region, uint32 SrcPitch,
const FByteBulkData* BulkData, TSharedRef<FTexturePlatformData, ESPMode::ThreadSafe>& PlatformData)
{
if (Texture->GetResource())
{
struct FUpdateTextureRegionsData
{
FTexture2DResource* Texture2DResource;
int32 MipIndex;
FUpdateTextureRegion2D Region;
uint32 SrcPitch;
uint32 NumMips;
// The Platform Data mips will be automatically deleted when all FUpdateTextureRegionsData that reference it are deleted
// in the render thread after being used to update the texture
TSharedRef<FTexturePlatformData, ESPMode::ThreadSafe> PlatformData;
FUpdateTextureRegionsData(TSharedRef<FTexturePlatformData, ESPMode::ThreadSafe>& InPlatformData) : PlatformData(InPlatformData) {}
};
FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData(PlatformData);
RegionData->Texture2DResource = (FTexture2DResource*)Texture->GetResource();
RegionData->MipIndex = MipIndex;
RegionData->Region = Region;
RegionData->SrcPitch = SrcPitch;
RegionData->NumMips = NumMips;
ENQUEUE_RENDER_COMMAND(UpdateTextureRegionsMutable)(
[RegionData, BulkData](FRHICommandList& CmdList)
{
check(int32(RegionData->NumMips) >= RegionData->Texture2DResource->GetCurrentMipCount());
int32 MipDifference = RegionData->NumMips - RegionData->Texture2DResource->GetCurrentMipCount();
check(MipDifference >= 0);
int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
uint8* SrcData = (uint8*)BulkData->LockReadOnly();
//uint32 Size = RegionData->SrcPitch / (sizeof(uint8) * 4);
//UE_LOG(LogMutable, Warning, TEXT("UpdateTextureRegionsMutable MipIndex = %d, FirstMip = %d, size = %d"),
// RegionData->MipIndex, CurrentFirstMip, Size);
//checkf(Size <= RegionData->Texture2DResource->GetSizeX(),
// TEXT("UpdateTextureRegionsMutable incorrect size. %d, %d. NumMips=%d"),
// Size, RegionData->Texture2DResource->GetSizeX(), RegionData->Texture2DResource->GetCurrentMipCount());
if (RegionData->MipIndex >= CurrentFirstMip + MipDifference)
{
RHIUpdateTexture2D(
RegionData->Texture2DResource->GetTexture2DRHI(),
RegionData->MipIndex - CurrentFirstMip - MipDifference,
RegionData->Region,
RegionData->SrcPitch,
SrcData);
}
BulkData->Unlock();
delete RegionData; // This will implicitly delete the Platform Data if this is the last RegionData referencing it
});
}
}
void UCustomizableInstancePrivate::ReuseTexture(UTexture2D* Texture, TSharedRef<FTexturePlatformData, ESPMode::ThreadSafe>& PlatformData)
{
uint32 NumMips = PlatformData->Mips.Num();
for (uint32 i = 0; i < NumMips; i++)
{
FTexture2DMipMap& Mip = PlatformData->Mips[i];
if (Mip.BulkData.GetElementCount() > 0)
{
FUpdateTextureRegion2D Region;
Region.DestX = 0;
Region.DestY = 0;
Region.SrcX = 0;
Region.SrcY = 0;
Region.Width = Mip.SizeX;
Region.Height = Mip.SizeY;
check(int32(Region.Width) <= Texture->GetSizeX());
check(int32(Region.Height) <= Texture->GetSizeY());
UpdateTextureRegionsMutable(Texture, i, NumMips, Region,
Mip.SizeX * sizeof(uint8) * 4, &Mip.BulkData, PlatformData);
}
}
}
void UCustomizableInstancePrivate::BuildMaterials(const TSharedRef<FUpdateContextPrivate>& OperationData, UCustomizableObjectInstance* Public)
{
MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildMaterials)
UCustomizableObject* CustomizableObject = Public->GetCustomizableObject();
const UModelResources& ModelResources = *CustomizableObject->GetPrivate()->GetModelResources();
TArray<FGeneratedTexture> NewGeneratedTextures;
// Temp copy to allow reuse of MaterialInstances
TArray<FGeneratedMaterial> OldGeneratedMaterials;
Exchange(OldGeneratedMaterials, GeneratedMaterials);
GeneratedMaterials.Reset();
// Prepare the data to store in order to regenerate resources for this instance (usually texture mips).
TSharedPtr<FMutableUpdateContext> UpdateContext = MakeShared<FMutableUpdateContext>(
CustomizableObject->GetPathName(),
Public->GetPathName(),
UCustomizableObjectSystem::GetInstance()->GetPrivate()->MutableSystem,
OperationData->Model,
CustomizableObject->GetPrivate()->GetModelStreamableBulkData(),
OperationData->Parameters,
OperationData->GetCapturedDescriptor().GetState());
// Cache the descriptor as a string if we want to later report it using our benchmark utility.
if (FLogBenchmarkUtil::IsBenchmarkingReportingEnabled())
{
UpdateContext->CapturedDescriptor = OperationData->GetCapturedDescriptor().ToString();
if (GWorld)
{
UpdateContext->bLevelBegunPlay = GWorld->GetBegunPlay();
}
}
const bool bReuseTextures = OperationData->bReuseInstanceTextures;
TArray<bool> RecreateRenderStateOnInstanceComponent;
RecreateRenderStateOnInstanceComponent.Init(false, OperationData->NumInstanceComponents);
TArray<bool> NotifyUpdateOnInstanceComponent;
NotifyUpdateOnInstanceComponent.Init(false, OperationData->NumInstanceComponents);
for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->NumInstanceComponents; ++InstanceComponentIndex)
{
const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex];
const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id;
if (!ModelResources.ComponentNamesPerObjectComponent.IsValidIndex(ObjectComponentIndex.GetValue()))
{
continue;
}
const FName& ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()];
TObjectPtr<USkeletalMesh>* Result = SkeletalMeshes.Find(ComponentName);
TObjectPtr<USkeletalMesh> SkeletalMesh = Result ? *Result : nullptr;
if (!SkeletalMesh)
{
continue;
}
const bool bReuseMaterials = !OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex];
// If the mesh is not transient, it means it's pass-through so it should use material overrides and not be modified in any way
const bool bIsTransientMesh = static_cast<bool>(SkeletalMesh->HasAllFlags(EObjectFlags::RF_Transient));
// It is not safe to replace the materials of a SkeletalMesh whose resources are initialized. Use overrides instead.
const bool bUseOverrideMaterialsOnly = !bIsTransientMesh || (OperationData->bUseMeshCache && SkeletalMesh->GetResourceForRendering()->IsInitialized());
UMaterialInterface* OverlayMaterial = nullptr;
FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex);
if (ComponentData)
{
ComponentData->OverrideMaterials.Reset();
ComponentData->OverlayMaterial = nullptr;
if (const uint32* ReferencedMaterialIndex = ObjectToInstanceIndexMap.Find(Component.OverlayMaterial))
{
if (ReferencedMaterials.IsValidIndex(*ReferencedMaterialIndex))
{
ComponentData->OverlayMaterial = ReferencedMaterials[*ReferencedMaterialIndex];
OverlayMaterial = ComponentData->OverlayMaterial;
}
}
}
if (!bUseOverrideMaterialsOnly)
{
RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] |= SkeletalMesh->GetOverlayMaterial() != OverlayMaterial;
SkeletalMesh->SetOverlayMaterial(OverlayMaterial);
}
TArray<FSkeletalMaterial> Materials;
// Maps serializations of FMutableMaterialPlaceholder to Created Dynamic Material instances, used to reuse materials across LODs
TSet<FMutableMaterialPlaceholder> ReuseMaterialCache;
// SurfaceId per MaterialSlotIndex
TArray<int32> SurfaceIdToMaterialIndex;
MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_LODLoop);
const int32 FirstLOD = OperationData->bStreamMeshLODs ?
OperationData->FirstLODAvailable[ComponentName] :
OperationData->GetFirstRequestedLOD()[ComponentName];
for (int32 LODIndex = FirstLOD; LODIndex < Component.LODCount; LODIndex++)
{
const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD+LODIndex];
if (!bUseOverrideMaterialsOnly && LODIndex < SkeletalMesh->GetLODNum())
{
SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Reset();
}
// Pass-through components will not have a reference mesh.
const FMutableRefSkeletalMeshData* RefSkeletalMeshData = nullptr;
if (ModelResources.ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex.GetValue()))
{
RefSkeletalMeshData = &ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()];
}
for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex)
{
const FInstanceUpdateData::FSurface& Surface = OperationData->InstanceUpdateData.Surfaces[LOD.FirstSurface + SurfaceIndex];
// Is this a material in a passthrough mesh that we don't modify?
if (Surface.MaterialIndex<0)
{
Materials.Emplace();
#if WITH_EDITOR
// Without this, a change of a referenced material and recompilation doesn't show up in the preview.
RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] = true;
#endif
continue;
}
// Reuse MaterialSlot from the previous LOD.
if (const int32 MaterialIndex = SurfaceIdToMaterialIndex.Find(Surface.SurfaceId); MaterialIndex != INDEX_NONE)
{
if (!bUseOverrideMaterialsOnly)
{
const int32 LODMaterialIndex = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Add(MaterialIndex);
SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections[SurfaceIndex].MaterialIndex = LODMaterialIndex;
}
continue;
}
const uint32 ReferencedMaterialIndex = ObjectToInstanceIndexMap[Surface.MaterialIndex];
UMaterialInterface* MaterialTemplate = ReferencedMaterials[ReferencedMaterialIndex];
if (!MaterialTemplate)
{
// Missing MaterialTemplate. Use DefaultMaterial instead.
MaterialTemplate = UMaterial::GetDefaultMaterial(MD_Surface);
check(MaterialTemplate);
UE_LOG(LogMutable, Error, TEXT("Build Materials: Missing referenced template to use as parent material on CustomizableObject [%s]."), *CustomizableObject->GetName());
}
// This section will require a new slot
SurfaceIdToMaterialIndex.Add(Surface.SurfaceId);
// Add and set up the material data for this slot
const int32 MaterialSlotIndex = Materials.Num();
FSkeletalMaterial& MaterialSlot = Materials.AddDefaulted_GetRef();
MaterialSlot.MaterialInterface = MaterialTemplate;
uint32 UsedSurfaceMetadataId = Surface.SurfaceMetadataId;
// If the surface metadata is invalid, check if any of the mesh fragments has metadata.
// For now use the fisrt found, an aggregate may be needed.
if (Surface.SurfaceMetadataId == 0 && LOD.Mesh)
{
int32 MeshSurfaceIndex = LOD.Mesh->Surfaces.IndexOfByPredicate([SurfaceId = Surface.SurfaceId](const mu::FMeshSurface& Surface)
{
return SurfaceId == Surface.Id;
});
if (MeshSurfaceIndex != INDEX_NONE)
{
for (const mu::FSurfaceSubMesh& SubMesh : LOD.Mesh->Surfaces[SurfaceIndex].SubMeshes)
{
const FMutableMeshMetadata* FoundMeshMetadata = ModelResources.MeshMetadata.Find(SubMesh.ExternalId);
if (!FoundMeshMetadata)
{
continue;
}
UsedSurfaceMetadataId = FoundMeshMetadata->SurfaceMetadataId;
if (UsedSurfaceMetadataId != 0)
{
break;
}
}
}
}
const FMutableSurfaceMetadata* FoundSurfaceMetadata = ModelResources.SurfaceMetadata.Find(UsedSurfaceMetadataId);
if (FoundSurfaceMetadata)
{
MaterialSlot.MaterialSlotName = FoundSurfaceMetadata->MaterialSlotName;
}
if (RefSkeletalMeshData)
{
SetMeshUVChannelDensity(MaterialSlot.UVChannelData, RefSkeletalMeshData->Settings.DefaultUVChannelDensity);
}
if (!bUseOverrideMaterialsOnly)
{
if (SkeletalMesh->GetResourceForRendering()->LODRenderData.IsValidIndex(LODIndex) &&
SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections.IsValidIndex(SurfaceIndex))
{
const int32 LODMaterialIndex = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Add(MaterialSlotIndex);
SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections[SurfaceIndex].MaterialIndex = LODMaterialIndex;
}
else
{
ensure(false);
}
}
FMutableMaterialPlaceholder MutableMaterialPlaceholder;
MutableMaterialPlaceholder.ParentMaterialID = MaterialTemplate->GetUniqueID();
MutableMaterialPlaceholder.MatIndex = MaterialSlotIndex;
{
MUTABLE_CPUPROFILER_SCOPE(ParamLoop);
for (int32 VectorIndex = 0; VectorIndex < Surface.VectorCount; ++VectorIndex)
{
const FInstanceUpdateData::FVector& Vector = OperationData->InstanceUpdateData.Vectors[Surface.FirstVector + VectorIndex];
// Decoding Material Layer from Mutable parameter name
FString EncodingString = "-MutableLayerParam:";
FString VectorName = Vector.Name.ToString();
int32 EncodingPosition = VectorName.Find(EncodingString);
int32 LayerIndex = -1;
if (EncodingPosition == INDEX_NONE)
{
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(Vector.Name), -1, Vector.Vector));
}
else
{
//Getting layer index
int32 LayerPosition = VectorName.Len() - (EncodingPosition + EncodingString.Len());
FString IndexString = VectorName.RightChop(VectorName.Len() - LayerPosition);
LayerIndex = FCString::Atof(*IndexString);
//Getting parameter name
FString Sufix = EncodingString + FString::FromInt(LayerIndex);
VectorName.RemoveFromEnd(Sufix);
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(VectorName), LayerIndex, Vector.Vector));
}
}
for (int32 ScalarIndex = 0; ScalarIndex < Surface.ScalarCount; ++ScalarIndex)
{
const FInstanceUpdateData::FScalar& Scalar = OperationData->InstanceUpdateData.Scalars[Surface.FirstScalar + ScalarIndex];
// Decoding Material Layer from Mutable parameter name
FString EncodingString = "-MutableLayerParam:";
FString ScalarName = Scalar.Name.ToString();
int32 EncodingPosition = ScalarName.Find(EncodingString);
int32 LayerIndex = -1;
if (EncodingPosition == INDEX_NONE)
{
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(Scalar.Name), -1, Scalar.Scalar));
}
else
{
//Getting layer index
int32 LayerPosition = ScalarName.Len() - (EncodingPosition + EncodingString.Len());
FString IndexString = ScalarName.RightChop(ScalarName.Len() - LayerPosition);
LayerIndex = FCString::Atof(*IndexString);
//Getting parameter name
FString Sufix = EncodingString + FString::FromInt(LayerIndex);
ScalarName.RemoveFromEnd(Sufix);
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(ScalarName), LayerIndex, Scalar.Scalar));
}
}
}
{
MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_ImageLoop);
// Get the cache of resources of all live instances of this object
FMutableResourceCache& Cache = UCustomizableObjectSystem::GetInstance()->GetPrivate()->GetObjectCache(CustomizableObject);
FString CurrentState = Public->GetCurrentState();
bool bNeverStream = OperationData->bNeverStream;
check((bNeverStream && OperationData->MipsToSkip == 0) ||
(!bNeverStream && OperationData->MipsToSkip >= 0));
for (int32 ImageIndex = 0; ImageIndex < Surface.ImageCount; ++ImageIndex)
{
const FInstanceUpdateData::FImage& Image = OperationData->InstanceUpdateData.Images[Surface.FirstImage + ImageIndex];
FString KeyName = Image.Name.ToString();
TSharedPtr<const mu::FImage> MutableImage = Image.Image;
UTexture2D* MutableTexture = nullptr; // Texture generated by mutable
UTexture* PassThroughTexture = nullptr; // Texture not generated by mutable
// \TODO: Change this key to a struct.
FString TextureReuseCacheRef = bReuseTextures ? FString::Printf(TEXT("%d-%d-%d-%d"), Image.BaseLOD, ObjectComponentIndex.GetValue(), Surface.SurfaceId, ImageIndex) : FString();
// If the mutable image is null, it must be in the cache
FMutableImageCacheKey ImageCacheKey = { Image.ImageID, OperationData->MipsToSkip };
if (!MutableImage)
{
TWeakObjectPtr<UTexture2D>* CachedPointerPtr = Cache.Images.Find(ImageCacheKey);
if (CachedPointerPtr)
{
ensure(!CachedPointerPtr->IsStale());
MutableTexture = CachedPointerPtr->Get();
}
check(MutableTexture);
}
// Check if the image is a reference to an engine texture
if (MutableImage && Image.bIsPassThrough)
{
check(MutableImage->IsReference());
uint32 ReferenceID = MutableImage->GetReferencedTexture();
if (ModelResources.PassThroughTextures.IsValidIndex(ReferenceID))
{
TSoftObjectPtr<UTexture> Ref = ModelResources.PassThroughTextures[ReferenceID];
// The texture should have been loaded by now by LoadAdditionalAssetsAsync()
PassThroughTexture = Ref.Get();
if (!PassThroughTexture)
{
// The texture should be loaded, something went wrong, possibly a bug in LoadAdditionalAssetsAsync()
UE_LOG(LogMutable, Error,
TEXT("Pass-through texture with name %s hasn't been loaded yet in BuildMaterials(). Forcing sync load."),
*Ref.ToSoftObjectPath().ToString());
ensure(false);
PassThroughTexture = MutablePrivate::LoadObject(Ref);
}
}
if (!PassThroughTexture)
{
// Internal error.
UE_LOG(LogMutable, Error, TEXT("Missing referenced image [%d]."), ReferenceID);
continue;
}
}
// Find the additional information for this image
int32 ImageKey = FCString::Atoi(*KeyName);
if (ImageKey >= 0 && ImageKey < ModelResources.ImageProperties.Num())
{
const FMutableModelImageProperties& Props = ModelResources.ImageProperties[ImageKey];
if (!MutableTexture && !PassThroughTexture && MutableImage)
{
TWeakObjectPtr<UTexture2D>* ReusedTexture = bReuseTextures ? TextureReuseCache.Find(TextureReuseCacheRef) : nullptr;
// This shared ptr will hold the reused texture platform data (mips) until the reused texture is updated
// and delete it automatically
TSharedPtr<FTexturePlatformData, ESPMode::ThreadSafe> ReusedTexturePlatformData;
// Ensure the name of the texture is unique (prevents black textures in editor previews)
FString MutableTextureName = FString::Printf(TEXT("T_%s_%llu"), *Props.TextureParameterName, Image.ImageID);
MutableTextureName.ReplaceInline(TEXT(" "), TEXT("_"));
MutableTextureName = MakeUniqueObjectName(GetTransientPackage(), UTexture2D::StaticClass(), *MutableTextureName, EUniqueObjectNameOptions::GloballyUnique).ToString();
if (ReusedTexture && (*ReusedTexture).IsValid() && !(*ReusedTexture)->HasAnyFlags(RF_BeginDestroyed))
{
// Only uncompressed textures can be reused. This also fixes an issue in the editor where textures supposedly
// uncompressed by their state, are still compressed because the CO has not been compiled at maximum settings
// and the uncompressed setting cannot be applied to them.
EPixelFormat PixelFormat = (*ReusedTexture)->GetPixelFormat();
if (PixelFormat == EPixelFormat::PF_R8G8B8A8)
{
MutableTexture = (*ReusedTexture).Get();
check(MutableTexture != nullptr);
}
else
{
ReusedTexture = nullptr;
MutableTexture = CreateTexture(MutableTextureName);
#if WITH_EDITOR
UE_LOG(LogMutable, Warning,
TEXT("Tried to reuse an uncompressed texture with name %s. Make sure the selected Mutable state disables texture compression/streaming, that one of the state's runtime parameters affects the texture and that the CO is compiled with max. optimization settings."),
*MutableTexture->GetName());
#endif
}
}
else
{
ReusedTexture = nullptr;
MutableTexture = CreateTexture(MutableTextureName);
}
if (MutableTexture)
{
if (OperationData->ImageToPlatformDataMap.Contains(Image.ImageID))
{
SetTexturePropertiesFromMutableImageProps(MutableTexture, Props, bNeverStream);
FTexturePlatformData* PlatformData = OperationData->ImageToPlatformDataMap[Image.ImageID];
if (ReusedTexture)
{
check(PlatformData->Mips.Num() == MutableTexture->GetPlatformData()->Mips.Num());
check(PlatformData->Mips[0].SizeX == MutableTexture->GetPlatformData()->Mips[0].SizeX);
check(PlatformData->Mips[0].SizeY == MutableTexture->GetPlatformData()->Mips[0].SizeY);
// Now the ReusedTexturePlatformData shared ptr owns the platform data
ReusedTexturePlatformData = TSharedPtr<FTexturePlatformData, ESPMode::ThreadSafe>(PlatformData);
}
else
{
// Now the MutableTexture owns the platform data
MutableTexture->SetPlatformData(PlatformData);
}
OperationData->ImageToPlatformDataMap.Remove(Image.ImageID);
}
else
{
UE_LOG(LogMutable, Error, TEXT("Required image [%s] with ID [%lld] was not generated in the mutable thread, and it is not cached. LOD [%d]. Object Component [%d]"),
*Props.TextureParameterName,
Image.ImageID,
LODIndex, ObjectComponentIndex.GetValue());
continue;
}
if (bNeverStream)
{
// To prevent LogTexture Error "Loading non-streamed mips from an external bulk file."
for (int32 i = 0; i < MutableTexture->GetPlatformData()->Mips.Num(); ++i)
{
MutableTexture->GetPlatformData()->Mips[i].BulkData.ClearBulkDataFlags(BULKDATA_PayloadInSeperateFile);
}
}
{
MUTABLE_CPUPROFILER_SCOPE(UpdateResource);
#if REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES
for (int32 i = 0; i < MutableTexture->GetPlatformData()->Mips.Num(); ++i)
{
uint32 DataFlags = MutableTexture->GetPlatformData()->Mips[i].BulkData.GetBulkDataFlags();
MutableTexture->GetPlatformData()->Mips[i].BulkData.SetBulkDataFlags(DataFlags | BULKDATA_SingleUse);
}
#endif
if (ReusedTexture)
{
// Must remove texture from cache since it will be reused with a different ImageID
for (TPair<FMutableImageCacheKey, TWeakObjectPtr<UTexture2D>>& CachedTexture : Cache.Images)
{
if (CachedTexture.Value == MutableTexture)
{
Cache.Images.Remove(CachedTexture.Key);
break;
}
}
check(ReusedTexturePlatformData.IsValid());
if (ReusedTexturePlatformData.IsValid())
{
TSharedRef<FTexturePlatformData, ESPMode::ThreadSafe> PlatformDataRef = ReusedTexturePlatformData.ToSharedRef();
ReuseTexture(MutableTexture, PlatformDataRef);
}
}
else if (MutableTexture)
{
//if (!bNeverStream) // No need to check bNeverStream. In that case, the texture won't use
// the MutableMipDataProviderFactory anyway and it's needed for detecting Mutable textures elsewhere
{
UMutableTextureMipDataProviderFactory* MutableMipDataProviderFactory = Cast<UMutableTextureMipDataProviderFactory>(MutableTexture->GetAssetUserDataOfClass(UMutableTextureMipDataProviderFactory::StaticClass()));
if (!MutableMipDataProviderFactory)
{
MutableMipDataProviderFactory = NewObject<UMutableTextureMipDataProviderFactory>();
if (MutableMipDataProviderFactory)
{
MutableMipDataProviderFactory->CustomizableObjectInstance = Public;
check(LODIndex < 256 && InstanceComponentIndex < 256 && ImageIndex < 256);
MutableMipDataProviderFactory->ImageRef.ImageID = Image.ImageID;
MutableMipDataProviderFactory->ImageRef.SurfaceId = Surface.SurfaceId;
MutableMipDataProviderFactory->ImageRef.LOD = uint8(Image.BaseLOD);
MutableMipDataProviderFactory->ImageRef.Component = uint8(InstanceComponentIndex);
MutableMipDataProviderFactory->ImageRef.Image = uint8(ImageIndex);
MutableMipDataProviderFactory->ImageRef.BaseMip = uint8(Image.BaseMip);
MutableMipDataProviderFactory->ImageRef.ConstantImagesNeededToGenerate = Image.ConstantImagesNeededToGenerate;
MutableMipDataProviderFactory->UpdateContext = UpdateContext;
MutableTexture->AddAssetUserData(MutableMipDataProviderFactory);
}
}
}
MutableTexture->UpdateResource();
}
}
Cache.Images.Add(ImageCacheKey, MutableTexture);
}
else
{
UE_LOG(LogMutable, Error, TEXT("Texture creation failed."));
}
}
FGeneratedTexture TextureData;
TextureData.Key = ImageCacheKey;
TextureData.Name = Props.TextureParameterName;
TextureData.Texture = MutableTexture ? MutableTexture : PassThroughTexture;
// Only add textures generated by mutable to the cache
if (MutableTexture)
{
NewGeneratedTextures.Add(TextureData);
}
// Decoding Material Layer from Mutable parameter name
FString ImageName = Image.Name.ToString();
FString EncodingString = "-MutableLayerParam:";
int32 EncodingPosition = ImageName.Find(EncodingString);
int32 LayerIndex = -1;
if (EncodingPosition == INDEX_NONE)
{
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(*Props.TextureParameterName, -1, TextureData));
}
else
{
//Getting layer index
int32 LayerPosition = ImageName.Len() - (EncodingPosition + EncodingString.Len());
FString IndexString = ImageName.RightChop(ImageName.Len() - LayerPosition);
LayerIndex = FCString::Atof(*IndexString);
MutableMaterialPlaceholder.AddParam(
FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(*Props.TextureParameterName, LayerIndex, TextureData));
}
}
else
{
// This means the compiled model (maybe coming from derived data) has images that the asset doesn't know about.
UE_LOG(LogMutable, Error, TEXT("CustomizableObject derived data out of sync with asset for [%s]. Try recompiling it."), *CustomizableObject->GetName());
}
if (bReuseTextures)
{
if (MutableTexture)
{
TextureReuseCache.Add(TextureReuseCacheRef, MutableTexture);
}
else
{
TextureReuseCache.Remove(TextureReuseCacheRef);
}
}
}
}
// Find or create the material for this slot
UMaterialInterface* MaterialInterface = MaterialSlot.MaterialInterface;
if (FMutableMaterialPlaceholder* FoundMaterialPlaceholder = ReuseMaterialCache.Find(MutableMaterialPlaceholder))
{
MaterialInterface = Materials[FoundMaterialPlaceholder->MatIndex].MaterialInterface;
}
else // Material not cached, create a new one
{
MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_CreateMaterial);
ReuseMaterialCache.Add(MutableMaterialPlaceholder);
FGeneratedMaterial& Material = GeneratedMaterials.AddDefaulted_GetRef();
Material.SurfaceId = Surface.SurfaceId;
Material.MaterialIndex = Surface.MaterialIndex;
Material.MaterialInterface = MaterialInterface;
#if WITH_EDITORONLY_DATA
Material.ComponentName = ComponentName;
#endif
UMaterialInstanceDynamic* MaterialInstance = nullptr;
if (const int32 OldMaterialIndex = OldGeneratedMaterials.Find(Material); bReuseMaterials && OldMaterialIndex != INDEX_NONE)
{
const FGeneratedMaterial& OldMaterial = OldGeneratedMaterials[OldMaterialIndex];
MaterialInstance = Cast<UMaterialInstanceDynamic>(OldMaterial.MaterialInterface);
Material.MaterialInterface = OldMaterial.MaterialInterface;
}
if (!MaterialInstance && MutableMaterialPlaceholder.GetParams().Num() != 0)
{
#if WITH_EDITOR
// Remove the MI_ or M_ prefixes from the material string so we use it as the name of the MID
FString MIDName = MaterialTemplate->GetName();
{
const FString MaterialPrefix = TEXT("M_"); // Material
const FString MaterialInstancePrefix = TEXT("MI_"); // Material Instance
const FString MaterialInstanceConstantPrefix = TEXT("MIC_"); // Material Instance Constant
if (MIDName.Find(MaterialInstancePrefix, ESearchCase::CaseSensitive) == 0)
{
MIDName = MIDName.RightChop(MaterialInstancePrefix.Len());
}
else if (MIDName.Find(MaterialPrefix, ESearchCase::CaseSensitive) == 0)
{
MIDName = MIDName.RightChop(MaterialPrefix.Len());
}
else if (MIDName.Find(MaterialInstanceConstantPrefix, ESearchCase::CaseSensitive) == 0)
{
MIDName = MIDName.RightChop(MaterialInstanceConstantPrefix.Len());
}
const uint32 MaterialPlaceHolderHash = GetTypeHash(MutableMaterialPlaceholder);
MIDName = FString::Printf(TEXT("%s%s_%u"),TEXT("MID_"),*MIDName, MaterialPlaceHolderHash);
MIDName = MakeUniqueObjectName(GetTransientPackage(), UMaterialInstanceDynamic::StaticClass(), *MIDName, EUniqueObjectNameOptions::GloballyUnique).ToString();
}
MaterialInstance = UMaterialInstanceDynamic::Create(MaterialTemplate, GetTransientPackage(), FName(MIDName));
#else
MaterialInstance = UMaterialInstanceDynamic::Create(MaterialTemplate, GetTransientPackage());
#endif
Material.MaterialInterface = MaterialInstance;
}
if (MaterialInstance)
{
for (const FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam& Param : MutableMaterialPlaceholder.GetParams())
{
switch (Param.Type)
{
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Vector:
if (Param.LayerIndex < 0)
{
FLinearColor Color = Param.Vector;
// HACK: We encode an invalid value (Nan) for table option "None.
// Decoding "None" color parameters that use the material color
if (FMath::IsNaN(Color.R))
{
FMaterialParameterInfo ParameterInfo(Param.ParamName);
MaterialTemplate->GetVectorParameterValue(ParameterInfo, Color);
}
MaterialInstance->SetVectorParameterValue(Param.ParamName, Color);
}
else
{
FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex);
MaterialInstance->SetVectorParameterValueByInfo(ParameterInfo, Param.Vector);
}
break;
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Scalar:
if (Param.LayerIndex < 0)
{
MaterialInstance->SetScalarParameterValue(FName(Param.ParamName), Param.Scalar);
}
else
{
FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex);
MaterialInstance->SetScalarParameterValueByInfo(ParameterInfo, Param.Scalar);
}
break;
case FMutableMaterialPlaceholder::EPlaceHolderParamType::Texture:
if (Param.LayerIndex < 0)
{
MaterialInstance->SetTextureParameterValue(Param.ParamName, Param.Texture.Texture);
}
else
{
FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex);
MaterialInstance->SetTextureParameterValueByInfo(ParameterInfo, Param.Texture.Texture);
}
if (!bDisableNotifyComponentsOfTextureUpdates)
{
NotifyUpdateOnInstanceComponent[InstanceComponentIndex] = true;
}
Material.Textures.Add(Param.Texture);
break;
}
}
}
MaterialInterface = Material.MaterialInterface;
}
// Assign the material to the slot, and add it to the OverrideMaterials
MaterialSlot.MaterialInterface = MaterialInterface;
if (ComponentData)
{
ComponentData->OverrideMaterials.Add(MaterialInterface);
}
}
}
if (!bUseOverrideMaterialsOnly)
{
// Mutable hacky LOD Streaming
if (!OperationData->bStreamMeshLODs)
{
// Copy data from the FirstLODAvailable into the LODs below.
for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstLOD; ++LODIndex)
{
SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(FirstLOD)->LODMaterialMap;
TIndirectArray<FSkeletalMeshLODRenderData>& LODRenderData = SkeletalMesh->GetResourceForRendering()->LODRenderData;
const int32 NumRenderSections = LODRenderData[LODIndex].RenderSections.Num();
check(NumRenderSections == LODRenderData[FirstLOD].RenderSections.Num());
if (NumRenderSections == LODRenderData[FirstLOD].RenderSections.Num())
{
for (int32 RenderSectionIndex = 0; RenderSectionIndex < NumRenderSections; ++RenderSectionIndex)
{
const int32 MaterialIndex = LODRenderData[FirstLOD].RenderSections[RenderSectionIndex].MaterialIndex;
LODRenderData[LODIndex].RenderSections[RenderSectionIndex].MaterialIndex = MaterialIndex;
}
}
}
}
// Force recreate render state after replacing the materials to avoid a crash in the render pipeline if the old materials are GCed while in use.
RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] |= SkeletalMesh->GetResourceForRendering()->IsInitialized() && SkeletalMesh->GetMaterials() != Materials;
SkeletalMesh->SetMaterials(Materials);
#if WITH_EDITOR
if (RecreateRenderStateOnInstanceComponent[InstanceComponentIndex])
{
// Close all open editors for this mesh to invalidate viewports.
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(SkeletalMesh);
}
#endif
}
// Ensure the number of materials is the same on both sides when using overrides.
//check(SkeletalMesh->GetMaterials().Num() == Materials.Num());
}
// Force recreate render state if the mesh is reused and the materials have changed.
// TODO: MTBL-1697 Remove after merging ConvertResources and Callbacks.
if (RecreateRenderStateOnInstanceComponent.Find(true) != INDEX_NONE || NotifyUpdateOnInstanceComponent.Find(true) != INDEX_NONE)
{
MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_RecreateRenderState);
for (TObjectIterator<UCustomizableObjectInstanceUsage> It; It; ++It)
{
UCustomizableObjectInstanceUsage* CustomizableObjectInstanceUsage = *It;
if (!IsValid(CustomizableObjectInstanceUsage) || CustomizableObjectInstanceUsage->GetCustomizableObjectInstance() != Public)
{
continue;
}
#if WITH_EDITOR
if (CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer))
{
continue;
}
#endif
const FName& ComponentName = CustomizableObjectInstanceUsage->GetComponentName();
const int32 ObjectComponentIndex = ModelResources.ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName);
int32 InstanceComponentIndex = -1;
for (int32 CurrentInstanceIndex=0; CurrentInstanceIndex<OperationData->InstanceUpdateData.Components.Num(); ++CurrentInstanceIndex)
{
if (OperationData->InstanceUpdateData.Components[CurrentInstanceIndex].Id.GetValue() == ObjectComponentIndex)
{
InstanceComponentIndex = CurrentInstanceIndex;
break;
}
}
bool bDoRecreateRenderStateOnComponent = RecreateRenderStateOnInstanceComponent.IsValidIndex(InstanceComponentIndex) && RecreateRenderStateOnInstanceComponent[InstanceComponentIndex];
bool bDoNotifyUpdateOnComponent = NotifyUpdateOnInstanceComponent.IsValidIndex(InstanceComponentIndex) && NotifyUpdateOnInstanceComponent[InstanceComponentIndex];
if (!bDoRecreateRenderStateOnComponent && !bDoNotifyUpdateOnComponent)
{
continue;
}
USkeletalMeshComponent* AttachedParent = CustomizableObjectInstanceUsage->GetAttachParent();
TObjectPtr<USkeletalMesh>* SkeletalMesh = SkeletalMeshes.Find(ComponentName);
if (!AttachedParent || (SkeletalMesh && AttachedParent->GetSkeletalMeshAsset() != *SkeletalMesh))
{
continue;
}
if (bDoRecreateRenderStateOnComponent)
{
AttachedParent->RecreateRenderState_Concurrent();
}
else if (bDoNotifyUpdateOnComponent)
{
IStreamingManager::Get().NotifyPrimitiveUpdated(AttachedParent);
}
}
}
{
MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_Exchange);
UCustomizableObjectSystemPrivate* CustomizableObjectSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate();
TexturesToRelease.Empty();
for (const FGeneratedTexture& Texture : NewGeneratedTextures)
{
CustomizableObjectSystem->AddTextureReference(Texture.Key);
}
for (const FGeneratedTexture& Texture : GeneratedTextures)
{
if (CustomizableObjectSystem->RemoveTextureReference(Texture.Key))
{
if (CustomizableObjectSystem->bReleaseTexturesImmediately)
{
TexturesToRelease.Add(Texture); // Texture count is zero, so prepare to release it
}
}
}
Exchange(GeneratedTextures, NewGeneratedTextures);
// All pass-through textures and meshes have been set, no need to keep referencing them from the instance
LoadedPassThroughTexturesPendingSetMaterial.Empty();
LoadedPassThroughMeshesPendingSetMaterial.Empty();
}
}
void UCustomizableObjectInstance::SetReplacePhysicsAssets(bool bReplaceEnabled)
{
bReplaceEnabled ? GetPrivate()->SetCOInstanceFlags(ReplacePhysicsAssets) : GetPrivate()->ClearCOInstanceFlags(ReplacePhysicsAssets);
}
void UCustomizableObjectInstance::SetReuseInstanceTextures(bool bTextureReuseEnabled)
{
bTextureReuseEnabled ? GetPrivate()->SetCOInstanceFlags(ReuseTextures) : GetPrivate()->ClearCOInstanceFlags(ReuseTextures);
}
void UCustomizableObjectInstance::SetForceGenerateResidentMips(bool bForceGenerateResidentMips)
{
bForceGenerateResidentMips ? GetPrivate()->SetCOInstanceFlags(ForceGenerateMipTail) : GetPrivate()->ClearCOInstanceFlags(ForceGenerateMipTail);
}
void UCustomizableObjectInstance::SetIsBeingUsedByComponentInPlay(bool bIsUsedByComponentInPlay)
{
bIsUsedByComponentInPlay ? GetPrivate()->SetCOInstanceFlags(UsedByComponentInPlay) : GetPrivate()->ClearCOInstanceFlags(UsedByComponentInPlay);
}
bool UCustomizableObjectInstance::GetIsBeingUsedByComponentInPlay() const
{
return GetPrivate()->HasCOInstanceFlags(UsedByComponentInPlay);
}
void UCustomizableObjectInstance::SetIsDiscardedBecauseOfTooManyInstances(bool bIsDiscarded)
{
bIsDiscarded ? GetPrivate()->SetCOInstanceFlags(DiscardedByNumInstancesLimit) : GetPrivate()->ClearCOInstanceFlags(DiscardedByNumInstancesLimit);
}
bool UCustomizableObjectInstance::GetIsDiscardedBecauseOfTooManyInstances() const
{
return GetPrivate()->HasCOInstanceFlags(DiscardedByNumInstancesLimit);
}
void UCustomizableObjectInstance::SetIsPlayerOrNearIt(bool bIsPlayerorNearIt)
{
bIsPlayerorNearIt ? GetPrivate()->SetCOInstanceFlags(UsedByPlayerOrNearIt) : GetPrivate()->ClearCOInstanceFlags(UsedByPlayerOrNearIt);
}
float UCustomizableObjectInstance::GetMinSquareDistToPlayer() const
{
return GetPrivate()->MinSquareDistFromComponentToPlayer;
}
void UCustomizableObjectInstance::SetMinSquareDistToPlayer(float NewValue)
{
GetPrivate()->MinSquareDistFromComponentToPlayer = NewValue;
}
int32 UCustomizableObjectInstance::GetNumComponents() const
{
return GetCustomizableObject() ? GetCustomizableObject()->GetComponentCount() : 0;
}
void UCustomizableObjectInstance::SetRequestedLODs(const TMap<FName, uint8>& InMinLODs, const TMap<FName, uint8>& InFirstRequestedLOD, FMutableInstanceUpdateMap& InOutRequestedUpdates)
{
check(PrivateData);
if (!GetPrivate()->CanUpdateInstance())
{
return;
}
if (GetPrivate()->SkeletalMeshStatus == ESkeletalMeshStatus::Error)
{
return;
}
UCustomizableObject* CustomizableObject = GetCustomizableObject();
if (!CustomizableObject)
{
return;
}
if (IsStreamingEnabled(*CustomizableObject))
{
return;
}
if (CVarPreserveUserLODsOnFirstGeneration.GetValueOnGameThread() &&
CustomizableObject->bPreserveUserLODsOnFirstGeneration &&
GetPrivate()->SkeletalMeshStatus != ESkeletalMeshStatus::Success)
{
return;
}
FMutableUpdateCandidate MutableUpdateCandidate(this);
// Clamp Min LOD
const UModelResources* ModelResources = CustomizableObject->GetPrivate()->GetModelResources();
if (!ModelResources)
{
return;
}
bool bMinLODChanged = false;
// Save the new LODs
MutableUpdateCandidate.MinLOD = InMinLODs;
MutableUpdateCandidate.FirstRequestedLOD = Descriptor.GetFirstRequestedLOD();
const TMap<FName, uint8>& FirstRequestedLOD = GetPrivate()->CommittedDescriptorHash.FirstRequestedLOD;
for (const FName& ComponentName : ModelResources->ComponentNamesPerObjectComponent)
{
uint8& InMinLOD = MutableUpdateCandidate.MinLOD.FindOrAdd(ComponentName);
if (const uint8* Result = InMinLODs.Find(ComponentName))
{
InMinLOD = *Result;
}
const uint8 MinLODIdx = CustomizableObject->GetPrivate()->GetMinLODIndex(ComponentName);
MutableUpdateCandidate.QualitySettingMinLODs.Add(ComponentName, MinLODIdx);
int32 MaxLODIdx = 0;
if (const uint8* Found = ModelResources->NumLODsAvailable.Find(ComponentName))
{
MaxLODIdx = *Found - 1;
}
InMinLOD = FMath::Clamp(InMinLOD, MinLODIdx, MaxLODIdx);
uint8 DescriptorMinLOD = 0;
if (const uint8* Result = Descriptor.MinLOD.Find(ComponentName))
{
DescriptorMinLOD = *Result;
}
bMinLODChanged |= DescriptorMinLOD != InMinLOD;
if (UCustomizableObjectSystem::GetInstance()->IsOnlyGenerateRequestedLODsEnabled())
{
uint8 CurrentMinLOD = 0;
if (const uint8* Result = GetPrivate()->CommittedDescriptor.MinLOD.Find(ComponentName))
{
CurrentMinLOD = *Result;
}
PrivateData->SetCOInstanceFlags(InMinLOD > CurrentMinLOD ? PendingLODsDowngrade : ECONone);
uint8 FirstNonStreamedLODIndex = 0;
if (const uint8* Found = ModelResources->NumLODsToStream.Find(ComponentName))
{
FirstNonStreamedLODIndex = *Found;
}
MutableUpdateCandidate.FirstRequestedLOD.Add(ComponentName, FirstNonStreamedLODIndex);
uint8 PredictedLOD = FirstNonStreamedLODIndex;
if (const uint8* Result = InFirstRequestedLOD.Find(ComponentName))
{
PredictedLOD = FMath::Min(*Result, PredictedLOD);
}
if (const uint8* Result = FirstRequestedLOD.Find(ComponentName))
{
PredictedLOD = FMath::Min(*Result, PredictedLOD);
}
PredictedLOD = FMath::Clamp(PredictedLOD, MinLODIdx, MaxLODIdx);
// Save new RequestedLODs
MutableUpdateCandidate.FirstRequestedLOD[ComponentName] = PredictedLOD;
}
}
if (bMinLODChanged || FirstRequestedLOD != MutableUpdateCandidate.FirstRequestedLOD)
{
// TODO: Remove this flag as it will become redundant with the new InOutRequestedUpdates system
PrivateData->SetCOInstanceFlags(PendingLODsUpdate);
InOutRequestedUpdates.Add(this, MutableUpdateCandidate);
}
}
#if WITH_EDITOR
void UCustomizableObjectInstance::Bake(const FBakingConfiguration& InBakingConfiguration)
{
if (ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get())
{
Module->BakeCustomizableObjectInstance(this, InBakingConfiguration);
}
else
{
// Notify of the error
UE_LOG(LogMutable, Error, TEXT("The module \" ICustomizableObjectEditorModule \" could not be loaded and therefore the baking operation could not be started."));
if (InBakingConfiguration.OnBakeOperationCompletedCallback.IsBound())
{
FCustomizableObjectInstanceBakeOutput Output;
Output.bWasBakeSuccessful = false;
Output.SavedPackages.Empty();
InBakingConfiguration.OnBakeOperationCompletedCallback.Execute(Output);
}
}
}
#endif
USkeletalMesh* UCustomizableObjectInstance::GetSkeletalMesh(int32 ObjectComponentIndex) const
{
return GetComponentMeshSkeletalMesh(FName(FString::FromInt(ObjectComponentIndex)));
}
USkeletalMesh* UCustomizableObjectInstance::GetComponentMeshSkeletalMesh(const FName& ComponentName) const
{
TObjectPtr<USkeletalMesh>* Result = GetPrivate()->SkeletalMeshes.Find(ComponentName);
return Result ? *Result : nullptr;
}
bool UCustomizableObjectInstance::HasAnySkeletalMesh() const
{
return !GetPrivate()->SkeletalMeshes.IsEmpty();
}
bool UCustomizableObjectInstance::HasAnyParameters() const
{
return Descriptor.HasAnyParameters();
}
TArray<FName> UCustomizableObjectInstance::GetComponentNames() const
{
TArray<FName> GeneratedComponents;
// For now, the instances don't really hold a direct array of generated components FNames.
// They can be identified with the ones having a valid SkeletalMesh in the SkeletalMeshes array, but this will
// not longer work when we have components that don't have a SkeletalMesh, like grooms, or panel clothing. (TODO)
for ( const TPair<FName, TObjectPtr<USkeletalMesh>>& Entry : GetPrivate()->SkeletalMeshes )
{
if (Entry.Value)
{
GeneratedComponents.Add(Entry.Key);
}
}
return GeneratedComponents;
}
TSubclassOf<UAnimInstance> UCustomizableObjectInstance::GetAnimBP(FName ComponentName, const FName& SlotName) const
{
FCustomizableInstanceComponentData* ComponentData = GetPrivate()->GetComponentData(ComponentName);
if (!ComponentData)
{
FString ErrorMsg = FString::Printf(TEXT("Tried to access an invalid component index [%s] in a Mutable Instance."), *ComponentName.ToString());
UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg);
#if WITH_EDITOR
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable")));
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true);
#endif
return nullptr;
}
TSoftClassPtr<UAnimInstance>* Result = ComponentData->AnimSlotToBP.Find(SlotName);
return Result ? Result->Get() : nullptr;
}
const FGameplayTagContainer& UCustomizableObjectInstance::GetAnimationGameplayTags() const
{
return GetPrivate()->AnimBPGameplayTags;
}
namespace MutablePrivate
{
template<typename DELEGATE>
void InternalForEachAnimInstance(UCustomizableInstancePrivate* Private, FName ComponentName, DELEGATE Delegate)
{
// allow us to log out both bad states with one pass
bool bAnyErrors = false;
if (!Delegate.IsBound())
{
FString ErrorMsg = FString::Printf(TEXT("Attempting to iterate over AnimInstances with an unbound delegate for component [%s]."), *ComponentName.ToString());
UE_LOG(LogMutable, Warning, TEXT("%s"), *ErrorMsg);
#if WITH_EDITOR
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable")));
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Warning, true);
#endif
bAnyErrors = true;
}
const FCustomizableInstanceComponentData* ComponentData = Private->GetComponentData(ComponentName);
if (!ComponentData)
{
FString ErrorMsg = FString::Printf(TEXT("Tried to access an invalid component [%s] in a Mutable Instance."), *ComponentName.ToString());
UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg);
#if WITH_EDITOR
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable")));
FMessageLog MessageLog("Mutable");
MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true);
#endif
bAnyErrors = true;
}
if (bAnyErrors)
{
return;
}
for (const TPair<FName, TSoftClassPtr<UAnimInstance>>& MapElem : ComponentData->AnimSlotToBP)
{
const FName& Index = MapElem.Key;
const TSoftClassPtr<UAnimInstance>& AnimBP = MapElem.Value;
// if this _can_ resolve to a real AnimBP
if (!AnimBP.IsNull())
{
// force a load right now - we don't know whether we would have loaded already - this could be called in editor
const TSubclassOf<UAnimInstance> LiveAnimBP = MutablePrivate::LoadClass(AnimBP);
if (LiveAnimBP)
{
Delegate.Execute(Index, LiveAnimBP);
}
}
}
}
}
void UCustomizableObjectInstance::ForEachComponentAnimInstance(FName ComponentName, FEachComponentAnimInstanceClassDelegate Delegate) const
{
MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate);
}
void UCustomizableObjectInstance::ForEachComponentAnimInstance(FName ComponentName, FEachComponentAnimInstanceClassNativeDelegate Delegate) const
{
MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate);
}
// Deprecated
void UCustomizableObjectInstance::ForEachAnimInstance(int32 ObjectComponentIndex, FEachComponentAnimInstanceClassDelegate Delegate) const
{
UCustomizableObject* CO = GetCustomizableObject();
if (CO)
{
FName ComponentName = CO->GetPrivate()->GetComponentName(FCustomizableObjectComponentIndex(ObjectComponentIndex));
MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate);
}
}
// Deprecated
void UCustomizableObjectInstance::ForEachAnimInstance(int32 ObjectComponentIndex, FEachComponentAnimInstanceClassNativeDelegate Delegate) const
{
UCustomizableObject* CO = GetCustomizableObject();
if (CO)
{
FName ComponentName = CO->GetPrivate()->GetComponentName(FCustomizableObjectComponentIndex(ObjectComponentIndex));
MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate);
}
}
bool UCustomizableObjectInstance::AnimInstanceNeedsFixup(TSubclassOf<UAnimInstance> AnimInstanceClass) const
{
return PrivateData->AnimBpPhysicsAssets.Contains(AnimInstanceClass);
}
void UCustomizableObjectInstance::AnimInstanceFixup(UAnimInstance* InAnimInstance) const
{
if (!InAnimInstance)
{
return;
}
TSubclassOf<UAnimInstance> AnimInstanceClass = InAnimInstance->GetClass();
const TArray<FAnimInstanceOverridePhysicsAsset>* AnimInstanceOverridePhysicsAssets =
PrivateData->GetGeneratedPhysicsAssetsForAnimInstance(AnimInstanceClass);
if (!AnimInstanceOverridePhysicsAssets)
{
return;
}
// Swap RigidBody anim nodes override physics assets with mutable generated ones.
if (UAnimBlueprintGeneratedClass* AnimClass = Cast<UAnimBlueprintGeneratedClass>(AnimInstanceClass))
{
bool bPropertyMismatchFound = false;
const int32 AnimNodePropertiesNum = AnimClass->AnimNodeProperties.Num();
for (const FAnimInstanceOverridePhysicsAsset& PropIndexAndAsset : *AnimInstanceOverridePhysicsAssets)
{
check(PropIndexAndAsset.PropertyIndex >= 0);
if (PropIndexAndAsset.PropertyIndex >= AnimNodePropertiesNum)
{
bPropertyMismatchFound = true;
continue;
}
const int32 AnimNodePropIndex = PropIndexAndAsset.PropertyIndex;
FStructProperty* StructProperty = AnimClass->AnimNodeProperties[AnimNodePropIndex];
if (!ensure(StructProperty))
{
bPropertyMismatchFound = true;
continue;
}
const bool bIsRigidBodyNode = StructProperty->Struct->IsChildOf(FAnimNode_RigidBody::StaticStruct());
if (!bIsRigidBodyNode)
{
bPropertyMismatchFound = true;
continue;
}
FAnimNode_RigidBody* RbanNode = StructProperty->ContainerPtrToValuePtr<FAnimNode_RigidBody>(InAnimInstance);
if (!ensure(RbanNode))
{
bPropertyMismatchFound = true;
continue;
}
RbanNode->OverridePhysicsAsset = PropIndexAndAsset.PhysicsAsset;
}
#if WITH_EDITOR
if (bPropertyMismatchFound)
{
UE_LOG(LogMutable, Warning, TEXT("AnimBp %s is not in sync with the data stored in the CO %s. A CO recompilation may be needed."),
*AnimInstanceClass.Get()->GetName(),
*GetCustomizableObject()->GetName());
}
#endif
}
}
const TArray<FAnimInstanceOverridePhysicsAsset>* UCustomizableInstancePrivate::GetGeneratedPhysicsAssetsForAnimInstance(TSubclassOf<UAnimInstance> AnimInstanceClass) const
{
const FAnimBpGeneratedPhysicsAssets* Found = AnimBpPhysicsAssets.Find(AnimInstanceClass);
if (!Found)
{
return nullptr;
}
return &Found->AnimInstancePropertyIndexAndPhysicsAssets;
}
FInstancedStruct UCustomizableObjectInstance::GetExtensionInstanceData(const UCustomizableObjectExtension* Extension) const
{
const FExtensionInstanceData* FoundData = Algo::FindBy(PrivateData->ExtensionInstanceData, Extension, &FExtensionInstanceData::Extension);
if (FoundData)
{
return FoundData->Data;
}
// Data not found. Return an empty instance.
return FInstancedStruct();
}
TSet<UAssetUserData*> UCustomizableObjectInstance::GetMergedAssetUserData(int32 ComponentIndex) const
{
UCustomizableInstancePrivate* PrivateInstanceData = GetPrivate();
if (PrivateInstanceData && PrivateInstanceData->ComponentsData.IsValidIndex(ComponentIndex))
{
TSet<UAssetUserData*> Set;
// Have to convert to UAssetUserData* because BP functions don't support TObjectPtr
for (const TObjectPtr<UAssetUserData>& Elem : PrivateInstanceData->ComponentsData[ComponentIndex].AssetUserDataArray)
{
Set.Add(Elem);
}
return Set;
}
else
{
return TSet<UAssetUserData*>();
}
}
bool UCustomizableObjectInstance::CanUpdateInstance() const
{
return GetPrivate()->CanUpdateInstance();
}
#if WITH_EDITORONLY_DATA
void CalculateBonesToRemove(const FSkeletalMeshLODRenderData& LODResource, const FReferenceSkeleton& RefSkeleton, TArray<FBoneReference>& OutBonesToRemove)
{
const int32 NumBones = RefSkeleton.GetNum();
OutBonesToRemove.Empty(NumBones);
TArray<bool> RemovedBones;
RemovedBones.Init(true, NumBones);
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
if (LODResource.RequiredBones.Find((uint16)BoneIndex) != INDEX_NONE)
{
RemovedBones[BoneIndex] = false;
continue;
}
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
if (RemovedBones.IsValidIndex(ParentIndex) && !RemovedBones[ParentIndex])
{
OutBonesToRemove.Add(RefSkeleton.GetBoneName(BoneIndex));
}
}
}
void UCustomizableInstancePrivate::RegenerateImportedModels()
{
MUTABLE_CPUPROFILER_SCOPE(RegenerateImportedModels);
using UVsVectorType = typename TDecay<decltype(DeclVal<FSoftSkinVertex>().UVs[0])>::Type;
struct FMeshDataConvertJob
{
int32 NumIndices = 0;
int32 IndicesOffset = 0;
const FRawStaticIndexBuffer16or32Interface* IndexBuffer = nullptr;
uint32* DestIndexBuffer = nullptr;
int32 NumVertices = 0;
int32 VerticesOffset = 0;
const FStaticMeshVertexBuffers* StaticVertexBuffers = nullptr;
const FSkinWeightVertexBuffer* SkinWeightVertexBuffer = nullptr;
FSoftSkinVertex* DestVertexBuffer = nullptr;
};
constexpr int32 MaxJobCost = 1 << 18;
constexpr int32 MaxVerticesPerJob = FMath::Max<int32>(1, MaxJobCost / sizeof(FSoftSkinVertex));
constexpr int32 MaxIndicesPerJob = FMath::Max<int32>(1, MaxJobCost / sizeof(int32));
TArray<FMeshDataConvertJob, TInlineAllocator<64>> Jobs;
TArray<int32, TInlineAllocator<64>> JobRanges;
JobRanges.Add(0);
for (const TTuple<FName, TObjectPtr<USkeletalMesh>>& Tuple : SkeletalMeshes)
{
USkeletalMesh* SkeletalMesh = Tuple.Get<1>();
if (!SkeletalMesh)
{
continue;
}
const bool bIsTransientMesh = static_cast<bool>(SkeletalMesh->HasAllFlags(EObjectFlags::RF_Transient));
if (!bIsTransientMesh)
{
// This must be a pass-through referenced mesh so don't do anything to it
continue;
}
FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering();
if (!RenderData || RenderData->IsInitialized())
{
continue;
}
for (UClothingAssetBase* ClothingAssetBase : SkeletalMesh->GetMeshClothingAssets())
{
if (!ClothingAssetBase)
{
continue;
}
UClothingAssetCommon* ClothAsset = Cast<UClothingAssetCommon>(ClothingAssetBase);
if (!ClothAsset)
{
continue;
}
if (!ClothAsset->LodData.Num())
{
continue;
}
for (FClothLODDataCommon& ClothLodData : ClothAsset->LodData)
{
ClothLodData.PointWeightMaps.Empty(16);
for (TPair<uint32, FPointWeightMap>& WeightMap : ClothLodData.PhysicalMeshData.WeightMaps)
{
if (WeightMap.Value.Num())
{
FPointWeightMap& PointWeightMap = ClothLodData.PointWeightMaps.AddDefaulted_GetRef();
PointWeightMap.Initialize(WeightMap.Value, WeightMap.Key);
}
}
}
}
FSkeletalMeshModel* ImportedModel = SkeletalMesh->GetImportedModel();
ImportedModel->bGuidIsHash = false;
ImportedModel->SkeletalMeshModelGUID = FGuid::NewGuid();
ImportedModel->LODModels.Empty();
int32 OriginalIndex = 0;
for (int32 LODIndex = 0; LODIndex < RenderData->LODRenderData.Num(); ++LODIndex)
{
ImportedModel->LODModels.Add(new FSkeletalMeshLODModel());
FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex];
FSkeletalMeshLODRenderData& LODRenderData = RenderData->LODRenderData[LODIndex];
LODModel.ActiveBoneIndices = LODRenderData.ActiveBoneIndices;
LODModel.NumTexCoords = LODRenderData.GetNumTexCoords();
LODModel.RequiredBones = LODRenderData.RequiredBones;
LODModel.NumVertices = LODRenderData.GetNumVertices();
// Indices
if (LODRenderData.MultiSizeIndexContainer.IsIndexBufferValid())
{
const FRawStaticIndexBuffer16or32Interface* IndexBuffer =
LODRenderData.MultiSizeIndexContainer.GetIndexBuffer();
const int32 NumIndices = IndexBuffer->Num();
LODModel.IndexBuffer.SetNum(NumIndices);
uint32* BaseDestIndexBuffer = LODModel.IndexBuffer.GetData();
const int32 NumIndicesJobs = FMath::DivideAndRoundUp(NumIndices, MaxIndicesPerJob);
int32 CurrentJobIndex = Jobs.Num();
Jobs.SetNum(Jobs.Num() + NumIndicesJobs);
for (int32 I = 0; I < NumIndicesJobs; ++I)
{
FMeshDataConvertJob Job;
Job.NumIndices = FMath::Min(MaxIndicesPerJob, NumIndices - I*MaxIndicesPerJob);
Job.IndexBuffer = IndexBuffer;
Job.IndicesOffset = I*MaxIndicesPerJob;
Job.DestIndexBuffer = BaseDestIndexBuffer + I*MaxIndicesPerJob;
Jobs[I + CurrentJobIndex] = Job;
}
}
LODModel.Sections.SetNum(LODRenderData.RenderSections.Num());
for (int32 SectionIndex = 0; SectionIndex < LODRenderData.RenderSections.Num(); ++SectionIndex)
{
check(!LODRenderData.StaticVertexBuffers.StaticMeshVertexBuffer.GetUseHighPrecisionTangentBasis());
const FSkelMeshRenderSection& RenderSection = LODRenderData.RenderSections[SectionIndex];
FSkelMeshSection& ImportedSection = ImportedModel->LODModels[LODIndex].Sections[SectionIndex];
ImportedSection.CorrespondClothAssetIndex = RenderSection.CorrespondClothAssetIndex;
ImportedSection.ClothingData = RenderSection.ClothingData;
if (RenderSection.ClothMappingDataLODs.Num())
{
TArray<FMeshToMeshVertData>& ImportedClothMappingData = ImportedSection.ClothMappingDataLODs.AddDefaulted_GetRef();
const int32 NumClothVerts = LODRenderData.ClothVertexBuffer.GetNumVertices();
ImportedClothMappingData.SetNumUninitialized(NumClothVerts);
for (int32 ClothVertDataIndex = 0; ClothVertDataIndex < NumClothVerts; ++ClothVertDataIndex)
{
ImportedClothMappingData[ClothVertDataIndex] = LODRenderData.ClothVertexBuffer.MappingData(ClothVertDataIndex);
}
}
// Vertices
ImportedSection.NumVertices = RenderSection.NumVertices;
ImportedSection.SoftVertices.Empty(RenderSection.NumVertices);
ImportedSection.SoftVertices.AddUninitialized(RenderSection.NumVertices);
ImportedSection.bUse16BitBoneIndex = LODRenderData.DoesVertexBufferUse16BitBoneIndex();
const int32 SectionNumVertices = RenderSection.NumVertices;
const int32 SectionBaseVertexIndex = RenderSection.BaseVertexIndex;
const FStaticMeshVertexBuffers* StaticVertexBuffers = &LODRenderData.StaticVertexBuffers;
const FSkinWeightVertexBuffer* SkinWeightVertexBuffer = &LODRenderData.SkinWeightVertexBuffer;
FSoftSkinVertex* BaseSectionSoftVertex = ImportedSection.SoftVertices.GetData();
int32 NumSectionJobs = FMath::DivideAndRoundUp<int32>(RenderSection.NumVertices, MaxVerticesPerJob);
int32 FirstSectionJobIndex = Jobs.Num();
Jobs.SetNum(Jobs.Num() + NumSectionJobs);
for (int32 I = 0; I < NumSectionJobs; ++I)
{
FMeshDataConvertJob Job;
Job.NumVertices = FMath::Min(MaxVerticesPerJob, SectionNumVertices - I*MaxVerticesPerJob);
Job.StaticVertexBuffers = StaticVertexBuffers;
Job.SkinWeightVertexBuffer = SkinWeightVertexBuffer;
Job.VerticesOffset = SectionBaseVertexIndex + I*MaxVerticesPerJob;
Job.DestVertexBuffer = BaseSectionSoftVertex + I*MaxVerticesPerJob;
Jobs[I + FirstSectionJobIndex] = Job;
}
// Triangles
ImportedSection.NumTriangles = RenderSection.NumTriangles;
ImportedSection.BaseIndex = RenderSection.BaseIndex;
ImportedSection.BaseVertexIndex = RenderSection.BaseVertexIndex;
ImportedSection.BoneMap = RenderSection.BoneMap;
// Add bones to remove
CalculateBonesToRemove(LODRenderData, SkeletalMesh->GetRefSkeleton(), SkeletalMesh->GetLODInfo(LODIndex)->BonesToRemove);
const TArray<int32>& LODMaterialMap = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap;
if (LODMaterialMap.IsValidIndex(RenderSection.MaterialIndex))
{
ImportedSection.MaterialIndex = LODMaterialMap[RenderSection.MaterialIndex];
}
else
{
// The material should have been in the LODMaterialMap
ensureMsgf(false, TEXT("Unexpected material index in UCustomizableInstancePrivate::RegenerateImportedModel"));
// Fallback index, may shift materials around sections
if (SkeletalMesh->GetMaterials().IsValidIndex(RenderSection.MaterialIndex))
{
ImportedSection.MaterialIndex = RenderSection.MaterialIndex;
}
else
{
ImportedSection.MaterialIndex = 0;
}
}
ImportedSection.MaxBoneInfluences = RenderSection.MaxBoneInfluences;
ImportedSection.OriginalDataSectionIndex = OriginalIndex++;
FSkelMeshSourceSectionUserData& SectionUserData = LODModel.UserSectionsData.FindOrAdd(ImportedSection.OriginalDataSectionIndex);
SectionUserData.bCastShadow = RenderSection.bCastShadow;
SectionUserData.bDisabled = RenderSection.bDisabled;
SectionUserData.CorrespondClothAssetIndex = RenderSection.CorrespondClothAssetIndex;
SectionUserData.ClothingData.AssetGuid = RenderSection.ClothingData.AssetGuid;
SectionUserData.ClothingData.AssetLodIndex = RenderSection.ClothingData.AssetLodIndex;
LODModel.SyncronizeUserSectionsDataArray();
// DDC keys
const USkeletalMeshLODSettings* LODSettings = SkeletalMesh->GetLODSettings();
const bool bValidLODSettings = LODSettings && LODSettings->GetNumberOfSettings() > LODIndex;
const FSkeletalMeshLODGroupSettings* SkeletalMeshLODGroupSettings = bValidLODSettings ? &LODSettings->GetSettingsForLODLevel(LODIndex) : nullptr;
FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex);
LODInfo->BuildGUID = LODInfo->ComputeDeriveDataCacheKey(SkeletalMeshLODGroupSettings);
LODModel.BuildStringID = LODModel.GetLODModelDeriveDataKey();
}
}
// Try to bundle Jobs so all cost roughly the same. Large Jobs are already split so they cost about MaxJobCost.
// It uses a gready approach and assumes in general Jobs are sorted by cost.
const int32 NumJobs = Jobs.Num();
for (int32 JobIndex = 0; JobIndex < NumJobs;)
{
int32 RangeJobCost = 0;
for (; JobIndex < NumJobs;)
{
int32 CurrentJobCost = Jobs[JobIndex].NumVertices*sizeof(FSoftSkinVertex) + Jobs[JobIndex].NumIndices*sizeof(int32);
RangeJobCost += CurrentJobCost;
if (RangeJobCost >= MaxJobCost)
{
// Go to the next Job if the current job alone cost is larger than MaxJobCost
// and no other job has been processed for the range.
JobIndex += static_cast<int32>(CurrentJobCost == RangeJobCost);
break;
}
++JobIndex;
}
JobRanges.Add(JobIndex);
}
}
{
MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion)
ParallelFor(JobRanges.Num() - 1, [&JobRanges, &Jobs](int32 JobId)
{
int32 JobRangeBegin = JobRanges[JobId];
int32 JobRangeEnd = JobRanges[JobId + 1];
for (int32 J = JobRangeBegin; J < JobRangeEnd; ++J)
{
FMeshDataConvertJob Job = Jobs[J];
if (Job.NumIndices > 0)
{
MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion_Indices)
for (int32 Index = 0; Index < Job.NumIndices; ++Index)
{
Job.DestIndexBuffer[Index] = Job.IndexBuffer->Get(Job.IndicesOffset + Index);
}
}
if (Job.NumVertices > 0)
{
MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion_Vertices)
check(Job.StaticVertexBuffers);
check(Job.SkinWeightVertexBuffer);
check(Job.DestVertexBuffer);
const FPositionVertex* PositionBuffer =
static_cast<const FPositionVertex*>(Job.StaticVertexBuffers->PositionVertexBuffer.GetVertexData()) + Job.VerticesOffset;
const FPackedNormal* TangentBuffer =
static_cast<const FPackedNormal*>(Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetTangentData()) + Job.VerticesOffset*2;
const int32 NumTexCoords = Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetNumTexCoords();
const int32 UVSize = Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetUseFullPrecisionUVs() ? 2 * sizeof(float) : 2 * sizeof(FFloat16);
const uint8* TexCoordBuffer =
static_cast<const uint8*>(Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetTexCoordData()) + Job.VerticesOffset*NumTexCoords*UVSize;
const FColor* ColorBuffer =
static_cast<const FColor*>(Job.StaticVertexBuffers->ColorVertexBuffer.GetVertexData());
const bool bHasColor = !!ColorBuffer;
ColorBuffer += Job.VerticesOffset;
const FSkinWeightVertexBuffer* SkinWeightBuffer = Job.SkinWeightVertexBuffer;
const int32 MaxBoneInfluences = Job.SkinWeightVertexBuffer->GetMaxBoneInfluences();
for (int32 JobVertexIndex = 0; JobVertexIndex < Job.NumVertices; ++JobVertexIndex)
{
FSoftSkinVertex* Vertex = Job.DestVertexBuffer + JobVertexIndex;
FMemory::Memzero(Vertex, sizeof(FSoftSkinVertex));
Vertex->Position = PositionBuffer[JobVertexIndex].Position;
const FPackedNormal* Tangent = TangentBuffer + JobVertexIndex*2;
Vertex->TangentX = Tangent[0].ToFVector3f();
Vertex->TangentZ = Tangent[1].ToFVector3f();
float TangentSign = Tangent[1].Vector.W < 0 ? -1.0f : 1.0f;
Vertex->TangentY = FVector3f::CrossProduct(Vertex->TangentZ, Vertex->TangentX) * TangentSign;
const void* TexCoord = TexCoordBuffer + JobVertexIndex*NumTexCoords*UVSize;
// Switch based jumptable.
if (UVSize == 4)
{
const FFloat16* TypedSource = reinterpret_cast<const FFloat16*>(TexCoord);
switch (NumTexCoords)
{
case 4: Vertex->UVs[3] = { TypedSource[6], TypedSource[7] }; // Fall through
case 3: Vertex->UVs[2] = { TypedSource[4], TypedSource[5] }; // Fall through
case 2: Vertex->UVs[1] = { TypedSource[2], TypedSource[3] }; // Fall through
case 1: Vertex->UVs[0] = { TypedSource[0], TypedSource[1] }; // Fall through
default: break;
}
}
else
{
const FVector2f* TypedSource = reinterpret_cast<const FVector2f*>(TexCoord);
switch (NumTexCoords)
{
case 4: Vertex->UVs[3] = TypedSource[3]; // Fall through
case 3: Vertex->UVs[2] = TypedSource[2]; // Fall through
case 2: Vertex->UVs[1] = TypedSource[1]; // Fall through
case 1: Vertex->UVs[0] = TypedSource[0]; // Fall through
default: break;
}
}
Vertex->Color = bHasColor ? ColorBuffer[JobVertexIndex] : FColor::White;
const int32 SourceVertexIndex = (JobVertexIndex + Job.VerticesOffset);
for (int32 InfluenceIndex = 0; InfluenceIndex < MaxBoneInfluences; ++InfluenceIndex)
{
Vertex->InfluenceBones[InfluenceIndex] = SkinWeightBuffer->GetBoneIndex(SourceVertexIndex, InfluenceIndex);
Vertex->InfluenceWeights[InfluenceIndex] = SkinWeightBuffer->GetBoneWeight(SourceVertexIndex, InfluenceIndex);
}
}
}
}
});
}
}
#endif