// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshDescriptionToDynamicMesh.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/DynamicMeshOverlay.h" #include "DynamicMesh/DynamicVertexAttribute.h" #include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h" #include "DynamicMesh/DynamicBoneAttribute.h" #include "DynamicMesh/MeshTangents.h" #include "DynamicMesh/NonManifoldMappingSupport.h" #include "MeshDescriptionBuilder.h" #include "StaticMeshAttributes.h" #include "SkeletalMeshAttributes.h" #include "Tasks/Task.h" #include "Util/ColorConstants.h" using namespace UE::Geometry; struct FVertexUV { int vid; float x; float y; bool operator==(const FVertexUV & o) const { return vid == o.vid && x == o.x && y == o.y; } }; FORCEINLINE uint32 GetTypeHash(const FVertexUV& Vector) { // ugh copied from FVector clearly should not be using CRC for hash!! return FCrc::MemCrc32(&Vector, sizeof(Vector)); } class FUVWelder { public: TMap UniqueVertexUVs; FDynamicMeshUVOverlay* UVOverlay; FUVWelder() : UVOverlay(nullptr) { } FUVWelder(FDynamicMeshUVOverlay* UVOverlayIn) { check(UVOverlayIn); UVOverlay = UVOverlayIn; } int FindOrAddUnique(const FVector2f& UV, int VertexID) { FVertexUV VertUV = { VertexID, UV.X, UV.Y }; const int32* FoundIndex = UniqueVertexUVs.Find(VertUV); if (FoundIndex != nullptr) { return *FoundIndex; } int32 NewIndex = UVOverlay->AppendElement(FVector2f(UV)); UniqueVertexUVs.Add(VertUV, NewIndex); return NewIndex; } }; struct FVertexNormal { int vid; float x; float y; float z; bool operator==(const FVertexNormal & o) const { return vid == o.vid && x == o.x && y == o.y && z == o.z; } }; FORCEINLINE uint32 GetTypeHash(const FVertexNormal& Vector) { // ugh copied from FVector clearly should not be using CRC for hash!! return FCrc::MemCrc32(&Vector, sizeof(Vector)); } class FNormalWelder { public: TMap UniqueVertexNormals; FDynamicMeshNormalOverlay* NormalOverlay; FNormalWelder() : NormalOverlay(nullptr) { } FNormalWelder(FDynamicMeshNormalOverlay* NormalOverlayIn) { check(NormalOverlayIn); NormalOverlay = NormalOverlayIn; } int FindOrAddUnique(const FVector3f & Normal, int VertexID) { FVertexNormal VertNormal = { VertexID, Normal.X, Normal.Y, Normal.Z }; const int32* FoundIndex = UniqueVertexNormals.Find(VertNormal); if (FoundIndex != nullptr) { return *FoundIndex; } int32 NewIndex = NormalOverlay->AppendElement( &Normal.X ); UniqueVertexNormals.Add(VertNormal, NewIndex); return NewIndex; } }; struct FVertexColor { int vid; FVector4f Color; bool operator==(const FVertexColor& o) const { return vid == o.vid && Color == o.Color; } }; FORCEINLINE uint32 GetTypeHash(const FVertexColor& Vector) { return FCrc::MemCrc32(&Vector, sizeof(Vector)); } class FColorWelder { public: TMap UniqueVertexColors; FDynamicMeshColorOverlay* ColorOverlay; FColorWelder() : ColorOverlay(nullptr) { } FColorWelder(FDynamicMeshColorOverlay* ColorOverlayIn) { check(ColorOverlayIn); ColorOverlay = ColorOverlayIn; } int FindOrAddUnique(const FVector4f& Color, int VertexID) { FVertexColor VertColor = { VertexID, Color }; const int32* FoundIndex = UniqueVertexColors.Find(VertColor); if (FoundIndex != nullptr) { return *FoundIndex; } int32 NewIndex = ColorOverlay->AppendElement(&Color.X); UniqueVertexColors.Add(VertColor, NewIndex); return NewIndex; } }; struct FSkinWeightsAttribCopyInfo { FSkinWeightsVertexAttributesConstRef MeshDescAttribRef; FDynamicMeshVertexSkinWeightsAttribute* DynaMeshAttribRef = nullptr; }; void FMeshDescriptionToDynamicMesh::Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut, bool bCopyTangents ) { TRACE_CPUPROFILER_EVENT_SCOPE(MeshDescriptionToDynamicMesh); TriIDMap.Reset(); VertIDMap.Reset(); MeshOut.Clear(); if (!ensure(MeshIn != nullptr)) { return; // nothing to convert } // allocate the VertIDMap. Unfortunately the array will need to grow more if MeshIn has non-manifold edges that need to be split VertIDMap.SetNumUninitialized(MeshIn->Vertices().Num()); // look up vertex positions TVertexAttributesConstRef VertexPositions = MeshIn->GetVertexPositions(); // copy vertex positions. Later we may have to append duplicate vertices to resolve non-manifold structures. for (const FVertexID VertexID : MeshIn->Vertices().GetElementIDs()) { const FVector3f Position = VertexPositions.Get(VertexID); int NewVertIdx = MeshOut.AppendVertex( (FVector3d)Position); VertIDMap[NewVertIdx] = VertexID; } // look up vertex-instance UVs and normals // @todo: does the MeshDescription always have UVs and Normals? FSkeletalMeshConstAttributes Attributes(*MeshIn); TVertexInstanceAttributesConstRef InstanceUVs = Attributes.GetVertexInstanceUVs(); TVertexInstanceAttributesConstRef InstanceNormals = Attributes.GetVertexInstanceNormals(); TVertexInstanceAttributesConstRef InstanceTangents = Attributes.GetVertexInstanceTangents(); TVertexInstanceAttributesConstRef InstanceBiTangentSign = Attributes.GetVertexInstanceBinormalSigns(); TVertexInstanceAttributesConstRef InstanceColors = Attributes.GetVertexInstanceColors(); TPolygonAttributesConstRef PolyGroups = MeshIn->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); if (bEnableOutputGroups) { MeshOut.EnableTriangleGroups(0); } bCopyTangents = bCopyTangents && InstanceTangents.IsValid() && InstanceBiTangentSign.IsValid(); // base triangle groups will track polygons int NumVertices = MeshIn->Vertices().Num(); int NumPolygons = MeshIn->Polygons().Num(); int NumVtxInstances = MeshIn->VertexInstances().Num(); if (bPrintDebugMessages) { UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: MeshDescription verts %d polys %d instances %d"), NumVertices, NumPolygons, NumVtxInstances); } FDateTime Time_AfterVertices = FDateTime::Now(); // Although it is slightly redundant, we will build up this list of MeshDescription data // so that it is easier to index into it below (profile whether extra memory here hurts us?) struct FTriData { FPolygonID PolygonID; int32 PolygonGroupID; FVertexInstanceID TriInstances[3]; }; TArray AddedTriangles; AddedTriangles.SetNum(MeshIn->Triangles().Num()); // allocate the TriIDMap (maps from DynamicMesh triangle ID to MeshDescription FTriangleID) TriIDMap.AddUninitialized(MeshIn->Triangles().Num()); bool bSrcIsManifold = true; // Iterate over triangles in the Mesh Description // NOTE: If you change the iteration order here, please update the corresponding iteration in FDynamicMeshToMeshDescription::UpdateAttributes, // which assumes the iteration order here is the same as here to correspond the triangles when writing updated attributes back! for (const FTriangleID TriangleID : MeshIn->Triangles().GetElementIDs()) { // Get the PolygonID, PolygonGroupID and general GroupID //--- FPolygonID PolygonID = MeshIn->GetTrianglePolygon(TriangleID); FPolygonGroupID PolygonGroupID = MeshIn->GetTrianglePolygonGroup(TriangleID); int GroupID = 0; switch (GroupMode) { case EPrimaryGroupMode::SetToPolyGroup: if (PolyGroups.IsValid()) { GroupID = PolyGroups.Get(PolygonID, 0); } break; case EPrimaryGroupMode::SetToPolygonID: GroupID = PolygonID.GetValue() + 1; // Shift IDs up by 1 to leave ID 0 as a default/unassigned group break; case EPrimaryGroupMode::SetToPolygonGroupID: GroupID = PolygonGroupID.GetValue() + 1; // Shift IDs up by 1 to leave ID 0 as a default/unassigned group break; case EPrimaryGroupMode::SetToZero: break; // keep at 0 } FTriData TriData; TriData.PolygonID = PolygonID; TriData.PolygonGroupID = PolygonGroupID; // stash the vertex instance IDs for this triangle. potentially needed for per-instance attribute welding TArrayView InstanceTri = MeshIn->GetTriangleVertexInstances(TriangleID); TriData.TriInstances[0] = InstanceTri[0]; TriData.TriInstances[1] = InstanceTri[1]; TriData.TriInstances[2] = InstanceTri[2]; //--- // Get the vertex IDs for this triangle. TArrayView TriangleVertexIDs = MeshIn->GetTriangleVertices(TriangleID); // sanity checkSlow(TriangleVertexIDs.Num() == 3); FIndex3i VertexIDs; VertexIDs[0] = TriangleVertexIDs[0].GetValue(); VertexIDs[1] = TriangleVertexIDs[1].GetValue(); VertexIDs[2] = TriangleVertexIDs[2].GetValue(); int NewTriangleID = MeshOut.AppendTriangle(VertexIDs, GroupID); // Deal with potential failure cases //-- already seen this triangle for some reason.. or the MeshDecription had a degenerate tri if (NewTriangleID == FDynamicMesh3::DuplicateTriangleID || NewTriangleID == FDynamicMesh3::InvalidID) { continue; } //-- non manifold // if append failed due to non-manifold, duplicate verts if (NewTriangleID == FDynamicMesh3::NonManifoldID) { bSrcIsManifold = false; int e0 = MeshOut.FindEdge(VertexIDs[0], VertexIDs[1]); int e1 = MeshOut.FindEdge(VertexIDs[1], VertexIDs[2]); int e2 = MeshOut.FindEdge(VertexIDs[2], VertexIDs[0]); // determine which verts need to be duplicated bool bDuplicate[3] = { false, false, false }; if (e0 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e0) == false) { bDuplicate[0] = true; bDuplicate[1] = true; } if (e1 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e1) == false) { bDuplicate[1] = true; bDuplicate[2] = true; } if (e2 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e2) == false) { bDuplicate[2] = true; bDuplicate[0] = true; } for (int32 i = 0; i < 3; ++i) { if (bDuplicate[i]) { const FVertexID& TriangleVertID = TriangleVertexIDs[i]; const FVector3f Position = VertexPositions[TriangleVertID]; const int32 NewVertIdx = MeshOut.AppendVertex( (FVector3d)Position ); VertexIDs[i] = NewVertIdx; VertIDMap.SetNumUninitialized(NewVertIdx + 1); VertIDMap[NewVertIdx] = TriangleVertID; } } NewTriangleID = MeshOut.AppendTriangle(VertexIDs, GroupID); checkSlow(NewTriangleID != FDynamicMesh3::NonManifoldID); } checkSlow(NewTriangleID >= 0); AddedTriangles[NewTriangleID] = TriData; TriIDMap[NewTriangleID] = TriangleID; } int32 MaxTriID = MeshOut.MaxTriangleID(); // really MaxTriID+1 // if the source mesh had duplicates then TriIDMap will be too long, this will trim excess TriIDMap.SetNum(MaxTriID); FDateTime Time_AfterTriangles = FDateTime::Now(); // // Enable relevant attributes and initialize UV/Normal welders // // the shared UV representation. const int32 NumUVElementChannels = MeshIn->GetNumUVElementChannels(); // the instanced UV representation. const int NumUVLayers = InstanceUVs.IsValid() ? InstanceUVs.GetNumChannels() : 0; // determine if we really have shared UVS. Legacy geo might not have them // - at the time of this writing MeshDescription has not been updated // to always populated the shared UVs during load time for legacy geometry that only has per-instance UVs. // but that is a promised improvement. bool bUseSharedUVs = (NumUVLayers == NumUVElementChannels); if (bUseSharedUVs) { for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) { const int32 NumSharedUVs = MeshIn->UVs(UVLayerIndex).GetArraySize(); bUseSharedUVs = bUseSharedUVs && (NumSharedUVs != 0); } } FDynamicMeshColorOverlay* ColorOverlay = nullptr; FDynamicMeshNormalOverlay* NormalOverlay = nullptr; FDynamicMeshNormalOverlay* TangentOverlay = nullptr; FDynamicMeshNormalOverlay* BiTangentOverlay = nullptr; FDynamicMeshMaterialAttribute* MaterialIDAttrib = nullptr; FDynamicMeshBoneNameAttribute* BoneNameAttrib = nullptr; FDynamicMeshBoneParentIndexAttribute* BoneParentIndexAttrib = nullptr; FDynamicMeshBonePoseAttribute* BonePoseAttrib = nullptr; FDynamicMeshBoneColorAttribute* BoneColorAttrib = nullptr; TArray SkinWeightAttribs; if (!bDisableAttributes) { MeshOut.EnableAttributes(); // by default 1-UV layer and 1-normal layer // Normals // set up for the tangent plane vectors if required int32 NumRequiredNormalLayers = (bCopyTangents) ? 3 : 1; if (MeshOut.Attributes()->NumNormalLayers() < NumRequiredNormalLayers) { // add additional normal layers and reserve space in them MeshOut.Attributes()->SetNumNormalLayers(NumRequiredNormalLayers); } for (int32 i = 0; i < NumRequiredNormalLayers; ++i) { MeshOut.Attributes()->GetNormalLayer(i)->InitializeTriangles(MeshOut.MaxTriangleID()); } NormalOverlay = MeshOut.Attributes()->PrimaryNormals(); if (bCopyTangents) { TangentOverlay = MeshOut.Attributes()->PrimaryTangents(); BiTangentOverlay = MeshOut.Attributes()->PrimaryBiTangents(); } // UVs MeshOut.Attributes()->SetNumUVLayers(NumUVLayers); // reserve space in any new UV layers. for (int32 i = 1; i < NumUVLayers; ++i) { MeshOut.Attributes()->GetUVLayer(i)->InitializeTriangles(MeshOut.MaxTriangleID()); } if (InstanceColors.IsValid()) { MeshOut.Attributes()->EnablePrimaryColors(); ColorOverlay = MeshOut.Attributes()->PrimaryColors(); } // always enable Material ID if there are any attributes MeshOut.Attributes()->EnableMaterialID(); MaterialIDAttrib = MeshOut.Attributes()->GetMaterialID(); // Copy all skin weight attributes, if they exist. MeshIn->VertexAttributes().ForEach([&](const FName InAttributeName, auto InAttributesRef) { if (FSkeletalMeshAttributes::IsSkinWeightAttribute(InAttributeName)) { const FName ProfileName = FSkeletalMeshAttributes::GetProfileNameFromAttribute(InAttributeName); FDynamicMeshVertexSkinWeightsAttribute *VertexSkinWeightsAttrib = new FDynamicMeshVertexSkinWeightsAttribute(&MeshOut); SkinWeightAttribs.Add({ Attributes.GetVertexSkinWeightsFromAttributeName(InAttributeName), VertexSkinWeightsAttrib }); MeshOut.Attributes()->AttachSkinWeightsAttribute(ProfileName, VertexSkinWeightsAttrib); } }); if (Attributes.HasBones()) { MeshOut.Attributes()->EnableBones(Attributes.GetNumBones()); if (Attributes.HasBoneNameAttribute()) { BoneNameAttrib = MeshOut.Attributes()->GetBoneNames(); } if (Attributes.HasBoneParentIndexAttribute()) { BoneParentIndexAttrib = MeshOut.Attributes()->GetBoneParentIndices(); } if (Attributes.HasBonePoseAttribute()) { BonePoseAttrib = MeshOut.Attributes()->GetBonePoses(); } if (Attributes.HasBoneColorAttribute()) { BoneColorAttrib = MeshOut.Attributes()->GetBoneColors(); } } } // find polygroup attribs const TAttributesSet& TriAttribsSet = MeshIn->TriangleAttributes(); TArray> PolygroupAttribs; TArray PolygroupAttribNames; TriAttribsSet.ForEach([&](const FName AttributeName, auto AttributesRef) { if (FSkeletalMeshAttributes::IsReservedAttributeName(AttributeName)) return; if (TriAttribsSet.template HasAttributeOfType(AttributeName)) { PolygroupAttribs.Add(TriAttribsSet.GetAttributesRef(AttributeName)); PolygroupAttribNames.Add(AttributeName); } }); // find weight attribs const TAttributesSet& VertAttribsSet = MeshIn->VertexAttributes(); TArray> WeightAttribs; TArray WeightAttribNames; VertAttribsSet.ForEach([&](const FName AttributeName, auto AttributesRef) { if (FSkeletalMeshAttributes::IsReservedAttributeName(AttributeName)) return; if (FSkeletalMeshAttributes::IsSkinWeightAttribute(AttributeName)) return; if (VertAttribsSet.template HasAttributeOfType(AttributeName)) { WeightAttribs.Add(VertAttribsSet.GetAttributesRef(AttributeName)); WeightAttribNames.Add(AttributeName); } }); if (!bDisableAttributes) { bool bFoundNonDefaultVertexInstanceColor = false; // we will weld/populate all the attributes simultaneously, hold on to futures in this array and then Wait for them at the end TArray Pending; if (PolygroupAttribs.Num() > 0) { MeshOut.Attributes()->SetNumPolygroupLayers(PolygroupAttribs.Num()); } MeshOut.Attributes()->SetNumWeightLayers(WeightAttribs.Num()); for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) { UE::Tasks::FTask UVTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&, UVLayerIndex, bUseSharedUVs]() // must copy UVLayerIndex here! { // the overlay to fill. FDynamicMeshUVOverlay* UVOverlay = MeshOut.Attributes()->GetUVLayer(UVLayerIndex); if (UVOverlay == nullptr) return; if (!bUseSharedUVs) // have to rely on welding the per-instance uvs { FUVWelder UVWelder; UVWelder.UVOverlay = UVOverlay; for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriUV; for (int j = 0; j < 3; ++j) { FVector2f UV = InstanceUVs.Get(TriData.TriInstances[j], UVLayerIndex); TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]); } UVOverlay->SetTriangle(TriangleID, TriUV); } } else { // copy uv "vertex buffer" const FUVArray& UVs = MeshIn->UVs(UVLayerIndex); TUVAttributesRef UVCoordinates = UVs.GetAttributes().GetAttributesRef(MeshAttribute::UV::UVCoordinate); // map to translate UVIds from FUVID::int32() to DynamicOverlay Index. TArray UVIndexMap; { // pre-compute max UVID in the mesh description int32 MaxUVElID = -1; for (FUVID UVID : UVs.GetElementIDs()) { MaxUVElID = FMath::Max(MaxUVElID, UVID.GetValue()); } UVIndexMap.AddUninitialized(MaxUVElID+1); } for (FUVID UVID : UVs.GetElementIDs()) { const FVector2f UVvalue = UVCoordinates[UVID]; int32 NewIndex = UVOverlay->AppendElement(FVector2f(UVvalue)); UVIndexMap[UVID.GetValue()] = NewIndex; } // copy uv "index buffer" FUVID InvalidUVID(INDEX_NONE); TArray TrisMissingUVs; TMap SplitUVMapping; // mapping from (ParentVID, OrigElID) -> SplitElID, for UV ElIDs that need to be split to multiple vertices for (int32 TriID : MeshOut.TriangleIndicesItr()) { FTriangleID TriangleID = TriIDMap[TriID]; // NB: the mesh description lacks a const method variant of this function, hence the const_cast. // Don't change the UVIndices values! TArrayView UVIndices = const_cast(MeshIn)->GetTriangleUVIndices(TriangleID, UVLayerIndex); if (UVIndices[0] == InvalidUVID || UVIndices[1] == InvalidUVID || UVIndices[2] == InvalidUVID) { // keep track of the tris that don't have UVs in the mesh description shared UV channel. TrisMissingUVs.Add(TriID); continue; } // translate to Overlay indicies FIndex3i TriUV( UVIndexMap[UVIndices[0].GetValue()], UVIndexMap[UVIndices[1].GetValue()], UVIndexMap[UVIndices[2].GetValue()] ); ///-- We have to do some clean-up on the shared UVs that come from MeshDecription --/// // This clean up should go away if MeshDescription can solve these problems during import from the source fbx files { // Helper to split UVs out on a triangle while keeping UVs with the same source ID and parent vertex the same element in the overlay auto SplitVertexUV = [&SplitUVMapping, &UVCoordinates, &UVIndices, &UVOverlay](int ParentVID, FIndex3i& TriUV, int Idx) { FIndex2i SplitUVID(ParentVID, TriUV[Idx]); int* FoundUVEl = SplitUVMapping.Find(SplitUVID); if (FoundUVEl) { TriUV[Idx] = *FoundUVEl; } else { const FVector2f UVvalue = UVCoordinates[UVIndices[Idx]]; int NewUVEl = UVOverlay->AppendElement(FVector2f(UVvalue)); TriUV[Idx] = NewUVEl; SplitUVMapping.Add(SplitUVID, NewUVEl); } }; // MeshDescription can attach multiple vertices to the same UV element. DynamicMesh does not. // if we have already used this element for a different mesh vertex, split it. const FIndex3i ParentTriangle = MeshOut.GetTriangle(TriID); for (int i = 0; i < 3; ++i) { int32 ParentVID = UVOverlay->GetParentVertex(TriUV[i]); if (ParentVID != FDynamicMesh3::InvalidID && ParentVID != ParentTriangle[i]) { SplitVertexUV(ParentTriangle[i], TriUV, i); } } // MeshDescription allows for degenerate UV tris. Dynamic Mesh does not. // if the UV tri is degenerate we split the degenerate UV edge by adding a new UV // in its place, or if it is totally degenerate we add 2 new UVs if (TriUV[0] == TriUV[1] && TriUV[0] == TriUV[2]) { const FVector2f UVvalue = UVCoordinates[UVIndices[1]]; SplitVertexUV(ParentTriangle[1], TriUV, 1); SplitVertexUV(ParentTriangle[2], TriUV, 2); } else { int SplitWhich = -1; if (TriUV[0] == TriUV[1]) { SplitWhich = 1; } else if (TriUV[0] == TriUV[2] || TriUV[1] == TriUV[2]) { SplitWhich = 2; } if (SplitWhich > -1) { SplitVertexUV(ParentTriangle[SplitWhich], TriUV, SplitWhich); } } } // set the triangle in the overlay UVOverlay->SetTriangle(TriID, TriUV); } // If some of the mesh description triangles were missing shared UVs, // use the per-vertex UVs on the mesh description and weld these. // NB: This can happen when multiple meshes with different UV layer count are "combined" during import // NB: these mesh description per-vertex UVs always exist, but may default to zero // we may want to revisit this in the future and not set UVs in the Overlay for these triangles, // but currently some of our tools (e.g. inspector) assume the every mesh triangle has an overlay triangle. if (TrisMissingUVs.Num() != 0) { FUVWelder UVWelder; UVWelder.UVOverlay = UVOverlay; for (int32 TriangleID : TrisMissingUVs) { const FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriUV; for (int j = 0; j < 3; ++j) { const FVector2f UV = InstanceUVs.Get(TriData.TriInstances[j], UVLayerIndex); TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]); } UVOverlay->SetTriangle(TriangleID, TriUV); } } } }); Pending.Add(MoveTemp(UVTask)); } if (NormalOverlay != nullptr) { UE::Tasks::FTask NormalTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() { FNormalWelder NormalWelder; NormalWelder.NormalOverlay = NormalOverlay; for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { const FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriNormals; for (int j = 0; j < 3; ++j) { const FVector3f Normal = InstanceNormals.Get(TriData.TriInstances[j]); TriNormals[j] = NormalWelder.FindOrAddUnique(Normal, Tri[j]); } NormalOverlay->SetTriangle(TriangleID, TriNormals); } }); Pending.Add(MoveTemp(NormalTask)); } if (TangentOverlay != nullptr) { auto TangentTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() { FNormalWelder TangentWelder; TangentWelder.NormalOverlay = TangentOverlay; for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { const FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriVector; for (int j = 0; j < 3; ++j) { const FVector3f Vector = InstanceTangents.Get(TriData.TriInstances[j]); TriVector[j] = TangentWelder.FindOrAddUnique(Vector, Tri[j]); } TangentOverlay->SetTriangle(TriangleID, TriVector); } }); Pending.Add(MoveTemp(TangentTask)); } if (BiTangentOverlay != nullptr) { auto BiTangentTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() { FNormalWelder BiTangentWelder; BiTangentWelder.NormalOverlay = BiTangentOverlay; for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { const FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriVector; for (int j = 0; j < 3; ++j) { // compute the bi tangent. const FVertexInstanceID VertexInstanceID = TriData.TriInstances[j]; const FVector3f NormalVector = InstanceNormals.Get(VertexInstanceID); const FVector3f TangentVector = InstanceTangents.Get(VertexInstanceID); const float BiSign = InstanceBiTangentSign.Get(VertexInstanceID); FVector3f BiTangentVector = BiSign * FVector3f::CrossProduct(NormalVector, TangentVector); BiTangentVector.Normalize(); TriVector[j] = BiTangentWelder.FindOrAddUnique(BiTangentVector, Tri[j]); } BiTangentOverlay->SetTriangle(TriangleID, TriVector); } }); Pending.Add(MoveTemp(BiTangentTask)); } if (ColorOverlay != nullptr) { auto ColorTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() { const FVector4f DefaultColor4 = InstanceColors.GetDefaultValue(); FColorWelder ColorWelder(ColorOverlay); for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { const FIndex3i Tri = MeshOut.GetTriangle(TriangleID); const FTriData& TriData = AddedTriangles[TriangleID]; FIndex3i TriVector; for (int j = 0; j < 3; ++j) { FVector4f InstanceColor4 = InstanceColors.Get(TriData.TriInstances[j], 0); ApplyVertexColorTransform(InstanceColor4); const FVector4f OverlayColor(InstanceColor4.X, InstanceColor4.Y, InstanceColor4.Z, InstanceColor4.W); TriVector[j] = ColorWelder.FindOrAddUnique(OverlayColor, Tri[j]); // need to detect if the vertex instance color actually held any real data.. bFoundNonDefaultVertexInstanceColor |= (InstanceColor4 != DefaultColor4); } ColorOverlay->SetTriangle(TriangleID, TriVector); } }); Pending.Add(MoveTemp(ColorTask)); } if (MaterialIDAttrib != nullptr) { auto MaterialTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() { for (int32 TriangleID : MeshOut.TriangleIndicesItr()) { const FTriData& TriData = AddedTriangles[TriangleID]; int32 MaterialIndex = TriData.PolygonGroupID; if (PolygonGroupToMaterialIndexMap.IsValidIndex(TriData.PolygonGroupID)) { MaterialIndex = PolygonGroupToMaterialIndexMap[TriData.PolygonGroupID]; } MaterialIDAttrib->SetValue(TriangleID, &MaterialIndex); } }); Pending.Add(MoveTemp(MaterialTask)); } for (const FSkinWeightsAttribCopyInfo& SkinWeightAttribInfo: SkinWeightAttribs) { auto SkinWeightsTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&, SkinWeightAttribInfo]() -> void { for (int32 VertexID: MeshOut.VertexIndicesItr()) { SkinWeightAttribInfo.DynaMeshAttribRef->SetValue(VertexID, SkinWeightAttribInfo.MeshDescAttribRef.Get(VertIDMap[VertexID])); } }); Pending.Add(MoveTemp(SkinWeightsTask)); } if (BoneNameAttrib != nullptr) { auto BonesTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() -> void { const int NumBones = Attributes.GetNumBones(); for (int32 BoneID = 0; BoneID < NumBones; ++BoneID) { BoneNameAttrib->SetValue(BoneID, Attributes.GetBoneNames().Get(BoneID)); if (BoneParentIndexAttrib) { BoneParentIndexAttrib->SetValue(BoneID, Attributes.GetBoneParentIndices().Get(BoneID)); } if (BonePoseAttrib) { BonePoseAttrib->SetValue(BoneID, Attributes.GetBonePoses().Get(BoneID)); } if (BoneColorAttrib) { BoneColorAttrib->SetValue(BoneID, Attributes.GetBoneColors().Get(BoneID)); } } }); Pending.Add(MoveTemp(BonesTask)); } // initialize polygroup layers for (int32 GroupLayerIdx = 0; GroupLayerIdx < PolygroupAttribs.Num(); GroupLayerIdx++) { auto PolygroupTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&,Index=GroupLayerIdx]() { TTriangleAttributesConstRef InputGroupSet = PolygroupAttribs[Index]; FDynamicMeshPolygroupAttribute* OutputGroupSet = MeshOut.Attributes()->GetPolygroupLayer(Index); if (ensure(OutputGroupSet)) { OutputGroupSet->SetName(PolygroupAttribNames[Index]); for (int32 tid : MeshOut.TriangleIndicesItr()) { FTriangleID SourceTriangleID = TriIDMap[tid]; int32 GroupID = InputGroupSet.Get(SourceTriangleID); OutputGroupSet->SetValue(tid, &GroupID); } } }); Pending.Add(MoveTemp(PolygroupTask)); } // initialize weight layers for (int32 WeightLayerIdx = 0; WeightLayerIdx < WeightAttribs.Num(); WeightLayerIdx++) { auto WeightTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, &WeightAttribs, &WeightAttribNames, &MeshOut, WeightLayerIdx]() { TVertexAttributesConstRef InputWeights = WeightAttribs[WeightLayerIdx]; FDynamicMeshWeightAttribute* OutputWeights = MeshOut.Attributes()->GetWeightLayer(WeightLayerIdx); if (ensure(OutputWeights)) { OutputWeights->SetName(WeightAttribNames[WeightLayerIdx]); for (int32 vid : MeshOut.VertexIndicesItr()) { FVertexID SourceVertexID = VertIDMap[vid]; float Value = InputWeights.Get(SourceVertexID); OutputWeights->SetValue(vid, &Value); } } }); Pending.Add(MoveTemp(WeightTask)); } // wait for all work to be done UE::Tasks::Wait(Pending); // add non-manifold mapping information if required. if (!bSrcIsManifold && bVIDsFromNonManifoldMeshDescriptionAttr) { TArray TmpVIDMap; TmpVIDMap.SetNumUninitialized(VertIDMap.Num()); for (int32 i = 0, I = VertIDMap.Num(); i < I; ++i) { TmpVIDMap[i] = VertIDMap[i].GetValue(); } FNonManifoldMappingSupport::AttachNonManifoldVertexMappingData(TmpVIDMap, MeshOut); } if (!bFoundNonDefaultVertexInstanceColor) { MeshOut.Attributes()->DisablePrimaryColors(); } } // free maps if no longer needed if (!bCalculateMaps) { TriIDMap.Empty(); VertIDMap.Empty(); } FDateTime Time_AfterAttribs = FDateTime::Now(); if (bPrintDebugMessages) { UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: Conversion Timing: Triangles %fs Attributbes %fs"), (Time_AfterTriangles - Time_AfterVertices).GetTotalSeconds(), (Time_AfterAttribs - Time_AfterTriangles).GetTotalSeconds()); int NumUVs = (MeshOut.HasAttributes() && NumUVLayers > 0) ? MeshOut.Attributes()->PrimaryUV()->MaxElementID() : 0; int NumNormals = (NormalOverlay != nullptr) ? NormalOverlay->MaxElementID() : 0; UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: FDynamicMesh verts %d triangles %d (primary) uvs %d normals %d"), MeshOut.MaxVertexID(), MeshOut.MaxTriangleID(), NumUVs, NumNormals); } } template static void CopyTangents_Internal(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents* TangentsOut, const TArray& TriIDMap) { FStaticMeshConstAttributes Attributes(*SourceMesh); TArrayView InstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray(); TArrayView InstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray(); TArrayView InstanceSigns = Attributes.GetVertexInstanceBinormalSigns().GetRawArray(); if (!ensureMsgf(InstanceNormals.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance Normals"))) return; if (!ensureMsgf(InstanceTangents.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance Tangents"))) return; if (!ensureMsgf(InstanceSigns.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance BinormalSigns"))) return; TangentsOut->SetMesh(TargetMesh); TangentsOut->InitializeTriVertexTangents(false); for (int32 TriID : TargetMesh->TriangleIndicesItr()) { const FTriangleID TriangleID = TriIDMap[TriID]; TArrayView InstanceTri = SourceMesh->GetTriangleVertexInstances(TriangleID); for (int32 j = 0; j < 3; ++j) { FVector Normal(InstanceNormals[InstanceTri[j]]); FVector Tangent(InstanceTangents[InstanceTri[j]]); float BitangentSign = InstanceSigns[InstanceTri[j]]; TVector Bitangent = VectorUtil::Bitangent((TVector)Normal, (TVector)Tangent, (RealType)BitangentSign); Tangent.Normalize(); UE::Geometry::Normalize(Bitangent); TangentsOut->SetPerTriangleTangent(TriID, j, (TVector)Tangent, (TVector)Bitangent); } } } void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents* TangentsOut) { if (!ensureMsgf(bCalculateMaps, TEXT("Cannot CopyTangents unless Maps were calculated"))) return; int32 TargetTriCount = TargetMesh->TriangleCount(); if (!ensureMsgf(TriIDMap.Num() == TargetTriCount, TEXT("Tried to CopyTangents to mesh with different triangle count"))) return; CopyTangents_Internal(SourceMesh, TargetMesh, TangentsOut, TriIDMap); } void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents* TangentsOut) { if (!ensureMsgf(bCalculateMaps, TEXT("Cannot CopyTangents unless Maps were calculated"))) return; if (!ensureMsgf(TriIDMap.Num() == TargetMesh->TriangleCount(), TEXT("Tried to CopyTangents to mesh with different triangle count"))) return; CopyTangents_Internal(SourceMesh, TargetMesh, TangentsOut, TriIDMap); } void FMeshDescriptionToDynamicMesh::ApplyVertexColorTransform(FVector4f& Color) const { if (bTransformVertexColorsLinearToSRGB) { // The corollary to bTransformVertexColorsSRGBToLinear in DynamicMeshToMeshDescription. // See DynamicMeshToMeshDescription::ApplyVertexColorTransform(..). // // StaticMeshes store vertex colors as FColor. The StaticMesh build always encodes // FColors as SRGB to ensure a good distribution of float values across the 8-bit range. // // Since there is no currently defined gamma space convention for vertex colors in // engine, an option is provided to pre-transform vertex colors (SRGB To Linear) when // writing out a MeshDescription. // // We similarly provide the inverse of that optional pre-transformation to maintain // color space consistency in our usage. LinearColors::LinearToSRGB(Color); } }