// Copyright Epic Games, Inc. All Rights Reserved. #include "DatasmithRuntimeUtils.h" #include "DatasmithRuntimeAuxiliaryData.h" #include "DatasmithPayload.h" #include "IDatasmithSceneElements.h" #include "StaticMeshResources.h" #include "Utility/DatasmithMeshHelper.h" #include "Algo/AnyOf.h" #include "Async/Async.h" #include "Engine/Polys.h" #include "MeshUtilitiesCommon.h" #include "OverlappingCorners.h" #include "PhysicsEngine/BodySetup.h" #include "PhysicsEngine/PhysicsSettings.h" #include "StaticMeshAttributes.h" #include "StaticMeshOperations.h" namespace DatasmithRuntime { extern const FString TexturePrefix; extern const FString MaterialPrefix; extern const FString MeshPrefix; bool /*FDatasmithStaticMeshImporter::*/ShouldRecomputeNormals(const FMeshDescription& MeshDescription, int32 BuildRequirements) { const TVertexInstanceAttributesConstRef Normals = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); check(Normals.IsValid()); return Algo::AnyOf(MeshDescription.VertexInstances().GetElementIDs(), [&](const FVertexInstanceID& InstanceID) { return !Normals[InstanceID].IsNormalized(); }); } bool /*FDatasmithStaticMeshImporter::*/ShouldRecomputeTangents(const FMeshDescription& MeshDescription, int32 BuildRequirements) { const TVertexInstanceAttributesConstRef Tangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); check(Tangents.IsValid()); return Algo::AnyOf(MeshDescription.VertexInstances().GetElementIDs(), [&](const FVertexInstanceID& InstanceID) { return !Tangents[InstanceID].IsNormalized(); }); } int32 GetNextOpenUVChannel(FMeshDescription& MeshDescription) { FStaticMeshConstAttributes Attributes(MeshDescription); int32 NumberOfUVs = Attributes.GetVertexInstanceUVs().GetNumChannels(); int32 FirstEmptyUVs = 0; for (; FirstEmptyUVs < NumberOfUVs; ++FirstEmptyUVs) { const TVertexInstanceAttributesConstRef UVChannels = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); const FVector2f DefValue = UVChannels.GetDefaultValue(); bool bHasNonDefaultValue = false; for (FVertexInstanceID InstanceID : MeshDescription.VertexInstances().GetElementIDs()) { if (UVChannels.Get(InstanceID, FirstEmptyUVs) != DefValue) { bHasNonDefaultValue = true; break; } } if (!bHasNonDefaultValue) { //We found an "empty" channel. break; } } return FirstEmptyUVs < MAX_MESH_TEXTURE_COORDS_MD ? FirstEmptyUVs : -1; } float Get2DSurface(const FVector4& Dimensions) { if (Dimensions[0] >= Dimensions[1] && Dimensions[2] >= Dimensions[1]) { return Dimensions[0] * Dimensions[2]; } if (Dimensions[0] >= Dimensions[2] && Dimensions[1] >= Dimensions[2]) { return Dimensions[0] * Dimensions[1]; } return Dimensions[2] * Dimensions[1]; } float CalcBlendWeight(const FVector4& Dimensions, float MaxArea, float Max2DSurface) { const float Current2DSurface = Get2DSurface(Dimensions); const float Weight = FMath::Sqrt((Dimensions[3] / MaxArea)) + FMath::Sqrt(Current2DSurface / Max2DSurface); return Weight; } void CalculateMeshesLightmapWeights(const TArray< FSceneGraphId >& MeshElementArray, const TMap< FSceneGraphId, TSharedPtr< IDatasmithElement > >& Elements, TMap< FSceneGraphId, float >& LightmapWeights) { TRACE_CPUPROFILER_EVENT_SCOPE(DatasmithRuntime::CalculateMeshesLightmapWeights); LightmapWeights.Reserve(MeshElementArray.Num()); float MaxArea = 0.0f; float Max2DSurface = 0.0f; // Compute the max values based on all meshes in the Datasmith Scene for (FSceneGraphId MeshElementId : MeshElementArray) { TSharedPtr< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[MeshElementId]); MaxArea = FMath::Max(MaxArea, MeshElement->GetArea()); const FVector4 Dimensions(MeshElement->GetWidth(), MeshElement->GetDepth(), MeshElement->GetHeight(), MeshElement->GetArea()); Max2DSurface = FMath::Max(Max2DSurface, Get2DSurface(Dimensions)); } float MaxWeight = 0.0f; for (FSceneGraphId MeshElementId : MeshElementArray) { TSharedPtr< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[MeshElementId]); const FVector4 Dimensions(MeshElement->GetWidth(), MeshElement->GetDepth(), MeshElement->GetHeight(), MeshElement->GetArea()); const float MeshWeight = CalcBlendWeight(Dimensions, MaxArea, Max2DSurface); MaxWeight = FMath::Max(MaxWeight, MeshWeight); LightmapWeights.Add(MeshElementId, MeshWeight); } for (FSceneGraphId MeshElementId : MeshElementArray) { LightmapWeights[MeshElementId] /= MaxWeight; } } int32 GenerateLightmapUVResolution(FMeshDescription& Mesh, int32 SrcLightmapIndex, int32 MinLightmapResolution) { TRACE_CPUPROFILER_EVENT_SCOPE(DatasmithRuntime::GenerateLightmapUVResolution); // Determine the absolute minimum lightmap resolution that can be used for packing FOverlappingCorners OverlappingCorners; FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, Mesh, THRESH_POINTS_ARE_SAME); // Packing expects at least one texel per chart. This is the absolute minimum to generate valid UVs. float ChartCount = FStaticMeshOperations::GetUVChartCount(Mesh, SrcLightmapIndex, ELightmapUVVersion::Latest, OverlappingCorners); const int32 AbsoluteMinResolution = 1 << FMath::CeilLogTwo(FMath::Sqrt(ChartCount)); return FMath::Clamp(MinLightmapResolution, AbsoluteMinResolution, 512); } void ProcessCollision(UStaticMesh* StaticMesh, FDatasmithMeshElementPayload& Payload) { // The following code is copied from StaticMeshEdit AddConvexGeomFromVertices (inaccessible outside UnrealEd) if (!StaticMesh) { return; } TArray< FVector3f > VertexPositions; DatasmithMeshHelper::ExtractVertexPositions(Payload.CollisionMesh, VertexPositions); if (VertexPositions.Num() == 0) { VertexPositions = MoveTemp( Payload.CollisionPointCloud ); } if (VertexPositions.Num() > 0) { #if WITH_EDITORONLY_DATA StaticMesh->bCustomizedCollision = true; #endif if (!ensure(StaticMesh->GetBodySetup())) { return; } // Convex elements must be removed first since the re-import process uses the same flow FKAggregateGeom& AggGeom = StaticMesh->GetBodySetup()->AggGeom; AggGeom.ConvexElems.Reset(); FKConvexElem& ConvexElem = AggGeom.ConvexElems.AddDefaulted_GetRef(); ConvexElem.VertexData.Reserve(VertexPositions.Num()); for (const FVector3f& Position : VertexPositions) { ConvexElem.VertexData.Add(FVector(Position)); } ConvexElem.UpdateElemBox(); } } TMap> FAssetRegistry::RegistrationMap; TMap*> FAssetRegistry::SceneMappings; union FRegistryKey { uint32 Pair[2]; uint64 Value; FRegistryKey(uint64 InValue) { Value = InValue; } FRegistryKey(uint32 SceneKey, FSceneGraphId AssetId) { Pair[0] = SceneKey; Pair[1] = AssetId; } }; void FAssetRegistry::RegisterMapping(uint32 SceneKey, TMap* AssetsMapping) { SceneMappings.Add(SceneKey, AssetsMapping); } void FAssetRegistry::UnregisterMapping(uint32 SceneKey) { ensure(SceneMappings.Contains(SceneKey)); SceneMappings.Remove(SceneKey); } void FAssetRegistry::RegisterAssetData(UObject* Asset, uint32 SceneKey, FAssetData& AssetData) { check(IsInGameThread()); ensure(SceneMappings.Contains(SceneKey)); if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData(); if(AuxillaryData == nullptr) { AuxillaryData = NewObject(Asset, NAME_None, RF_NoFlags); AssetUserData->AddAssetUserData(AuxillaryData); } if (AuxillaryData->Referencers.Num() == 0 && !RegistrationMap.Contains(AssetData.Hash)) { RegistrationMap.Emplace(AssetData.Hash, Asset); } FRegistryKey RegistryKey(SceneKey, AssetData.ElementId); AuxillaryData->Referencers.Add(RegistryKey.Value); if (AuxillaryData->bIsCompleted) { AssetData.AddState(EAssetState::Completed); } else { AssetData.ClearState(EAssetState::Completed); } } } int32 FAssetRegistry::UnregisterAssetData(UObject* Asset, uint32 SceneKey, FSceneGraphId AssetId) { check(IsInGameThread()); ensure(SceneMappings.Contains(SceneKey)); if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { FRegistryKey RegistryKey(SceneKey, AssetId); if (AuxillaryData->Referencers.Contains(RegistryKey.Value)) { AuxillaryData->Referencers.Remove(RegistryKey.Value); return AuxillaryData->Referencers.Num(); } } } ensure(false); return -1; } void FAssetRegistry::SetObjectCompletion(UObject* Asset, bool bIsCompleted) { if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { AuxillaryData->bIsCompleted.store(bIsCompleted); if (bIsCompleted) { for (uint64 ReferencerKey : AuxillaryData->Referencers) { const FRegistryKey RegistryKey(ReferencerKey); ensure(SceneMappings.Contains(RegistryKey.Pair[0])); TMap& AssetsMapping = *(SceneMappings[RegistryKey.Pair[0]]); ensure(AssetsMapping.Contains(RegistryKey.Pair[1])); AssetsMapping[RegistryKey.Pair[1]].AddState(EAssetState::Completed); } } else { for (uint64 ReferencerKey : AuxillaryData->Referencers) { const FRegistryKey RegistryKey(ReferencerKey); ensure(SceneMappings.Contains(RegistryKey.Pair[0])); TMap& AssetsMapping = *(SceneMappings[RegistryKey.Pair[0]]); ensure(AssetsMapping.Contains(RegistryKey.Pair[1])); AssetsMapping[RegistryKey.Pair[1]].ClearState(EAssetState::Completed); } } return; } } ensure(false); } bool FAssetRegistry::IsObjectCompleted(UObject* Asset) { bool bIsCompleted = false; if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { for (uint64 ReferencerKey : AuxillaryData->Referencers) { const FRegistryKey RegistryKey(ReferencerKey); ensure(SceneMappings.Contains(RegistryKey.Pair[0])); TMap& AssetsMapping = *(SceneMappings[RegistryKey.Pair[0]]); ensure(AssetsMapping.Contains(RegistryKey.Pair[1])); bIsCompleted |= AssetsMapping[RegistryKey.Pair[1]].HasState(EAssetState::Completed); } return bIsCompleted; } } return bIsCompleted; } int32 FAssetRegistry::GetAssetReferenceCount(UObject * Asset) { if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { return AuxillaryData->Referencers.Num(); } } return -1; } void FAssetRegistry::UnregisteredAssetsData(UObject* Asset, uint32 SceneKey, TFunction UpdateFunc) { if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Asset)) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { TArray ReferencersToDelete; for (uint64 ReferencerKey : AuxillaryData->Referencers) { const FRegistryKey RegistryKey(ReferencerKey); // If SceneKey is specified, only apply function to assets of that scene if (SceneKey && SceneKey != RegistryKey.Pair[0]) { continue; } ReferencersToDelete.Add(ReferencerKey); } for (uint64 ReferencerKey : ReferencersToDelete) { AuxillaryData->Referencers.Remove(ReferencerKey); } for (uint64 ReferencerKey : ReferencersToDelete) { const FRegistryKey RegistryKey(ReferencerKey); ensure(SceneMappings.Contains(RegistryKey.Pair[0])); TMap& AssetsMapping = *(SceneMappings[RegistryKey.Pair[0]]); ensure(AssetsMapping.Contains(RegistryKey.Pair[1])); UpdateFunc(AssetsMapping[RegistryKey.Pair[1]]); } return; } } ensure(false); } UObject* FAssetRegistry::FindObjectFromHash(uint32 ElementHash) { TStrongObjectPtr* AssetPtr = RegistrationMap.Find(ElementHash); return AssetPtr ? (*AssetPtr).Get() : nullptr; } bool FAssetRegistry::CleanUp() { TArray EntriesToDelete; EntriesToDelete.Reserve(RegistrationMap.Num()); for (TPair>& Entry : RegistrationMap) { if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Entry.Value.Get())) { if (UDatasmithRuntimeAuxiliaryData* AuxillaryData = AssetUserData->GetAssetUserData()) { if (AuxillaryData->Referencers.Num() == 0) { Entry.Value->ClearFlags(RF_Public); Entry.Value->SetFlags(RF_Transient); Entry.Value->Rename(nullptr, nullptr, REN_NonTransactional | REN_DontCreateRedirectors); Entry.Value->MarkAsGarbage(); Entry.Value.Reset(); EntriesToDelete.Add(Entry.Key); } } } } for (uint32 ElementHash : EntriesToDelete) { RegistrationMap.Remove(ElementHash); } return EntriesToDelete.Num() > 0; } // Code below has been borrowed from GenerateKDopAsSimpleCollision in GeomFitUtils.cpp // to generate k-DOP (k-Discrete Oriented Polytopes) with 26 polytopes #define RCP_SQRT2 (0.70710678118654752440084436210485f) #define RCP_SQRT3 (0.57735026918962576450914878050196f) const FVector KDopDir26[26] = { FVector( 1.f, 0.f, 0.f), FVector(-1.f, 0.f, 0.f), FVector( 0.f, 1.f, 0.f), FVector( 0.f,-1.f, 0.f), FVector( 0.f, 0.f, 1.f), FVector( 0.f, 0.f,-1.f), FVector( 0.f, RCP_SQRT2, RCP_SQRT2), FVector( 0.f,-RCP_SQRT2, -RCP_SQRT2), FVector( 0.f, RCP_SQRT2, -RCP_SQRT2), FVector( 0.f,-RCP_SQRT2, RCP_SQRT2), FVector( RCP_SQRT2, 0.f, RCP_SQRT2), FVector(-RCP_SQRT2, 0.f, -RCP_SQRT2), FVector( RCP_SQRT2, 0.f, -RCP_SQRT2), FVector(-RCP_SQRT2, 0.f, RCP_SQRT2), FVector( RCP_SQRT2, RCP_SQRT2, 0.f), FVector(-RCP_SQRT2, -RCP_SQRT2, 0.f), FVector( RCP_SQRT2, -RCP_SQRT2, 0.f), FVector(-RCP_SQRT2, RCP_SQRT2, 0.f), FVector( RCP_SQRT3, RCP_SQRT3, RCP_SQRT3), FVector( RCP_SQRT3, RCP_SQRT3, -RCP_SQRT3), FVector( RCP_SQRT3, -RCP_SQRT3, RCP_SQRT3), FVector( RCP_SQRT3, -RCP_SQRT3, -RCP_SQRT3), FVector(-RCP_SQRT3, RCP_SQRT3, RCP_SQRT3), FVector(-RCP_SQRT3, RCP_SQRT3, -RCP_SQRT3), FVector(-RCP_SQRT3, -RCP_SQRT3, RCP_SQRT3), FVector(-RCP_SQRT3, -RCP_SQRT3, -RCP_SQRT3), }; constexpr float HalfWorldMax = HALF_WORLD_MAX; void GenerateKDopAsSimpleCollision(UBodySetup* BodySetup, const FStaticMeshLODResources& Resources, const FVector* Directions, int32 DirectionCount) { TArray MaxDistances; MaxDistances.Reserve(DirectionCount); for (int32 Index = 0; Index < DirectionCount; ++Index) { MaxDistances.Add(-MAX_FLT); } // For each vertex, project along each kdop direction, to find the max in that direction. const FPositionVertexBuffer& PositionVertexBuffer = Resources.VertexBuffers.PositionVertexBuffer; for(int32 Index = 0; Index < Resources.GetNumVertices(); ++Index) { for(int32 DirIndex = 0; DirIndex < DirectionCount; ++DirIndex) { const float Dist = (FVector)PositionVertexBuffer.VertexPosition(Index) | Directions[DirIndex]; MaxDistances[DirIndex] = FMath::Max(Dist, MaxDistances[DirIndex]); } } // Inflate MaxDistances to ensure it is no degenerate const float MinSize = 0.1f; for (int32 Index = 0; Index < DirectionCount; ++Index) { MaxDistances[Index] += MinSize; } // Now we have the Planes of the kdop, we work out the face polygons. TArray Planes; Planes.Reserve(DirectionCount); for (int32 Index = 0; Index < DirectionCount; ++Index) { Planes.Add( FPlane(Directions[Index], MaxDistances[Index]) ); } TArray Element; Element.Reserve(DirectionCount); for (int32 Index = 0; Index < DirectionCount; ++Index) { FPoly& Polygon = Element.AddZeroed_GetRef(); FVector3f Base, AxisX, AxisY; Polygon.Init(); Polygon.Normal = (FVector3f)Planes[Index]; Polygon.Normal.FindBestAxisVectors(AxisX, AxisY); Base = FVector3f(Planes[Index] * Planes[Index].W); Polygon.Vertices.Reserve(4); new(Polygon.Vertices) FVector3f(Base + AxisX * HalfWorldMax + AxisY * HalfWorldMax); new(Polygon.Vertices) FVector3f(Base + AxisX * HalfWorldMax - AxisY * HalfWorldMax); new(Polygon.Vertices) FVector3f(Base - AxisX * HalfWorldMax - AxisY * HalfWorldMax); new(Polygon.Vertices) FVector3f(Base - AxisX * HalfWorldMax + AxisY * HalfWorldMax); for (int32 Jndex = 0; Jndex < DirectionCount; ++Jndex) { if(Index != Jndex) { if(!Polygon.Split(-FVector3f(Planes[Jndex]), FVector3f(Planes[Jndex] * Planes[Jndex].W))) { Polygon.Vertices.Empty(); break; } } } if(Polygon.Vertices.Num() < 3) { // If poly resulted in no verts, remove from array Element.Pop(EAllowShrinking::No); } else { // Other stuff... Polygon.iLink = Index; Polygon.CalcNormal(1); } } if(Element.Num() < 4) { return; } FKConvexElem& ConvexElem = BodySetup->AggGeom.ConvexElems.AddDefaulted_GetRef(); for (FPoly& Poly : Element) { for (FVector3f& Position : Poly.Vertices) { ConvexElem.VertexData.Add((FVector)Position); } } ConvexElem.UpdateElemBox(); } // End of Code borrowed from GenerateKDopAsSimpleCollision in GeomFitUtils.cpp void BuildCollision(UBodySetup* BodySetup, ECollisionTraceFlag CollisionFlag, const FStaticMeshLODResources& Resources) { BodySetup->CollisionTraceFlag = CollisionFlag; if (BodySetup->CollisionTraceFlag == ECollisionTraceFlag::CTF_UseDefault) { BodySetup->CollisionTraceFlag = UPhysicsSettings::Get()->DefaultShapeComplexity; } // Use k-DOP 26 as the collision mesh if simple collision is required // #ue_dsruntime - TODO | Choose better shape of collision mesh if (BodySetup->CollisionTraceFlag != ECollisionTraceFlag::CTF_UseComplexAsSimple && BodySetup->AggGeom.ConvexElems.Num() == 0) { GenerateKDopAsSimpleCollision(BodySetup, Resources, KDopDir26, 26); } // Creation of collision meshes can only happen on game thread AsyncTask(ENamedThreads::GameThread, [BodySetup] { #ifdef LIVEUPDATE_TIME_LOGGING double LocalStartTime = FPlatformTime::Seconds(); Timer __Timer(LocalStartTime, "BuildCollision"); #endif BodySetup->CreatePhysicsMeshes(); }); } } // End of namespace DatasmithRuntime