Files
2025-05-18 13:04:45 +08:00

958 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithRuntimeUtils.h"
#include "DatasmithTranslator.h"
#include "LogCategory.h"
#include "DatasmithPayload.h"
#include "DatasmithUtils.h"
#include "Engine/CollisionProfile.h"
#include "IDatasmithSceneElements.h"
#include "Engine/StaticMeshSourceData.h"
#include "StaticMeshResources.h"
#include "Misc/Paths.h"
#include "Utility/DatasmithMeshHelper.h"
#include "Async/Async.h"
#include "Components/StaticMeshComponent.h"
#include "PhysicsEngine/BodySetup.h"
#include "Engine/StaticMeshActor.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "StaticMeshAttributes.h"
#include "StaticMeshOperations.h"
#include "UObject/Package.h"
#if WITH_EDITOR
#endif
namespace DatasmithRuntime
{
bool FSceneImporter::ProcessMeshData(FAssetData& MeshData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::ProcessMeshData);
// Something is wrong. Do not go any further
if (MeshData.HasState(EAssetState::PendingDelete))
{
UE_LOG(LogDatasmithRuntime, Error, TEXT("A mesh marked for deletion is actually used by the scene"));
return false;
}
if (MeshData.HasState(EAssetState::Processed))
{
return true;
}
TSharedPtr< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[MeshData.ElementId]);
URuntimeMesh* StaticMesh = MeshData.GetObject<URuntimeMesh>();
// If static mesh already completed, check if geometry has changed
if (StaticMesh)
{
uint32 NewResourceHash = GetTypeHash(MeshElement->GetFileHash());
// Force recreation of the static mesh if the mesh's file has changed
if (MeshData.ResourceHash != NewResourceHash)
{
MeshData.ClearState(EAssetState::Completed);
FAssetRegistry::UnregisterAssetData(StaticMesh, SceneKey, MeshData.ElementId);
StaticMesh = nullptr;
MeshData.Object.Reset();
}
}
if (StaticMesh == nullptr)
{
MeshData.Hash = GetTypeHash(MeshElement->CalculateElementHash(true), EDataType::Mesh);
MeshData.ResourceHash = GetTypeHash(MeshElement->GetFileHash());
if (UObject* AssetPtr = FAssetRegistry::FindObjectFromHash(MeshData.Hash))
{
StaticMesh = Cast<URuntimeMesh>(AssetPtr);
check(StaticMesh);
}
else
{
FString MeshName = FString::Printf(TEXT("SM_%s_%d"), MeshElement->GetName(), MeshData.ElementId);
MeshName = FDatasmithUtils::SanitizeObjectName(MeshName);
#ifdef ASSET_DEBUG
UPackage* Package = CreatePackage(*FPaths::Combine( TEXT("/Game/Runtime/Meshes"), MeshName));
StaticMesh = NewObject< URuntimeMesh >(Package, NAME_None, RF_Public);
#else
StaticMesh = NewObject< URuntimeMesh >(GetTransientPackage());
#endif
check(StaticMesh);
RenameObject(StaticMesh, *MeshName);
StaticMesh->SetWorld(RootComponent->GetWorld());
// Add the creation of the mesh to the queue
FActionTaskFunction TaskFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
this->OnGoingTasks.Emplace( Async(
#if WITH_EDITOR
EAsyncExecution::LargeThreadPool,
#else
EAsyncExecution::ThreadPool,
#endif
[this, ElementId = Referencer.GetId()]() -> bool
{
return this->CreateStaticMesh(ElementId);
},
[this]()->void
{
this->ActionCounter.Increment();
}
));
return EActionResult::Succeeded;
};
AddToQueue(EQueueTask::MeshQueue, { TaskFunc, {EDataType::Mesh, MeshData.ElementId, 0 } });
TasksToComplete |= EWorkerTask::MeshCreate;
// If applicable, Apply metadata on newly created static mesh
ApplyMetadata(MeshData.MetadataId, StaticMesh);
MeshElementSet.Add(MeshData.ElementId);
}
MeshData.Object = TWeakObjectPtr< UObject >(StaticMesh);
}
// Collect materials used by static mesh
for (int32 Index = 0; Index < MeshElement->GetMaterialSlotCount(); Index++)
{
if (const IDatasmithMaterialIDElement* MaterialIDElement = MeshElement->GetMaterialSlotAt(Index).Get())
{
const FString MaterialPathName(MaterialIDElement->GetName());
if (!MaterialPathName.StartsWith(TEXT("/")))
{
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialPathName))
{
ProcessMaterialData(AssetDataList[*MaterialElementIdPtr]);
}
}
else
{
// Force loading of material asset if it exists. It will be assigned when the mesh is built
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>(FSoftObjectPath(MaterialPathName).TryLoad());
if (MaterialInterface == nullptr)
{
UE_LOG(LogDatasmithRuntime, Warning, TEXT("ProcessMeshData: Cannot find material %s"), *MaterialPathName);
}
}
}
}
// If static mesh already completed, then material assignment has changed
if (MeshData.HasState(EAssetState::Completed))
{
UpdateStaticMeshMaterials(MeshData);
}
// Create BodySetup in game thread to avoid allocating during a garbage collect later on
if (StaticMesh->GetBodySetup() == nullptr)
{
StaticMesh->CreateBodySetup();
}
MeshData.SetState(EAssetState::Processed);
FAssetRegistry::RegisterAssetData(StaticMesh, SceneKey, MeshData);
return true;
}
bool FSceneImporter::ProcessMeshActorData(FActorData& ActorData, IDatasmithMeshActorElement* MeshActorElement)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::ProcessMeshActorData);
if (ActorData.HasState(EAssetState::Processed))
{
return true;
}
// Invalid reference to a mesh. Abort creation of component
if (FCString::Strlen(MeshActorElement->GetStaticMeshPathName()) == 0)
{
ActorData.SetState(EAssetState::Processed);
return false;
}
FActionTaskFunction CreateComponentFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->CreateMeshComponent(Referencer.GetId(), Cast<UStaticMesh>(Object));
};
FString StaticMeshPathName(MeshActorElement->GetStaticMeshPathName());
UStaticMesh* StaticMesh = nullptr;
if (!StaticMeshPathName.StartsWith(TEXT("/")))
{
if (FSceneGraphId* MeshElementIdPtr = AssetElementMapping.Find(MeshPrefix + StaticMeshPathName))
{
FAssetData& MeshData = AssetDataList[*MeshElementIdPtr];
if (!ProcessMeshData(MeshData))
{
return false;
}
AddToQueue(EQueueTask::NonAsyncQueue, { CreateComponentFunc, *MeshElementIdPtr, { EDataType::Actor, ActorData.ElementId, 0 } });
TasksToComplete |= EWorkerTask::MeshComponentCreate;
ActorData.AssetId = *MeshElementIdPtr;
StaticMesh = MeshData.GetObject<UStaticMesh>();
}
}
else
{
StaticMesh = Cast<UStaticMesh>(FSoftObjectPath(StaticMeshPathName).TryLoad());
}
// The referenced static mesh was not found. Abort creation of component
if (StaticMesh == nullptr)
{
return false;
}
if (MeshActorElement->GetMaterialOverridesCount() > 0)
{
FActionTaskFunction AssignMaterialFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->AssignMaterial(Referencer, Cast<UMaterialInstanceDynamic>(Object));
};
TArray< FStaticMaterial >& StaticMaterials = StaticMesh->GetStaticMaterials();
TMap<FString, int32> SlotMapping;
SlotMapping.Reserve(StaticMaterials.Num());
for (int32 Index = 0; Index < StaticMaterials.Num(); ++Index)
{
const FStaticMaterial& StaticMaterial = StaticMaterials[Index];
if (StaticMaterial.MaterialSlotName != NAME_None)
{
SlotMapping.Add(StaticMaterial.MaterialSlotName.ToString(), Index);
}
}
// #ue_datasmithruntime: Missing code to handle the case where a MaterialID's name is an asset's path
// All the materials of the static mesh are overridden by one single material
// Note: for that case, we assume the actor has only one override
if (MeshActorElement->GetMaterialOverride(0)->GetId() == -1)
{
TSharedPtr<const IDatasmithMaterialIDElement> MaterialIDElement = MeshActorElement->GetMaterialOverride(0);
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialIDElement->GetName()))
{
ProcessMaterialData(AssetDataList[*MaterialElementIdPtr]);
DependencyList.Add(MaterialIDElement->GetNodeId(), { EDataType::Actor, ActorData.ElementId, 0xffff });
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, { EDataType::Actor, ActorData.ElementId, 0xffff } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
}
else
{
for (int32 Index = 0; Index < MeshActorElement->GetMaterialOverridesCount(); ++Index)
{
TSharedPtr<const IDatasmithMaterialIDElement> MaterialIDElement = MeshActorElement->GetMaterialOverride(Index);
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialIDElement->GetName()))
{
ProcessMaterialData(AssetDataList[*MaterialElementIdPtr]);
const FString MaterialSlotName = FString::Printf(TEXT("%d"), MaterialIDElement->GetId());
// If staticmesh has no material assigned, material assignment will be queued later when the mesh component is created
if (StaticMaterials.Num() > 0 && SlotMapping.Contains(MaterialSlotName))
{
const int32 MaterialIndex = SlotMapping[MaterialSlotName];
DependencyList.Add(MaterialIDElement->GetNodeId(), { EDataType::Actor, ActorData.ElementId, (uint16)MaterialIndex });
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, { EDataType::Actor, ActorData.ElementId, (uint16)MaterialIndex } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
}
}
}
}
ActorData.SetState(EAssetState::Processed);
return true;
}
bool FSceneImporter::CreateStaticMesh(FSceneGraphId ElementId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CreateStaticMesh);
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "CreateStaticMesh");
#endif
TSharedRef< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[ElementId]).ToSharedRef();
FAssetData& MeshData = AssetDataList[ElementId];
UStaticMesh* StaticMesh = MeshData.GetObject<UStaticMesh>();
if (StaticMesh == nullptr)
{
ensure(false);
return false;
}
FDatasmithMeshElementPayload MeshPayload;
{
if (!Translator->LoadStaticMesh(MeshElement, MeshPayload))
{
// If mesh cannot be loaded, add scene's resource path if valid and retry
bool bSecondTrySucceeded = false;
if (FPaths::DirectoryExists(SceneElement->GetResourcePath()) && FPaths::IsRelative(MeshElement->GetFile()))
{
MeshElement->SetFile( *FPaths::Combine(SceneElement->GetResourcePath(), MeshElement->GetFile()) );
bSecondTrySucceeded = Translator->LoadStaticMesh(MeshElement, MeshPayload);
}
if (!bSecondTrySucceeded)
{
// #ueent_datasmithruntime: TODO : Update FAssetFactory
ActionCounter.Add(MeshData.Referencers.Num());
FAssetRegistry::UnregisteredAssetsData(StaticMesh, SceneKey, [](FAssetData& AssetData) -> void
{
AssetData.AddState(EAssetState::Completed);
AssetData.Object.Reset();
});
UE_LOG(LogDatasmithRuntime, Warning, TEXT("CreateStaticMesh: Loading file %s failed. Mesh element %s has not been imported"), MeshElement->GetFile(), MeshElement->GetLabel());
return true;
}
}
}
TArray< FMeshDescription >& MeshDescriptions = MeshPayload.LodMeshes;
// Empty mesh?
if (MeshDescriptions.Num() == 0)
{
ActionCounter.Add(MeshData.Referencers.Num());
FAssetRegistry::UnregisteredAssetsData(StaticMesh, SceneKey, [](FAssetData& AssetData) -> void
{
AssetData.AddState(EAssetState::Completed);
AssetData.Object.Reset();
});
UE_LOG(LogDatasmithRuntime, Warning, TEXT("CreateStaticMesh: %s does not have a mesh description"), MeshElement->GetLabel());
return true;
}
// 4. Collisions
StaticMesh->GetBodySetup()->AggGeom.EmptyElements();
if (ImportOptions.BuildCollisions != ECollisionEnabled::NoCollision && ImportOptions.CollisionType != ECollisionTraceFlag::CTF_UseComplexAsSimple)
{
ProcessCollision(StaticMesh, MeshPayload);
}
// Extracted from FDatasmithStaticMeshImporter::SetupStaticMesh
#if WITH_EDITOR
StaticMesh->SetNumSourceModels(MeshDescriptions.Num());
#endif
FillStaticMeshMaterials(MeshData, MeshDescriptions);
for (int32 LodIndex = 0; LodIndex < MeshDescriptions.Num(); ++LodIndex)
{
FMeshDescription& MeshDescription = MeshDescriptions[LodIndex];
// We should always have some UV data in channel 0 because it is used in the mesh tangent calculation during the build.
if (!DatasmithMeshHelper::HasUVData(MeshDescription, 0))
{
DatasmithMeshHelper::CreateDefaultUVs(MeshDescription);
}
// We should always have valid normals, tangents and binormals
bool bHasInvalidNormals;
bool bHasInvalidTangents;
FStaticMeshOperations::HasInvalidVertexInstanceNormalsOrTangents(MeshDescription, bHasInvalidNormals, bHasInvalidTangents);
// If normals are invalid, compute normals and tangents at polygon level then vertex level
if (bHasInvalidNormals)
{
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(MeshDescription, THRESH_POINTS_ARE_SAME);
const EComputeNTBsFlags ComputeFlags = EComputeNTBsFlags::Normals | EComputeNTBsFlags::Tangents | EComputeNTBsFlags::UseMikkTSpace;
FStaticMeshOperations::ComputeTangentsAndNormals(MeshDescription, ComputeFlags);
}
else if (bHasInvalidTangents)
{
FStaticMeshOperations::ComputeMikktTangents(MeshDescription, true);
}
}
#if WITH_EDITOR
// Force the generation of UVs data with full precision in the vertex buffer
StaticMesh->GetSourceModel(0).BuildSettings.bUseFullPrecisionUVs = true;
#endif
TArray<const FMeshDescription*> MeshDescriptionPointers;
for (FMeshDescription& MeshDescription : MeshDescriptions)
{
MeshDescriptionPointers.Add(&MeshDescription);
}
{
UStaticMesh::FBuildMeshDescriptionsParams Params;
Params.bUseHashAsGuid = true;
// Do not mark the package dirty since MarkPackageDirty is not thread safe
Params.bMarkPackageDirty = false;
Params.bBuildSimpleCollision = false;
// Do not commit since we only need the render data and commit is slow
Params.bCommitMeshDescription = false;
Params.bFastBuild = true;
#if !WITH_EDITOR
// Force build process to keep index buffer for complex collision when in game
Params.bAllowCpuAccess = ImportOptions.BuildCollisions != ECollisionEnabled::NoCollision && (ImportOptions.CollisionType == ECollisionTraceFlag::CTF_UseComplexAsSimple || ImportOptions.CollisionType == ECollisionTraceFlag::CTF_UseSimpleAndComplex);
#endif
StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPointers, Params);
}
if (ImportOptions.BuildCollisions != ECollisionEnabled::NoCollision)
{
DatasmithRuntime::BuildCollision(StaticMesh->GetBodySetup(), ImportOptions.CollisionType, StaticMesh->GetRenderData()->LODResources[0]);
}
// Free up memory
MeshDescriptions.Empty();
#if WITH_EDITORONLY_DATA
StaticMesh->ClearMeshDescriptions();
#endif
check(StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->IsInitialized());
MeshData.ClearState(EAssetState::Building);
FAssetRegistry::SetObjectCompletion(StaticMesh, true);
return true;
}
EActionResult::Type FSceneImporter::CreateMeshComponent(FSceneGraphId ActorId, UStaticMesh* StaticMesh)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CreateMeshComponent);
if (StaticMesh == nullptr)
{
ActionCounter.Increment();
return EActionResult::Succeeded;
}
FActorData& ActorData = ActorDataList[ActorId];
// Component has been removed, no action needed
if (ActorData.ElementId == INDEX_NONE)
{
return EActionResult::Succeeded;
}
IDatasmithMeshActorElement* MeshActorElement = static_cast<IDatasmithMeshActorElement*>(Elements[ActorId].Get());
UStaticMeshComponent* MeshComponent = ActorData.GetObject<UStaticMeshComponent>();
if (MeshComponent == nullptr)
{
if (ImportOptions.BuildHierarchy != EBuildHierarchyMethod::None && !MeshActorElement->IsAComponent())
{
AStaticMeshActor* Actor = Cast<AStaticMeshActor>(RootComponent->GetOwner()->GetWorld()->SpawnActor(AStaticMeshActor::StaticClass(), nullptr, nullptr));
check(Actor != nullptr);
RenameObject(Actor, MeshActorElement->GetName());
#if WITH_EDITOR
Actor->SetActorLabel( MeshActorElement->GetLabel() );
#endif
Actor->Tags.Empty(MeshActorElement->GetTagsCount());
for (int32 Index = 0; Index < MeshActorElement->GetTagsCount(); ++Index)
{
Actor->Tags.Add(MeshActorElement->GetTag(Index));
}
MeshComponent = Actor->GetStaticMeshComponent();
}
else
{
FName ComponentName = NAME_None;
if (ImportOptions.BuildHierarchy != EBuildHierarchyMethod::None)
{
ComponentName = MakeUniqueObjectName(RootComponent->GetOwner(), UStaticMeshComponent::StaticClass(), MeshActorElement->GetLabel());
}
MeshComponent = NewObject< UStaticMeshComponent >(RootComponent->GetOwner(), ComponentName);
}
MeshComponent->ComponentTags.Add(RuntimeTag);
MeshComponent->bAlwaysCreatePhysicsState = ImportOptions.BuildCollisions != ECollisionEnabled::NoCollision;
MeshComponent->BodyInstance.SetCollisionEnabled(ImportOptions.BuildCollisions);
if (MeshComponent->bAlwaysCreatePhysicsState)
{
MeshComponent->BodyInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
}
else
{
MeshComponent->BodyInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
ActorData.Object = TWeakObjectPtr<UObject>(MeshComponent);
}
FinalizeComponent(ActorData);
MeshComponent->SetStaticMesh(StaticMesh);
if (StaticMesh != nullptr)
{
#ifdef ASSET_DEBUG
StaticMesh->ClearFlags(RF_Public);
#endif
// Allocate memory or not for override materials
TArray< TObjectPtr<UMaterialInterface> >& OverrideMaterials = MeshComponent->OverrideMaterials;
// There are override materials, make sure the slots are allocated
if (MeshActorElement->GetMaterialOverridesCount() > 0)
{
// Update override materials if mesh element has less materials assigned than static mesh
if (StaticMesh->GetStaticMaterials().Num() > OverrideMaterials.Num())
{
FActionTaskFunction AssignMaterialFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->AssignMaterial(Referencer, Cast<UMaterialInstanceDynamic>(Object));
};
TArray< FStaticMaterial >& StaticMaterials = StaticMesh->GetStaticMaterials();
if (MeshActorElement->GetMaterialOverride(0)->GetId() < 0)
{
TSharedPtr<const IDatasmithMaterialIDElement> MaterialIDElement = MeshActorElement->GetMaterialOverride(0);
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialIDElement->GetName()))
{
DependencyList.Add(MaterialIDElement->GetNodeId(), { EDataType::Actor, ActorData.ElementId, 0xffff });
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, { EDataType::Actor, ActorData.ElementId, 0xffff } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
}
else
{
const int32 StaticMaterialCount = StaticMaterials.Num();
TMap<FString, int32> SlotMapping;
SlotMapping.Reserve(StaticMaterialCount);
for (int32 Index = 0; Index < StaticMaterialCount; ++Index)
{
const FStaticMaterial& StaticMaterial = StaticMaterials[Index];
if (StaticMaterial.MaterialSlotName != NAME_None)
{
SlotMapping.Add(StaticMaterial.MaterialSlotName.ToString(), Index);
}
}
for (int32 Index = 0; Index < MeshActorElement->GetMaterialOverridesCount(); ++Index)
{
TSharedPtr<const IDatasmithMaterialIDElement> MaterialIDElement = MeshActorElement->GetMaterialOverride(Index);
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialIDElement->GetName()))
{
const FString MaterialSlotName = FString::Printf(TEXT("%d"), MaterialIDElement->GetId());
int32 MaterialIndex = INDEX_NONE;
if (SlotMapping.Contains(MaterialSlotName))
{
MaterialIndex = SlotMapping[MaterialSlotName];
}
else if (MaterialIDElement->GetId() < StaticMaterialCount)
{
MaterialIndex = MaterialIDElement->GetId();
}
if (MaterialIndex != INDEX_NONE)
{
DependencyList.Add(MaterialIDElement->GetNodeId(), { EDataType::Actor, ActorData.ElementId, (uint16)Index });
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, { EDataType::Actor, ActorData.ElementId, (uint16)MaterialIndex } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
}
}
}
}
OverrideMaterials.SetNum(StaticMesh->GetStaticMaterials().Num());
for (int32 Index = 0; Index < OverrideMaterials.Num(); ++Index)
{
OverrideMaterials[Index] = nullptr;
}
}
// No override material, discard the array if necessary
else if (OverrideMaterials.Num() > 0)
{
OverrideMaterials.Empty();
}
}
MeshComponent->MarkRenderStateDirty();
ActorData.AddState(EAssetState::Completed);
// Update counters
ActionCounter.Increment();
return EActionResult::Succeeded;
}
EActionResult::Type FSceneImporter::AssignMaterial(const FReferencer& Referencer, UMaterialInstanceDynamic* Material)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::AssignMaterial);
if (Material == nullptr)
{
// #ue_dsruntime: Log message material not assigned
ActionCounter.Increment();
return EActionResult::Failed;
}
if (Referencer.Type == (uint8)EDataType::Mesh)
{
FAssetData& MeshData = AssetDataList[Referencer.GetId()];
if (!MeshData.HasState(EAssetState::Completed))
{
return EActionResult::Retry;
}
// Static mesh can be null if creation failed
if (UStaticMesh* StaticMesh = MeshData.GetObject<UStaticMesh>())
{
TArray< FStaticMaterial >& StaticMaterials = StaticMesh->GetStaticMaterials();
if (Referencer.Slot == 0xffff)
{
for (FStaticMaterial& StaticMaterial : StaticMaterials)
{
StaticMaterial.MaterialInterface = Material;
}
}
else if (!StaticMaterials.IsValidIndex(Referencer.Slot))
{
ensure(false);
ActionCounter.Increment();
return EActionResult::Failed;
}
else
{
StaticMaterials[Referencer.Slot].MaterialInterface = Material;
}
#ifdef ASSET_DEBUG
Material->ClearFlags(RF_Public);
#endif
// Mark dependent mesh components' render state as dirty
for (FReferencer& ActorReferencer : MeshData.Referencers)
{
FActorData& ActorData = ActorDataList[ActorReferencer.GetId()];
if (UActorComponent* ActorComponent = ActorData.GetObject<UActorComponent>())
{
ActorComponent->MarkRenderStateDirty();
}
}
}
}
else if (Referencer.Type == (uint8)EDataType::Actor)
{
FActorData& ActorData = ActorDataList[Referencer.GetId()];
if (!ActorData.HasState(EAssetState::Completed))
{
return EActionResult::Retry;
}
// Static mesh can be null if creation failed
if (UStaticMeshComponent* MeshComponent = ActorData.GetObject<UStaticMeshComponent>())
{
if (Referencer.Slot == 0xffff)
{
for (int32 Index = 0; Index < MeshComponent->GetNumMaterials(); ++Index)
{
MeshComponent->SetMaterial(Index, Material);
}
}
else if ((int32)Referencer.Slot >= MeshComponent->GetNumMaterials())
{
ensure(false);
ActionCounter.Increment();
return EActionResult::Failed;
}
else
{
MeshComponent->SetMaterial(Referencer.Slot, Material);
}
// Force rebuilding of render data for mesh component
MeshComponent->MarkRenderStateDirty();
#ifdef ASSET_DEBUG
Material->ClearFlags(RF_Public);
#endif
}
else
{
ensure(false);
ActionCounter.Increment();
return EActionResult::Failed;
}
}
else
{
ensure(false);
ActionCounter.Increment();
return EActionResult::Failed;
}
ActionCounter.Increment();
return EActionResult::Succeeded;
}
void FSceneImporter::UpdateStaticMeshMaterials(FAssetData& MeshData)
{
TSharedPtr< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[MeshData.ElementId]);
UStaticMesh* StaticMesh = MeshData.GetObject<UStaticMesh>();
if (!MeshElement.IsValid() || StaticMesh == nullptr)
{
return;
}
TArray<FStaticMaterial>& StaticMaterials = StaticMesh->GetStaticMaterials();
const int32 MaterialSlotCount = StaticMaterials.Num();
TMap<FString, int32> SlotMapping;
SlotMapping.Reserve(MaterialSlotCount);
for(int32 Index = 0; Index < MaterialSlotCount; ++Index)
{
const FStaticMaterial& StaticMaterial = StaticMaterials[Index];
SlotMapping.Add(StaticMaterial.MaterialSlotName.ToString(), Index);
}
FActionTaskFunction AssignMaterialFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->AssignMaterial(Referencer, Cast<UMaterialInstanceDynamic>(Object));
};
TFunction<bool(const IDatasmithMaterialIDElement*,int32)> UpdateMaterialSlot;
UpdateMaterialSlot = [&](const IDatasmithMaterialIDElement* MaterialIDElement, int32 SlotIndex) -> bool
{
const FString MaterialPathName(MaterialIDElement->GetName());
UMaterialInterface* PreviousMaterialInterface = StaticMaterials[SlotIndex].MaterialInterface;
if (!MaterialPathName.StartsWith(TEXT("/")))
{
StaticMaterials[SlotIndex].MaterialInterface = nullptr;
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialPathName))
{
DependencyList.Add(MaterialIDElement->GetNodeId(), { EDataType::Mesh, MeshData.ElementId, (uint16)SlotIndex });
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, { EDataType::Mesh, MeshData.ElementId, (uint16)SlotIndex } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
else
{
DependencyList.Remove(MaterialIDElement->GetNodeId());
}
}
else
{
StaticMaterials[SlotIndex].MaterialInterface = Cast<UMaterialInterface>(FSoftObjectPath(MaterialPathName).TryLoad());
}
return PreviousMaterialInterface != StaticMaterials[SlotIndex].MaterialInterface;
};
bool bUpdateReferencers = false;
// Check to see if there is material to apply on all slots
int32 OverrideIndex = INDEX_NONE;
for (int32 Index = 0; Index < MeshElement->GetMaterialSlotCount(); Index++)
{
if (MeshElement->GetMaterialSlotAt(Index).IsValid() && MeshElement->GetMaterialSlotAt(Index)->GetId() < 0)
{
OverrideIndex = Index;
break;
}
}
if (OverrideIndex != INDEX_NONE)
{
const IDatasmithMaterialIDElement* MaterialIDElement = MeshElement->GetMaterialSlotAt(OverrideIndex).Get();
for (int32 Index = 0; Index < MaterialSlotCount; Index++)
{
bUpdateReferencers |= UpdateMaterialSlot(MaterialIDElement, Index);
}
}
// Apply material on specific slots
for (int32 Index = 0; Index < MeshElement->GetMaterialSlotCount(); Index++)
{
if (const IDatasmithMaterialIDElement* MaterialIDElement = MeshElement->GetMaterialSlotAt(Index).Get())
{
if (MaterialIDElement->GetId() >= 0)
{
const FString MaterialSlotName = FString::Printf(TEXT("%d"), MaterialIDElement->GetId());
int32 SlotIndex = INDEX_NONE;
if (SlotMapping.Contains(MaterialSlotName))
{
SlotIndex = SlotMapping[MaterialSlotName];
}
else if (MaterialIDElement->GetId() < MaterialSlotCount)
{
SlotIndex = MaterialIDElement->GetId();
}
else
{
UE_LOG(LogDatasmithRuntime, Warning, TEXT("CreateStaticMesh: Cannot assign material %s to any slot"), MaterialIDElement->GetName());
continue;
}
bUpdateReferencers |= UpdateMaterialSlot(MaterialIDElement, SlotIndex);
}
}
}
if (bUpdateReferencers)
{
// Mark dependent mesh components' render state as dirty
for (FReferencer& ActorReferencer : MeshData.Referencers)
{
const FActorData& ActorData = ActorDataList[ActorReferencer.GetId()];
if (ActorData.HasState(EAssetState::Completed))
{
if (UActorComponent* ActorComponent = ActorData.GetObject<UActorComponent>())
{
ActorComponent->MarkRenderStateDirty();
}
}
}
}
}
void FSceneImporter::FillStaticMeshMaterials(FAssetData& MeshData, TArray<FMeshDescription>& MeshDescriptions)
{
TSharedRef< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[MeshData.ElementId]).ToSharedRef();
UStaticMesh* StaticMesh = MeshData.GetObject<UStaticMesh>();
if (StaticMesh == nullptr)
{
return;
}
TMap<FString, int32> SlotMapping;
// Update static mesh's static material array for LOD 0
TArray<FStaticMaterial>& StaticMaterials = StaticMesh->GetStaticMaterials();
FMeshDescription& MeshDescription = MeshDescriptions[0];
FStaticMeshAttributes Attributes(MeshDescription);
TPolygonGroupAttributesConstRef<FName> MaterialSlotNameAttribute = Attributes.GetPolygonGroupMaterialSlotNames();
const int32 MaterialSlotCount = MeshDescription.PolygonGroups().Num();
StaticMaterials.SetNum(MaterialSlotCount);
{
int32 Index = 0;
for (FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
{
FStaticMaterial& StaticMaterial = StaticMaterials[Index];
StaticMaterial.MaterialSlotName = MaterialSlotNameAttribute[PolygonGroupID];
StaticMaterial.MaterialInterface = nullptr;
// Done to remove an assert from an 'ensure' in UStaticMesh::GetUVChannelData
StaticMaterial.UVChannelData = FMeshUVChannelInfo(1.f);
++Index;
}
}
// Add task to update material interfaces on static materials if applicable
if (MeshElement->GetMaterialSlotCount() > 0)
{
FActionTaskFunction UpdateMaterialsFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
this->UpdateStaticMeshMaterials(AssetDataList[Referencer.GetId()]);
return EActionResult::Succeeded;
};
AddToQueue(EQueueTask::NonAsyncQueue, { UpdateMaterialsFunc, DirectLink::InvalidId, { EDataType::Mesh, MeshData.ElementId, 0 } });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
// Add slots defined in subsequent LODs but not present in LOD 0
TSet<FName> LODSlotNames;
for (int32 LODIndex = 1; LODIndex < MeshDescriptions.Num(); ++LODIndex)
{
FMeshDescription& LODMeshDescription = MeshDescriptions[LODIndex];
FStaticMeshAttributes LODAttributes(LODMeshDescription);
TPolygonGroupAttributesConstRef<FName> LODMaterialSlotNameAttribute = LODAttributes.GetPolygonGroupMaterialSlotNames();
for (FPolygonGroupID PolygonGroupID : LODMeshDescription.PolygonGroups().GetElementIDs())
{
const FName LODSlotName = LODMaterialSlotNameAttribute[PolygonGroupID];
if (!SlotMapping.Contains(LODSlotName.ToString()))
{
LODSlotNames.Add(LODSlotName);
}
}
}
if (LODSlotNames.Num() > 0)
{
StaticMaterials.SetNum(MaterialSlotCount + LODSlotNames.Num());
int32 Index = MaterialSlotCount;
for (FName& SlotName : LODSlotNames)
{
FStaticMaterial& StaticMaterial = StaticMaterials[Index];
StaticMaterial.MaterialSlotName = SlotName;
StaticMaterial.MaterialInterface = nullptr;
// Done to remove an assert from an 'ensure' in UStaticMesh::GetUVChannelData
StaticMaterial.UVChannelData = FMeshUVChannelInfo(1.f);
++Index;
}
}
}
}