// Copyright Epic Games, Inc. All Rights Reserved. #include "DynamicMeshToMeshDescription.h" #include "SkeletalMeshAttributes.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/DynamicMeshOverlay.h" #include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h" #include "DynamicMesh/DynamicBoneAttribute.h" #include "DynamicMesh/DynamicVertexAttribute.h" #include "MeshDescriptionBuilder.h" #include "DynamicMesh/MeshTangents.h" #include "DynamicMesh/NonManifoldMappingSupport.h" #include "Util/ColorConstants.h" using namespace UE::Geometry; namespace DynamicMeshToMeshDescriptionConversionHelper { // NOTE: assumes the order of triangles in the MeshIn correspond to the ordering over tris on MeshOut // This matches conversion currently used in MeshDescriptionToDynamicMesh.cpp, but if that changes we will need to change this function to match! template void SetAttributesFromOverlay( const FDynamicMesh3* MeshInArg, const FMeshDescription& MeshOutArg, TVertexInstanceAttributesRef& InstanceAttrib, const TDynamicMeshVectorOverlay* Overlay, OutAttributeType DefaultValue, int AttribIndex=0) { for (const FTriangleID TriangleID : MeshOutArg.Triangles().GetElementIDs()) { TArrayView InstanceTri = MeshOutArg.GetTriangleVertexInstances(TriangleID); int32 MeshInTriIdx = TriangleID.GetValue(); if (Overlay->IsSetTriangle(MeshInTriIdx)) { FIndex3i OverlayVertIndices = Overlay->GetTriangle(MeshInTriIdx); InstanceAttrib.Set(InstanceTri[0], AttribIndex, OutAttributeType(Overlay->GetElement(OverlayVertIndices.A))); InstanceAttrib.Set(InstanceTri[1], AttribIndex, OutAttributeType(Overlay->GetElement(OverlayVertIndices.B))); InstanceAttrib.Set(InstanceTri[2], AttribIndex, OutAttributeType(Overlay->GetElement(OverlayVertIndices.C))); } else { InstanceAttrib.Set(InstanceTri[0], AttribIndex, DefaultValue); InstanceAttrib.Set(InstanceTri[1], AttribIndex, DefaultValue); InstanceAttrib.Set(InstanceTri[2], AttribIndex, DefaultValue); } } } void ConvertVertices( const FDynamicMesh3& InMesh, FMeshDescriptionBuilder& InBuilder, const bool bReconstructNonManifoldMesh, TArray& OutVertexMappings ) { OutVertexMappings.SetNum(InMesh.MaxVertexID()); FNonManifoldMappingSupport ManifoldMapping(InMesh); if (bReconstructNonManifoldMesh && ManifoldMapping.IsNonManifoldVertexInSource()) { // Figure out the actual vertex count we need. For non-mapping vertices, we count them separately and // then tally them all up. The new vertices will be appended after the original vertices. int32 MaxOriginalVertexID = 0; int32 NumNewVertices = 0; for (const int VertID : InMesh.VertexIndicesItr()) { int32 OriginalVertID = ManifoldMapping.GetOriginalNonManifoldVertexID(VertID); if (OriginalVertID != INDEX_NONE) { MaxOriginalVertexID = FMath::Max(MaxOriginalVertexID, OriginalVertID); } else { NumNewVertices++; } } int32 NewVertexID = MaxOriginalVertexID + 1; InBuilder.ReserveNewVertices(NewVertexID + NumNewVertices); TSet DefinedVertexIDs; for (int VertID : InMesh.VertexIndicesItr()) { const FVector3d Position = InMesh.GetVertex(VertID); int32 OriginalVertID = ManifoldMapping.GetOriginalNonManifoldVertexID(VertID); if (OriginalVertID != INDEX_NONE) { // FMeshDescription::CreateVertexWithID is not happy if we try to create the same vertex twice. if (!DefinedVertexIDs.Contains(OriginalVertID)) { OutVertexMappings[VertID] = InBuilder.AppendVertexWithId(OriginalVertID, Position); DefinedVertexIDs.Add(OriginalVertID); } else { OutVertexMappings[VertID] = FVertexID{OriginalVertID}; } } else { OutVertexMappings[VertID] = InBuilder.AppendVertexWithId(NewVertexID++, Position); } } } else { // allocate InBuilder.ReserveNewVertices(InMesh.VertexCount()); // create "vertex buffer" in MeshDescription for (int VertID : InMesh.VertexIndicesItr()) { OutVertexMappings[VertID] = InBuilder.AppendVertex(InMesh.GetVertex(VertID)); } } } } void FDynamicMeshToMeshDescription::Update(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, bool bUpdateNormals, bool bUpdateTangents, bool bUpdateUVs) { FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&MeshOut); check(MeshIn->IsCompactV()); // can't currently update the shared UV connectivity data structure on the MeshDescription :( // -- see FDynamicMeshToMeshDescription::UpdateAttributes() // after this has been fixed, please update USimpleDynamicMeshComponent::Bake() to use the Update() path accordingly check(bUpdateUVs == false); // update positions int32 NumVertices = MeshOut.Vertices().Num(); check(NumVertices <= MeshIn->VertexCount()); for (int32 VertID = 0; VertID < NumVertices; ++VertID) { Builder.SetPosition(FVertexID(VertID), (FVector)MeshIn->GetVertex(VertID)); } UpdateAttributes(MeshIn, MeshOut, bUpdateNormals, bUpdateTangents, bUpdateUVs); } void FDynamicMeshToMeshDescription::UpdateAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, bool bUpdateNormals, bool bUpdateTangents, bool bUpdateUVs) { check(MeshIn->IsCompactV()); FStaticMeshAttributes Attributes(MeshOut); if (bUpdateNormals) { TVertexInstanceAttributesRef InstanceAttrib = Attributes.GetVertexInstanceNormals(); bool bIsValidDst = InstanceAttrib.IsValid(); ensureMsgf(bIsValidDst, TEXT("Trying to update normals on a MeshDescription that has no normal attributes")); if (bIsValidDst) { const FDynamicMeshNormalOverlay* Overlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; if (Overlay) { check(MeshIn->TriangleCount() == MeshOut.Triangles().Num()) DynamicMeshToMeshDescriptionConversionHelper::SetAttributesFromOverlay(MeshIn, MeshOut, InstanceAttrib, Overlay, FVector3f::ZeroVector); } else { check(MeshIn->VertexCount() == MeshOut.Vertices().Num()); for (int VertID : MeshIn->VertexIndicesItr()) { FVector3f Normal = MeshIn->GetVertexNormal(VertID); for (FVertexInstanceID InstanceID : MeshOut.GetVertexVertexInstanceIDs(FVertexID(VertID))) { InstanceAttrib.Set(InstanceID, Normal); } } } } } if (bUpdateTangents) { UpdateTangents(MeshIn, MeshOut); } if (bUpdateUVs) { TVertexInstanceAttributesRef InstanceAttrib = Attributes.GetVertexInstanceUVs(); ensureMsgf(InstanceAttrib.IsValid(), TEXT("Trying to update UVs on a MeshDescription that has no texture coordinate attributes")); if (InstanceAttrib.IsValid()) { if (MeshIn->HasAttributes()) { MeshOut.SuspendUVIndexing(); check(MeshIn->TriangleCount() == MeshOut.Triangles().Num()) int32 NumLayers = MeshIn->Attributes()->NumUVLayers(); MeshOut.SetNumUVChannels(NumLayers); // This resets MeshDescription's internal TriangleUV array InstanceAttrib.SetNumChannels(NumLayers); for (int UVLayerIndex = 0; UVLayerIndex < NumLayers; UVLayerIndex++) { const FDynamicMeshUVOverlay* UVOverlay = MeshIn->Attributes()->GetUVLayer(UVLayerIndex); // update the vertex Attribute UVs DynamicMeshToMeshDescriptionConversionHelper::SetAttributesFromOverlay(MeshIn, MeshOut, InstanceAttrib, UVOverlay, FVector2f::ZeroVector, UVLayerIndex); // rebuild the shared UVs FUVArray& UVArray = MeshOut.UVs(UVLayerIndex); UVArray.Reset(); // delete existing UV buffer UVArray.Reserve(UVOverlay->ElementCount()); // rebuild the UV buffer int32 MaxID = UVOverlay->MaxElementID(); // the true maxid +1 TArray ElIDToUVIDMap; ElIDToUVIDMap.AddUninitialized(MaxID); for (int32 ElID = 0; ElID < MaxID; ++ElID) { if (!UVOverlay->IsElement(ElID)) { continue; } FVector2f UVValue = UVOverlay->GetElement(ElID); FUVID UVID = UVArray.Add(); ElIDToUVIDMap[ElID] = UVID; UVArray.GetAttributes().GetAttributesRef(MeshAttribute::UV::UVCoordinate)[UVID] = UVValue; } for (const FTriangleID TriangleID : MeshOut.Triangles().GetElementIDs()) { int TriID = TriangleID.GetValue(); // assumes the same TriIDs in both meshes. TArray> MDTri; if (UVOverlay->IsSetTriangle(TriID)) { FIndex3i ElIDs = UVOverlay->GetTriangle(TriID); MDTri.Add(ElIDToUVIDMap[ElIDs[0]]); MDTri.Add(ElIDToUVIDMap[ElIDs[1]]); MDTri.Add(ElIDToUVIDMap[ElIDs[2]]); } else { for (int j = 0; j < 3; ++j) { FUVID UVID = UVArray.Add(); UVArray.GetAttributes().GetAttributesRef(MeshAttribute::UV::UVCoordinate)[UVID] = FVector2f::ZeroVector; MDTri.Add(UVID); } } MeshOut.SetTriangleUVIndices(TriangleID, MDTri, UVLayerIndex); } } MeshOut.ResumeUVIndexing(); if (0) { // Verify the shared UVs and per-vertexinstance UVs match for (int UVLayerIndex = 0; UVLayerIndex < NumLayers; UVLayerIndex++) { for (const FTriangleID TriangleID : MeshOut.Triangles().GetElementIDs()) { TArrayView TriWedges = MeshOut.GetTriangleVertexInstances(TriangleID); TArrayView UVTri = MeshOut.GetTriangleUVIndices(TriangleID, UVLayerIndex); for (int32 i = 0; i < 3; ++i) { // UV from shared FVector2f SharedUV = MeshOut.UVs(UVLayerIndex).GetAttributes().GetAttributesRef(MeshAttribute::UV::UVCoordinate)[UVTri[i]]; FVector2f WedgeUV = InstanceAttrib.Get(TriWedges[i], UVLayerIndex); check(SharedUV == WedgeUV); } } } } } else { // [todo] correctly build shared UVs? check(MeshIn->VertexCount() == MeshOut.Vertices().Num()); for (int VertID : MeshIn->VertexIndicesItr()) { FVector2f UV = MeshIn->GetVertexUV(VertID); for (FVertexInstanceID InstanceID : MeshOut.GetVertexVertexInstanceIDs(FVertexID(VertID))) { InstanceAttrib.Set(InstanceID, UV); } } } } } } void FDynamicMeshToMeshDescription::UpdateTangents(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, const FMeshTangentsd* Tangents) { if (!ensureMsgf(MeshIn->TriangleCount() == MeshOut.Triangles().Num(), TEXT("Trying to update MeshDescription Tangents from Mesh that does not have same triangle count"))) return; if (!ensureMsgf(MeshIn->IsCompactT(), TEXT("Trying to update MeshDescription Tangents from a non-compact DynamicMesh"))) return; if (!ensureMsgf(MeshIn->HasAttributes(), TEXT("Trying to update MeshDescription Tangents from a DynamicMesh that has no Normals attribute"))) return; FStaticMeshAttributes Attributes(MeshOut); const FDynamicMeshNormalOverlay* Normals = MeshIn->Attributes()->PrimaryNormals(); TVertexInstanceAttributesRef TangentAttrib = Attributes.GetVertexInstanceTangents(); TVertexInstanceAttributesRef BinormalSignAttrib = Attributes.GetVertexInstanceBinormalSigns(); if (!ensureMsgf(TangentAttrib.IsValid(), TEXT("Trying to update Tangents on a MeshDescription that has no Tangent Vertex Instance attribute"))) return; if (!ensureMsgf(BinormalSignAttrib.IsValid(), TEXT("Trying to update Tangents on a MeshDescription that has no BinormalSign Vertex Instance attribute"))) return; if (TangentAttrib.IsValid() && BinormalSignAttrib.IsValid()) { int32 NumTriangles = MeshIn->TriangleCount(); for (int32 k = 0; k < NumTriangles; ++k) { FVector3f TriNormals[3]; Normals->GetTriElements(k, TriNormals[0], TriNormals[1], TriNormals[2]); TArrayView TriInstances = MeshOut.GetTriangleVertexInstances(FTriangleID(k)); for (int j = 0; j < 3; ++j) { FVector3d Tangent, Bitangent; Tangents->GetPerTriangleTangent(k, j, Tangent, Bitangent); float BitangentSign = (float)VectorUtil::BitangentSign((FVector3d)TriNormals[j], Tangent, Bitangent); TangentAttrib.Set(TriInstances[j], (FVector3f)Tangent); BinormalSignAttrib.Set(TriInstances[j], BitangentSign); } } } } void FDynamicMeshToMeshDescription::UpdateTangents(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) { if (!ensureMsgf(MeshIn->IsCompactT(), TEXT("Trying to update MeshDescription Tangents from a non-compact DynamicMesh"))) return; if (!ensureMsgf(MeshIn->TriangleCount() == MeshOut.Triangles().Num(), TEXT("Trying to update MeshDescription Tangents from Mesh that does not have same triangle count"))) return; if (!ensureMsgf(MeshIn->HasAttributes(), TEXT("Trying to update MeshDescription Tangents from a DynamicMesh that has no attributes, e.g. normals"))) return; // src const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->Attributes()->PrimaryNormals(); const FDynamicMeshNormalOverlay* TangentOverlay = MeshIn->Attributes()->PrimaryTangents(); const FDynamicMeshNormalOverlay* BiTangentOverlay = MeshIn->Attributes()->PrimaryBiTangents(); const bool bHasValidSrc = NormalOverlay && TangentOverlay && BiTangentOverlay; ensureMsgf(bHasValidSrc, TEXT("Trying to update MeshDescription Tangents from a DynamicMesh that does not have all three tangent space attributes")); if (!bHasValidSrc) { return; } // dst FStaticMeshAttributes Attributes(MeshOut); const TVertexInstanceAttributesRef TangentAttrib = Attributes.GetVertexInstanceTangents(); const TVertexInstanceAttributesRef BiTangentSignAttrib = Attributes.GetVertexInstanceBinormalSigns(); if (!ensureMsgf(TangentAttrib.IsValid(), TEXT("Trying to update Tangents on a MeshDescription that has no Tangent Vertex Instance attribute"))) return; if (!ensureMsgf(BiTangentSignAttrib.IsValid(), TEXT("Trying to update Tangents on a MeshDescription that has no BinormalSign Vertex Instance attribute"))) return; const int32 NumTriangles = MeshIn->TriangleCount(); for (int32 t = 0; t < NumTriangles; ++t) { const bool bTriHasTangentSpace = NormalOverlay->IsSetTriangle(t) && TangentOverlay->IsSetTriangle(t) && BiTangentOverlay->IsSetTriangle(t); if (!bTriHasTangentSpace) continue; // get data from dynamic mesh overlays FVector3f TriNormals[3]; NormalOverlay->GetTriElements(t, TriNormals[0], TriNormals[1], TriNormals[2]); FVector3f TriTangents[3]; TangentOverlay->GetTriElements(t, TriTangents[0], TriTangents[1], TriTangents[2]); FVector3f TriBiTangents[3]; BiTangentOverlay->GetTriElements(t, TriBiTangents[0], TriBiTangents[1], TriBiTangents[2]); // set data in the mesh description per-vertex attributes TArrayView TriInstances = MeshOut.GetTriangleVertexInstances(FTriangleID(t)); for (int32 i = 0; i < 3; ++i) { const float BiTangentSign = VectorUtil::BitangentSign(TriNormals[i], TriTangents[i], TriBiTangents[i]); TangentAttrib.Set(TriInstances[i], TriTangents[i]); BiTangentSignAttrib.Set(TriInstances[i], BiTangentSign); } } } void FDynamicMeshToMeshDescription::UpdateVertexColors(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) { FStaticMeshAttributes Attributes(MeshOut); TVertexInstanceAttributesRef InstanceColorsAttrib = Attributes.GetVertexInstanceColors(); if (!ensureMsgf(MeshIn->IsCompactT(), TEXT("Trying to update MeshDescription Colors from a non-compact DynamicMesh"))) return; if (!ensureMsgf(MeshIn->TriangleCount() == MeshOut.Triangles().Num(), TEXT("Trying to update MeshDescription Colors from Mesh that does not have same triangle count"))) return; if (!ensureMsgf(MeshIn->HasAttributes() && MeshIn->Attributes()->HasPrimaryColors(), TEXT("Trying to update MeshDescription Colors from a DynamicMesh that has no attribute set at all or has no color data in its attribute set"))) return; if (!ensureMsgf(InstanceColorsAttrib.IsValid(), TEXT("Trying to update colors on a MeshDescription that has no color attributes"))) return; const FDynamicMeshColorOverlay* ColorOverlay = MeshIn->Attributes()->PrimaryColors(); const int32 NumTriangles = MeshIn->TriangleCount(); for (int32 t = 0; t < NumTriangles; ++t) { if (!ColorOverlay->IsSetTriangle(t)) { continue; } FVector4f TriColors[3]; ColorOverlay->GetTriElements(t, TriColors[0], TriColors[1], TriColors[2]); ApplyVertexColorTransform(TriColors[0]); ApplyVertexColorTransform(TriColors[1]); ApplyVertexColorTransform(TriColors[2]); TArrayView InstanceIDs = MeshOut.GetTriangleVertexInstances(FTriangleID(t)); for (int32 i = 0; i < 3; ++i) { FVector4f InstanceColor4(TriColors[i].X, TriColors[i].Y, TriColors[i].Z, TriColors[i].W); InstanceColorsAttrib.Set(InstanceIDs[i], 0, InstanceColor4); } } } void FDynamicMeshToMeshDescription::Convert(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, bool bCopyTangents) { if (MeshIn->HasAttributes()) { //Convert_SharedInstances(MeshIn, MeshOut); Convert_NoSharedInstances(MeshIn, MeshOut, bCopyTangents); } else { Convert_NoAttributes(MeshIn, MeshOut); } } void FDynamicMeshToMeshDescription::SetMaterialIDMapFromInverseMap(TArrayView PolygroupIDToMaterialIDMap) { int32 MaxMatID = -1; for (int32 MatID : PolygroupIDToMaterialIDMap) { MaxMatID = FMath::Max(MaxMatID, MatID); } MaterialIDToPolygroupIDMap.SetNumUninitialized(MaxMatID + 1); for (int32 Idx = 0; Idx < MaterialIDToPolygroupIDMap.Num(); ++Idx) { MaterialIDToPolygroupIDMap[Idx] = Idx; } for (int32 PolyGroupIdx = 0; PolyGroupIdx < PolygroupIDToMaterialIDMap.Num(); ++PolyGroupIdx) { int32 MatID = PolygroupIDToMaterialIDMap[PolyGroupIdx]; if (MaterialIDToPolygroupIDMap.IsValidIndex(MatID)) { MaterialIDToPolygroupIDMap[MatID] = PolyGroupIdx; } } } bool FDynamicMeshToMeshDescription::HaveMatchingElementCounts(const FDynamicMesh3* DynamicMesh, const FMeshDescription* MeshDescription, bool bVerticesOnly, bool bAttributesOnly) { bool bVerticesMatch = DynamicMesh->IsCompactV() && DynamicMesh->VertexCount() == MeshDescription->Vertices().Num(); bool bTrianglesMatch = DynamicMesh->IsCompactT() && DynamicMesh->TriangleCount() == MeshDescription->Triangles().Num(); if (bVerticesOnly || (bAttributesOnly && !DynamicMesh->HasAttributes())) { return bVerticesMatch; } else if (bAttributesOnly && DynamicMesh->HasAttributes()) { return bTrianglesMatch; } return bVerticesMatch && bTrianglesMatch; } bool FDynamicMeshToMeshDescription::HaveMatchingElementCounts(const FDynamicMesh3* DynamicMesh, const FMeshDescription* MeshDescription) { bool bUpdateAttributes = ConversionOptions.bUpdateNormals || ConversionOptions.bUpdateUVs; return HaveMatchingElementCounts(DynamicMesh, MeshDescription, !bUpdateAttributes, !ConversionOptions.bUpdatePositions); } void FDynamicMeshToMeshDescription::UpdateUsingConversionOptions(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) { // See if we can do a buffer update without having to alter triangles. if (HaveMatchingElementCounts(MeshIn, &MeshOut)) { if (ConversionOptions.bUpdatePositions) { Update(MeshIn, MeshOut, ConversionOptions.bUpdateNormals, ConversionOptions.bUpdateTangents, ConversionOptions.bUpdateUVs); } else if (ConversionOptions.bUpdateNormals || ConversionOptions.bUpdateTangents || ConversionOptions.bUpdateUVs) { UpdateAttributes(MeshIn, MeshOut, ConversionOptions.bUpdateNormals, ConversionOptions.bUpdateTangents, ConversionOptions.bUpdateUVs); } if (ConversionOptions.bUpdateVtxColors) { UpdateVertexColors(MeshIn, MeshOut); } } else { // Do a full conversion. Convert(MeshIn, MeshOut); } } void FDynamicMeshToMeshDescription::Convert_NoAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) { MeshOut.Empty(); FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&MeshOut); Builder.SuspendMeshDescriptionIndexing(); constexpr int32 UVLayerIndex = 0; Builder.SetNumUVLayers(1); Builder.ReserveNewUVs(MeshIn->VertexCount(), UVLayerIndex); bool bCopyGroupToPolyGroup = false; if (ConversionOptions.bSetPolyGroups && MeshIn->HasTriangleGroups()) { Builder.EnablePolyGroups(); bCopyGroupToPolyGroup = true; } // Convert the vertices and construct a mapping table between the dynamic mesh and the MeshDescription, optionally applying // an undoing conversion for the non-manifold fixes applied when converting from a MeshDescription to dynamic mesh. TArray MapV; DynamicMeshToMeshDescriptionConversionHelper::ConvertVertices(*MeshIn, Builder, ConversionOptions.bConvertBackToNonManifold, MapV); FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); // create new instances when seen TMap InstanceList; TMap InstanceUVIDMap; for (int TriID : MeshIn->TriangleIndicesItr()) { FIndex3i Triangle = MeshIn->GetTriangle(TriID); FIndex3i UVTriangle(-1, -1, -1); FIndex3i NormalTriangle = Triangle; FVertexInstanceID InstanceTri[3]; FUVID UVIDs[3]; for (int j = 0; j < 3; ++j) { FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); if (InstanceList.Contains(InstanceElem) == false) { FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[Triangle[j]]); InstanceList.Add(InstanceElem, NewInstanceID); FVector Normal = MeshIn->HasVertexNormals() ? FVector(MeshIn->GetVertexNormal(Triangle[j])) : FVector::UpVector; Builder.SetInstanceNormal(NewInstanceID, Normal); // Add UV to MeshDescription UVvertex buffer FVector2D UV = MeshIn->HasVertexUVs() ? FVector2D(MeshIn->GetVertexUV(Triangle[j])) : FVector2D::ZeroVector; FUVID UVID = Builder.AppendUV(UV, UVLayerIndex); // associate UVID with this instance InstanceUVIDMap.Add(NewInstanceID.GetValue(), UVID); } InstanceTri[j] = InstanceList[InstanceElem]; UVIDs[j] = InstanceUVIDMap[InstanceTri[j].GetValue()]; } FTriangleID NewTriangleID = Builder.AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); // append the UV triangle - builder takes care of the rest Builder.AppendUVTriangle(NewTriangleID, UVIDs[0], UVIDs[1], UVIDs[2], UVLayerIndex); if (bCopyGroupToPolyGroup) { Builder.SetPolyGroupID(NewTriangleID, MeshIn->GetTriangleGroup(TriID)); } } Builder.ResumeMeshDescriptionIndexing(); } void FDynamicMeshToMeshDescription::Convert_SharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) { const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; MeshOut.Empty(); FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&MeshOut); bool bCopyGroupToPolyGroup = false; if (ConversionOptions.bSetPolyGroups && MeshIn->HasTriangleGroups()) { Builder.EnablePolyGroups(); bCopyGroupToPolyGroup = true; } // create vertices TArray MapV; MapV.SetNum(MeshIn->MaxVertexID()); for (int VertID : MeshIn->VertexIndicesItr()) { MapV[VertID] = Builder.AppendVertex((FVector)MeshIn->GetVertex(VertID)); } FPolygonGroupID ZeroPolygonGroupID = Builder.AppendPolygonGroup(); // check if we have per-triangle material ID const FDynamicMeshMaterialAttribute* MaterialIDAttrib = (MeshIn->HasAttributes() && MeshIn->Attributes()->HasMaterialID()) ? MeshIn->Attributes()->GetMaterialID() : nullptr; // need to know max material index value so we can reserve groups in MeshDescription int32 MaxPolygonGroupID = 0; if (MaterialIDAttrib) { for (int TriID : MeshIn->TriangleIndicesItr()) { int32 MaterialID; MaterialIDAttrib->GetValue(TriID, &MaterialID); if (MaterialIDToPolygroupIDMap.IsValidIndex(MaterialID)) { MaterialID = MaterialIDToPolygroupIDMap[MaterialID]; } MaxPolygonGroupID = FMath::Max(MaterialID, MaxPolygonGroupID); } if (MaxPolygonGroupID == 0) { MaterialIDAttrib = nullptr; } else { for (int k = 0; k < MaxPolygonGroupID; ++k) { Builder.AppendPolygonGroup(); } } } // build all vertex instances (splitting as needed) // store per-triangle instance ids struct Tri { FVertexInstanceID V[3]; Tri() : V{ INDEX_NONE,INDEX_NONE,INDEX_NONE } {} }; TArray TriVertInstances; TriVertInstances.SetNum(MeshIn->MaxTriangleID()); TArray KnownInstanceIDs; int NumUVLayers = MeshIn->HasAttributes() ? MeshIn->Attributes()->NumUVLayers() : 0; Builder.SetNumUVLayers(NumUVLayers); int KIItemLen = 1 + (NormalOverlay ? 1 : 0) + NumUVLayers; for (int VertID : MeshIn->VertexIndicesItr()) { KnownInstanceIDs.Reset(); for (int TriID : MeshIn->VtxTrianglesItr(VertID)) { FIndex3i Tri = MeshIn->GetTriangle(TriID); int SubIdx = IndexUtil::FindTriIndex(VertID, Tri); int32 FoundInstance = INDEX_NONE; for (int KIItemIdx = 0; KIItemIdx < KnownInstanceIDs.Num(); KIItemIdx += KIItemLen) { int KIItemInternalIdx = KIItemIdx; if (NormalOverlay && KnownInstanceIDs[KIItemInternalIdx++] != NormalOverlay->GetTriangle(TriID)[SubIdx]) { continue; } bool FoundInUVs = true; for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) { const FDynamicMeshUVOverlay* Overlay = MeshIn->Attributes()->GetUVLayer(UVLayerIndex); if (KnownInstanceIDs[KIItemInternalIdx++] != Overlay->GetTriangle(TriID)[SubIdx]) { FoundInUVs = false; break; } } if (!FoundInUVs) { continue; } FoundInstance = KnownInstanceIDs[KIItemInternalIdx++]; check(KIItemInternalIdx == KIItemIdx + KIItemLen); break; } if (FoundInstance == INDEX_NONE) { FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[VertID]); if (NormalOverlay) { int ElID = NormalOverlay->GetTriangle(TriID)[SubIdx]; KnownInstanceIDs.Add(int32(ElID)); FVector3f ElementNormal = ElID != -1 ? NormalOverlay->GetElement(ElID) : FVector3f::UnitZ(); Builder.SetInstanceNormal(NewInstanceID, (FVector)ElementNormal); } else { Builder.SetInstanceNormal(NewInstanceID, FVector::UpVector); } for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) { const FDynamicMeshUVOverlay* Overlay = MeshIn->Attributes()->GetUVLayer(UVLayerIndex); int ElID = Overlay->GetTriangle(TriID)[SubIdx]; KnownInstanceIDs.Add(int32(ElID)); FVector2f ElementUV = ElID != -1 ? Overlay->GetElement(ElID) : FVector2f::Zero(); Builder.SetInstanceUV(NewInstanceID, (FVector2D)ElementUV, UVLayerIndex); } FoundInstance = NewInstanceID.GetValue(); KnownInstanceIDs.Add(FoundInstance); } TriVertInstances[TriID].V[SubIdx] = FVertexInstanceID(FoundInstance); } } // build the polygons for (int TriID : MeshIn->TriangleIndicesItr()) { // transfer material index to MeshDescription polygon group (by convention) FPolygonGroupID UsePolygonGroupID = ZeroPolygonGroupID; if (MaterialIDAttrib) { int32 MaterialID; MaterialIDAttrib->GetValue(TriID, &MaterialID); if (MaterialIDToPolygroupIDMap.IsValidIndex(MaterialID)) { MaterialID = MaterialIDToPolygroupIDMap[MaterialID]; } UsePolygonGroupID = FPolygonGroupID(MaterialID); } FTriangleID NewTriangleID = Builder.AppendTriangle(TriVertInstances[TriID].V[0], TriVertInstances[TriID].V[1], TriVertInstances[TriID].V[2], UsePolygonGroupID); if (bCopyGroupToPolyGroup) { Builder.SetPolyGroupID(NewTriangleID, MeshIn->GetTriangleGroup(TriID)); } } } void FDynamicMeshToMeshDescription::Convert_NoSharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, bool bCopyTangents) { const bool bHasAttributes = MeshIn->HasAttributes(); // check if we have per-triangle material ID const FDynamicMeshMaterialAttribute* MaterialIDAttrib = (bHasAttributes && MeshIn->Attributes()->HasMaterialID()) ? MeshIn->Attributes()->GetMaterialID() : nullptr; // cache tangent space and UV and color overlay info const FDynamicMeshNormalOverlay* NormalOverlay = bHasAttributes ? MeshIn->Attributes()->PrimaryNormals() : nullptr; const FDynamicMeshNormalOverlay* TangentOverlay = bHasAttributes ? MeshIn->Attributes()->PrimaryTangents() : nullptr; const FDynamicMeshNormalOverlay* BiTangentOverlay = bHasAttributes ? MeshIn->Attributes()->PrimaryBiTangents() : nullptr; const FDynamicMeshColorOverlay* ColorOverlay = bHasAttributes ? MeshIn->Attributes()->PrimaryColors() : nullptr; const int32 NumUVLayers = bHasAttributes ? MeshIn->Attributes()->NumUVLayers() : 0; const bool bHasBones = bHasAttributes && MeshIn->Attributes()->HasBones(); const FDynamicMeshBoneNameAttribute* BoneNames = bHasBones ? MeshIn->Attributes()->GetBoneNames() : nullptr; const FDynamicMeshBoneParentIndexAttribute* BoneParentIndices = bHasBones ? MeshIn->Attributes()->GetBoneParentIndices() : nullptr; const FDynamicMeshBonePoseAttribute* BonePoses = bHasBones ? MeshIn->Attributes()->GetBonePoses() : nullptr; const FDynamicMeshBoneColorAttribute* BoneColors = bHasBones ? MeshIn->Attributes()->GetBoneColors() : nullptr; const bool bHasSkinWeightsAttributes = bHasAttributes && !MeshIn->Attributes()->GetSkinWeightsAttributes().IsEmpty(); // cache the UV layers TArray UVLayers; for (int32 k = 0; k < NumUVLayers; ++k) { UVLayers.Add(MeshIn->Attributes()->GetUVLayer(k)); } MeshOut.Empty(); FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&MeshOut); bool bCopyGroupToPolyGroup = false; if (ConversionOptions.bSetPolyGroups && MeshIn->HasTriangleGroups()) { Builder.EnablePolyGroups(); bCopyGroupToPolyGroup = true; } // We register skeletal attributes if either bone names or skinning infomation is available if (bHasBones || bHasSkinWeightsAttributes) { FSkeletalMeshAttributes MeshOutAttributes(MeshOut); MeshOutAttributes.Register(); MeshOutAttributes.RegisterColorAttribute(); // If there was an attribute to map from the mesh description index to the imported mesh index, remove that // now, since we don't carry this mapping over through the dynamic mesh. MeshOutAttributes.UnregisterImportPointIndexAttribute(); } if (bHasBones) { FSkeletalMeshAttributes MeshOutAttributes(MeshOut); checkSlow(MeshOutAttributes.HasBones()); // FSkeletalMeshAttributes::Register() must have been called const int32 NumBones = MeshIn->Attributes()->GetNumBones(); MeshOutAttributes.BoneAttributes(); MeshOutAttributes.ReserveNewBones(NumBones); for (int Idx = 0; Idx < NumBones; ++Idx) { const FBoneID BoneID = MeshOutAttributes.CreateBone(); MeshOutAttributes.GetBoneNames().Set(BoneID, BoneNames->GetValue(Idx)); MeshOutAttributes.GetBoneParentIndices().Set(BoneID, BoneParentIndices->GetValue(Idx)); MeshOutAttributes.GetBonePoses().Set(BoneID, BonePoses->GetValue(Idx)); MeshOutAttributes.GetBoneColors().Set(BoneID, BoneColors->GetValue(Idx)); } } TMap VertexBoneWeightsMap; if (bHasSkinWeightsAttributes) { FSkeletalMeshAttributes MeshOutAttributes(MeshOut); for (const TTuple>& AttributeInfo: MeshIn->Attributes()->GetSkinWeightsAttributes()) { FName ProfileName = AttributeInfo.Key; MeshOutAttributes.RegisterSkinWeightAttribute(ProfileName); VertexBoneWeightsMap.Add(ProfileName, MeshOutAttributes.GetVertexSkinWeights(ProfileName)); } } // always copy when we are baking new mesh? should this be a config option? bool bCopyInstanceColors = (ColorOverlay != nullptr); // disable indexing during the full build of the mesh Builder.SuspendMeshDescriptionIndexing(); // Convert the vertices and construct a mapping table between the dynamic mesh and the MeshDescription, optionally applying // an undoing conversion for the non-manifold fixes applied when converting from a MeshDescription to dynamic mesh. TArray MapV; DynamicMeshToMeshDescriptionConversionHelper::ConvertVertices(*MeshIn, Builder, ConversionOptions.bConvertBackToNonManifold, MapV); // create UV vertex buffer in MeshDescription Builder.SetNumUVLayers(NumUVLayers); TArray> MapUVArray; MapUVArray.SetNum(NumUVLayers); TArray UnsetUVIDs; UnsetUVIDs.Init(INDEX_NONE, NumUVLayers); // UVIDs for unset UVs to share, if needed for (int32 k = 0; k < NumUVLayers; ++k) { const FDynamicMeshUVOverlay* UVOverlay = UVLayers[k]; TArray& MapUV = MapUVArray[k]; MapUV.SetNum(UVOverlay->MaxElementID()); Builder.ReserveNewUVs(UVOverlay->ElementCount(), k); for (int32 ElementID : UVOverlay->ElementIndicesItr()) { const FVector2D UVvalue = (FVector2D)UVOverlay->GetElement(ElementID); MapUV[ElementID] = Builder.AppendUV(UVvalue, k); } } FPolygonGroupID ZeroPolygonGroupID = Builder.AppendPolygonGroup(); // construct a function that will transfer tangent space data. // if the DynamicMesh has a full tangent space: a normal, tangent and bitangent sign will be transfered // otherwise just transfer the normal if it exists const bool bCopyFullTangentSpace = bCopyTangents && (NormalOverlay != nullptr && TangentOverlay != nullptr && BiTangentOverlay != nullptr); TFunction TangetSpaceInstanceSetter; if (bCopyFullTangentSpace) // create function that sets the tangent space { TangetSpaceInstanceSetter = [NormalOverlay, TangentOverlay, BiTangentOverlay, &Builder](int TriID, FVertexInstanceID TriVertInstances[3]) { FIndex3i NormalTri = NormalOverlay->GetTriangle(TriID); FIndex3i TangentTri = TangentOverlay->GetTriangle(TriID); FIndex3i BiTangentTri = BiTangentOverlay->GetTriangle(TriID); for (int32 j = 0; j < 3; ++j) { const FVertexInstanceID CornerInstanceID = TriVertInstances[j]; const FVector3f TriVertNormal = (NormalOverlay->IsElement(NormalTri[j])) ? NormalOverlay->GetElement(NormalTri[j]) : FVector3f(FVector::UpVector); const FVector3f TriVertTangent = (TangentOverlay->IsElement(TangentTri[j])) ? TangentOverlay->GetElement(TangentTri[j]) : FVector3f(FVector::ForwardVector); const FVector3f TriVertBiTangent = (BiTangentOverlay->IsElement(BiTangentTri[j])) ? BiTangentOverlay->GetElement(BiTangentTri[j]) : FVector3f(FVector::RightVector); // infer sign float BiTangentSign = VectorUtil::BitangentSign(TriVertNormal, TriVertTangent, TriVertBiTangent); // set the tangent space Builder.SetInstanceTangentSpace(CornerInstanceID, (FVector)TriVertNormal, (FVector)TriVertTangent, BiTangentSign); } }; } else if (NormalOverlay != nullptr) // create function that just sets the normals { TangetSpaceInstanceSetter = [NormalOverlay, &Builder](int TriID, FVertexInstanceID TriVertInstances[3]) { FIndex3i NormalTri = NormalOverlay->GetTriangle(TriID); for (int32 j = 0; j < 3; ++j) { FVertexInstanceID CornerInstanceID = TriVertInstances[j]; FVector TriVertNormal = (NormalOverlay->IsElement(NormalTri[j])) ? (FVector)NormalOverlay->GetElement(NormalTri[j]) : FVector::UpVector; Builder.SetInstanceNormal(CornerInstanceID, TriVertNormal); } }; } else // dummy function that does nothing { TangetSpaceInstanceSetter = [](int TriID, FVertexInstanceID TriVertInstances[3]){ return;}; } TFunction ColorInstanceSetter; if (bCopyInstanceColors) { ColorInstanceSetter = [this, ColorOverlay, &Builder](int TriID, FVertexInstanceID TriVertInstances[3]) { FIndex3i ColorTri = ColorOverlay->GetTriangle(TriID); for (int32 j = 0; j < 3; ++j) { FVector4f DstColor(1,1,1,1); const FVertexInstanceID CornerInstanceID = TriVertInstances[j]; if (ColorOverlay->IsElement(ColorTri[j])) { FVector4f TriVertColor4 = ColorOverlay->GetElement(ColorTri[j]); ApplyVertexColorTransform(TriVertColor4); DstColor = FVector4f(TriVertColor4.X, TriVertColor4.Y, TriVertColor4.Z, TriVertColor4.W); } Builder.SetInstanceColor(CornerInstanceID, DstColor); } }; } else { ColorInstanceSetter = [](int TriID, FVertexInstanceID TriVertInstances[3]) { return; }; } // need to know max material index value so we can reserve groups in MeshDescription int32 MaxPolygonGroupID = 0; if (MaterialIDAttrib) { for (int TriID : MeshIn->TriangleIndicesItr()) { int32 MaterialID; MaterialIDAttrib->GetValue(TriID, &MaterialID); if (MaterialIDToPolygroupIDMap.IsValidIndex(MaterialID)) { MaterialID = MaterialIDToPolygroupIDMap[MaterialID]; } MaxPolygonGroupID = FMath::Max(MaterialID, MaxPolygonGroupID); } if (MaxPolygonGroupID == 0) { MaterialIDAttrib = nullptr; } else { for (int k = 0; k < MaxPolygonGroupID; ++k) { Builder.AppendPolygonGroup(); } } } TArray UVTris; UVTris.SetNum(NumUVLayers); TArray IndexToTriangleIDMap; IndexToTriangleIDMap.SetNum(MeshIn->MaxTriangleID()); for (int TriID : MeshIn->TriangleIndicesItr()) { FIndex3i Triangle = MeshIn->GetTriangle(TriID); // create new vtx instances for each triangle FVertexInstanceID TriVertInstances[3]; for (int32 j = 0; j < 3; ++j) { const FVertexID TriVertex = MapV[Triangle[j]]; TriVertInstances[j] = Builder.AppendInstance(TriVertex); } // transfer material index to MeshDescription polygon group (by convention) FPolygonGroupID UsePolygonGroupID = ZeroPolygonGroupID; if (MaterialIDAttrib) { int32 MaterialID; MaterialIDAttrib->GetValue(TriID, &MaterialID); if (MaterialIDToPolygroupIDMap.IsValidIndex(MaterialID)) { MaterialID = MaterialIDToPolygroupIDMap[MaterialID]; } UsePolygonGroupID = FPolygonGroupID(MaterialID); } // add the triangle to MeshDescription FTriangleID NewTriangleID = Builder.AppendTriangle(TriVertInstances[0], TriVertInstances[1], TriVertInstances[2], UsePolygonGroupID); IndexToTriangleIDMap[TriID] = NewTriangleID; // transfer UVs. Note the Builder sets both the shared and per-instance UVs from this for (int32 k = 0; k < NumUVLayers; ++k) { FUVID UVIDs[3] = {FUVID(-1), FUVID(-1), FUVID(-1)}; // add zero uvs for unset triangles. if (!UVLayers[k]->IsSetTriangle(TriID)) { if (UnsetUVIDs[k].GetValue() == INDEX_NONE) { UnsetUVIDs[k] = Builder.AppendUV(FVector2D::ZeroVector, k); } for (int32 j = 0; j < 3; ++j) { UVIDs[j] = UnsetUVIDs[k]; } } else { const TArray& MapUV = MapUVArray[k]; // triangle of UV element ids from dynamic mesh. references values already stored in MeshDescription. FIndex3i UVTri = UVLayers[k]->GetTriangle(TriID); // translate to MeshDescription Ids for (int32 j = 0; j < 3; ++j) { UVIDs[j] = MapUV[ UVTri[j] ]; } } // append the UV triangle - builder takes care of the rest Builder.AppendUVTriangle(NewTriangleID, UVIDs[0], UVIDs[1], UVIDs[2], k); } // transfer tangent space. // NB: MeshDescription doesn't store and explicit bitangent, so this conversion isn't perfect. // NB: only per-instance normals , tangents, bitangent sign are supported in MeshDescription at this time // NB: will need to be updated to follow the pattern used in UVs above when MeshDescription supports shared tangent space elements. TangetSpaceInstanceSetter(TriID, TriVertInstances); // transfer the color overlay to per-instance colors. // NB: will need to be updated to follow the pattern used in UVs above if MeshDescription supports shared color elements ColorInstanceSetter(TriID, TriVertInstances); if (bCopyGroupToPolyGroup) { Builder.SetPolyGroupID(NewTriangleID, MeshIn->GetTriangleGroup(TriID)); } } // convert polygroup layers ConvertPolygroupLayers(MeshIn, MeshOut, IndexToTriangleIDMap); // convert weight map layers ConvertWeightLayers(MeshIn, MeshOut, MapV); // Convert all attached skin weights, if we're converting a mesh description that originated from // a USkeletalMesh. if (bHasSkinWeightsAttributes) { for (const TTuple>& AttributeInfo: MeshIn->Attributes()->GetSkinWeightsAttributes()) { FName ProfileName = AttributeInfo.Key; FSkinWeightsVertexAttributesRef& VertexBoneWeights = VertexBoneWeightsMap[ProfileName]; const FDynamicMeshVertexSkinWeightsAttribute *MeshSkinWeights = AttributeInfo.Value.Get(); for (int32 VertexIndex = 0; VertexIndex < MapV.Num(); VertexIndex++) { if (const FVertexID VertexID = MapV[VertexIndex]; VertexID != INDEX_NONE) { UE::AnimationCore::FBoneWeights BW; MeshSkinWeights->GetValue(VertexIndex, BW); VertexBoneWeights.Set(VertexID, BW); } } } } Builder.ResumeMeshDescriptionIndexing(); } void FDynamicMeshToMeshDescription::ConvertPolygroupLayers(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, const TArray& IndexToTriangleIDMap) { if (MeshIn->Attributes() == nullptr) return; TAttributesSet& TriAttribsSet = MeshOut.TriangleAttributes(); TSet UniqueNames; int32 UniqueIDGenerator = 0; for (int32 li = 0; li < MeshIn->Attributes()->NumPolygroupLayers(); ++li) { const FDynamicMeshPolygroupAttribute* Polygroups = MeshIn->Attributes()->GetPolygroupLayer(li); FName LayerName = Polygroups->GetName(); while (LayerName == NAME_None || UniqueNames.Contains(LayerName)) { FString BaseName = (Polygroups->GetName() == NAME_None) ? TEXT("Groups") : LayerName.ToString(); LayerName = FName( FString::Printf(TEXT("%s_%d"), *BaseName, UniqueIDGenerator++) ); } UniqueNames.Add(LayerName); // Find existing attribute with the same name. If not found, create a new one. TTriangleAttributesRef Attribute; if (TriAttribsSet.HasAttribute(LayerName)) { Attribute = TriAttribsSet.GetAttributesRef(LayerName); } else { TriAttribsSet.RegisterAttribute(LayerName, 1, 0, EMeshAttributeFlags::AutoGenerated); Attribute = TriAttribsSet.GetAttributesRef(LayerName); } if (ensure(Attribute.IsValid())) { for (int32 tid : MeshIn->TriangleIndicesItr()) { FTriangleID TriangleID = IndexToTriangleIDMap[tid]; int32 GroupID = Polygroups->GetValue(tid); Attribute.Set(TriangleID, GroupID); } } else { UE_LOG(LogTemp, Warning, TEXT("FDynamicMeshToMeshDescription::ConvertPolygroupLayers - could not create attribute named %s"), *LayerName.ToString()); } } } void FDynamicMeshToMeshDescription::ConvertWeightLayers(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut, const TArray& IndexToVertexIDMap) { if (MeshIn->Attributes() == nullptr) return; TAttributesSet& VertexAttribsSet = MeshOut.VertexAttributes(); TSet UniqueNames; int32 UniqueIDGenerator = 0; for (int32 LayerIndex = 0; LayerIndex < MeshIn->Attributes()->NumWeightLayers(); ++LayerIndex) { const FDynamicMeshWeightAttribute* Weights = MeshIn->Attributes()->GetWeightLayer(LayerIndex); FName LayerName = Weights->GetName(); while (LayerName == NAME_None || UniqueNames.Contains(LayerName)) { FString BaseName = (Weights->GetName() == NAME_None) ? TEXT("Weights") : LayerName.ToString(); LayerName = FName( FString::Printf(TEXT("%s_%d"), *BaseName, UniqueIDGenerator++) ); } UniqueNames.Add(LayerName); // Find existing attribute with the same name. If not found, create a new one. TVertexAttributesRef Attribute; if (VertexAttribsSet.HasAttribute(LayerName)) { Attribute = VertexAttribsSet.GetAttributesRef(LayerName); } else { VertexAttribsSet.RegisterAttribute(LayerName, 1, 0, EMeshAttributeFlags::AutoGenerated); Attribute = VertexAttribsSet.GetAttributesRef(LayerName); } if (ensure(Attribute.IsValid())) { for (int32 InVertexID : MeshIn->VertexIndicesItr()) { FVertexID OutVertexID = IndexToVertexIDMap[InVertexID]; float Value; Weights->GetValue(InVertexID, &Value); Attribute.Set(OutVertexID, Value); } } else { UE_LOG(LogTemp, Warning, TEXT("FDynamicMeshToMeshDescription::ConvertWeightLayers - could not create attribute named %s"), *LayerName.ToString()); } } } void FDynamicMeshToMeshDescription::ApplyVertexColorTransform(FVector4f& Color) const { // There is inconsistency in how vertex colors are intended to be consumed in // our shaders. Some shaders consume it as linear (ex. MeshPaint), others as SRGB which // manually convert to linear in the shader. // // All StaticMeshes store vertex colors as an 8-bit FColor. In order to ensure a good // distribution of float values across the 8-bit range, the StaticMesh build always // encodes FColors as SRGB. // // Until there is some defined gamma space convention for vertex colors in our shaders, // we provide this option to pre-transform our linear float colors with an SRGBToLinear // conversion to counteract the StaticMesh build LinearToSRGB conversion. This is how // MeshPaint ensures linear vertex colors in the shaders. if (ConversionOptions.bTransformVtxColorsSRGBToLinear) { LinearColors::SRGBToLinear(Color); } }