// Copyright Epic Games, Inc. All Rights Reserved. #if WITH_EDITOR #include "ChaosClothAsset/ClothPatternToDynamicMesh.h" #include "ChaosClothAsset/ClothPatternToDynamicMeshMappingSupport.h" #include "ChaosClothAsset/ClothAsset.h" #include "ChaosClothAsset/ClothComponent.h" #include "ChaosClothAsset/CollectionClothFacade.h" #include "ChaosClothAsset/ClothCollectionGroup.h" #include "DynamicMesh/MeshNormals.h" #include "Engine/SkeletalMesh.h" #include "SkeletalMeshAttributes.h" #include "ToDynamicMesh.h" namespace UE::Chaos::ClothAsset { // // Wrapper for accessing a Cloth Pattern. Implements the interface expected by TToDynamicMesh<>. // class FClothPatternWrapper { public: typedef int32 TriIDType; typedef int32 VertIDType; typedef int32 WedgeIDType; typedef int32 UVIDType; typedef int32 NormalIDType; typedef int32 ColorIDType; FClothPatternWrapper(const FCollectionClothConstFacade& ClothFacade, int32 PatternIndex, EClothPatternVertexType VertexDataType) : VertexDataType(VertexDataType), Cloth(ClothFacade) { NumTexCoords = 0; if (PatternIndex == INDEX_NONE) { // All patterns in one dynamic mesh switch (VertexDataType) { case EClothPatternVertexType::Render: { const int32 NumVertices = Cloth.GetNumRenderVertices(); VertIDs.SetNum(NumVertices); TConstArrayView> UVs = Cloth.GetRenderUVs(); for (int32 VtxIndex = 0; VtxIndex < NumVertices; ++VtxIndex) { VertIDs[VtxIndex] = VtxIndex; NumTexCoords = FMath::Max(NumTexCoords, UVs[VtxIndex].Num()); } NormalIDs = VertIDs; const TConstArrayView Indices = Cloth.GetRenderIndices(); const int32 NumFaces = Cloth.GetNumRenderFaces(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE) { TriIDs.Add(TriIndex); } } } break; case EClothPatternVertexType::Sim2D: { const int32 NumVertices = Cloth.GetNumSimVertices2D(); const TConstArrayView SimVertex3DLookup = Cloth.GetSimVertex3DLookup(); VertIDs.Reserve(NumVertices); NormalIDs.Reserve(NumVertices); for (int32 VtxIndex = 0; VtxIndex < NumVertices; ++VtxIndex) { if (SimVertex3DLookup[VtxIndex] != INDEX_NONE) { VertIDs.Add(VtxIndex); NormalIDs.Add(SimVertex3DLookup[VtxIndex]); } } const TConstArrayView Indices = Cloth.GetSimIndices2D(); const int32 NumFaces = Cloth.GetNumSimFaces(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][0]] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][1]] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][2]] != INDEX_NONE) { TriIDs.Add(TriIndex); } } } break; case EClothPatternVertexType::Sim3D: { const int32 NumVertices = Cloth.GetNumSimVertices3D(); VertIDs.SetNum(NumVertices); for (int32 VtxIndex = 0; VtxIndex < NumVertices; ++VtxIndex) { VertIDs[VtxIndex] = VtxIndex; } NormalIDs = VertIDs; const TConstArrayView Indices = Cloth.GetSimIndices3D(); const int32 NumFaces = Cloth.GetNumSimFaces(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE) { TriIDs.Add(TriIndex); } } NumTexCoords = 1; // SimPositions2D will be stored as UVs }break; default: checkNoEntry(); } } else { switch (VertexDataType) { case EClothPatternVertexType::Render: { FCollectionClothRenderPatternConstFacade Pattern = Cloth.GetRenderPattern(PatternIndex); const int32 NumVertices = Pattern.GetNumRenderVertices(); const int32 VertexOffset = Pattern.GetRenderVerticesOffset(); VertIDs.SetNum(NumVertices); TConstArrayView> UVs = Pattern.GetRenderUVs(); for (int32 VtxIndex = 0; VtxIndex < NumVertices; ++VtxIndex) { VertIDs[VtxIndex] = VtxIndex + VertexOffset; NumTexCoords = FMath::Max(NumTexCoords, UVs[VtxIndex].Num()); } NormalIDs = VertIDs; const TConstArrayView Indices = Pattern.GetRenderIndices(); const int32 NumFaces = Pattern.GetNumRenderFaces(); const int32 FaceOffset = Pattern.GetRenderFacesOffset(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE) { TriIDs.Add(TriIndex + FaceOffset); } } } break; case EClothPatternVertexType::Sim2D: { FCollectionClothSimPatternConstFacade Pattern = Cloth.GetSimPattern(PatternIndex); const int32 NumVertices = Pattern.GetNumSimVertices2D(); const TConstArrayView SimVertex3DLookup = Pattern.GetSimVertex3DLookup(); const int32 VertexOffset = Pattern.GetSimVertices2DOffset(); VertIDs.Reserve(NumVertices); NormalIDs.Reserve(NumVertices); for (int32 VtxIndex = 0; VtxIndex < NumVertices; ++VtxIndex) { if (SimVertex3DLookup[VtxIndex] != INDEX_NONE) { VertIDs.Add(VtxIndex + VertexOffset); NormalIDs.Add(SimVertex3DLookup[VtxIndex]); } } const TConstArrayView Indices = Pattern.GetSimIndices2D(); const int32 NumFaces = Pattern.GetNumSimFaces(); const int32 FaceOffset = Pattern.GetSimFacesOffset(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][0] - VertexOffset] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][1] - VertexOffset] != INDEX_NONE && SimVertex3DLookup[Indices[TriIndex][2] - VertexOffset] != INDEX_NONE) { TriIDs.Add(TriIndex + FaceOffset); } } } break; case EClothPatternVertexType::Sim3D: { FCollectionClothSimPatternConstFacade Pattern = Cloth.GetSimPattern(PatternIndex); VertIDs = Pattern.GetSimVertex3DLookup(); NormalIDs = Pattern.GetSimVertex3DLookup(); const TConstArrayView Indices = Pattern.GetSimIndices3D(); const int32 NumFaces = Pattern.GetNumSimFaces(); const int32 FaceOffset = Pattern.GetSimFacesOffset(); TriIDs.Reserve(NumFaces); for (int32 TriIndex = 0; TriIndex < NumFaces; ++TriIndex) { if (Indices[TriIndex][0] != INDEX_NONE && Indices[TriIndex][1] != INDEX_NONE && Indices[TriIndex][2] != INDEX_NONE) { TriIDs.Add(TriIndex + FaceOffset); } } NumTexCoords = 1; // SimPositions2D will be stored as UVs } break; default: checkNoEntry(); } } // // Weight map layers precomputation // if(VertexDataType != EClothPatternVertexType::Render) { WeightMapNames = Cloth.GetWeightMapNames(); } else { WeightMapNames = Cloth.GetUserDefinedAttributeNames(ClothCollectionGroup::RenderVertices); } // Set the reference skeleton if available const FSoftObjectPath& SkeletalMeshPathName = ClothFacade.GetSkeletalMeshSoftObjectPathName(); const USkeletalMesh* const SkeletalMesh = Cast(SkeletalMeshPathName.TryLoad()); RefSkeleton = SkeletalMesh ? &SkeletalMesh->GetRefSkeleton() : nullptr; } int32 NumTris() const { return TriIDs.Num(); } int32 NumVerts() const { return VertIDs.Num(); } int32 NumUVLayers() const { return NumTexCoords; } int32 NumWeightMapLayers() const { return WeightMapNames.Num(); } FName GetWeightMapName(int32 LayerIndex) const { return WeightMapNames[LayerIndex]; } float GetVertexWeight(int32 LayerIndex, VertIDType VertexIndex) const { checkSlow(LayerIndex < WeightMapNames.Num()); // All weights live on 3D indices. const int32 VertexWeightIndex = (VertexDataType == EClothPatternVertexType::Sim2D) ? Cloth.GetSimVertex3DLookup()[VertexIndex] : VertexIndex; TConstArrayView WeightMap; if (VertexDataType != EClothPatternVertexType::Render) { WeightMap = Cloth.GetWeightMap(WeightMapNames[LayerIndex]); } else { WeightMap = Cloth.GetUserDefinedAttribute(WeightMapNames[LayerIndex], ClothCollectionGroup::RenderVertices); } checkSlow(VertexWeightIndex < WeightMap.Num()); if (VertexWeightIndex < WeightMap.Num()) { return WeightMap[VertexWeightIndex]; } else { return 0; } } // --"Vertex Buffer" info const TArray& GetVertIDs() const { return VertIDs; } FVector3d GetPosition(VertIDType VtxID) const { switch (VertexDataType) { case EClothPatternVertexType::Render: { const TConstArrayView RenderPositions = Cloth.GetRenderPosition(); return FVector3d(RenderPositions[VtxID]); } case EClothPatternVertexType::Sim2D: { const TConstArrayView SimPositions = Cloth.GetSimPosition2D(); const FVector2f& Pos = SimPositions[VtxID]; return FVector3d(Pos[0], Pos[1], 0.0); } case EClothPatternVertexType::Sim3D: { const TConstArrayView SimPositions = Cloth.GetSimPosition3D(); const FVector3f& Pos = SimPositions[VtxID]; return FVector3d(Pos[0], Pos[1], Pos[2]); } default: checkNoEntry(); return FVector3d(); }; } // --"Index Buffer" info const TArray& GetTriIDs() const { return TriIDs; } bool GetTri(TriIDType TriID, VertIDType& VID0, VertIDType& VID1, VertIDType& VID2) const { FIntVector Face; switch (VertexDataType) { case EClothPatternVertexType::Render: Face = Cloth.GetRenderIndices()[TriID]; break; case EClothPatternVertexType::Sim2D: Face = Cloth.GetSimIndices2D()[TriID]; break; case EClothPatternVertexType::Sim3D: Face = Cloth.GetSimIndices3D()[TriID]; break; default: checkNoEntry(); } VID0 = Face[0]; VID1 = Face[1]; VID2 = Face[2]; return true; } bool HasNormals() const { return true; } bool HasTangents() const { return VertexDataType == EClothPatternVertexType::Render; } bool HasBiTangents() const { return VertexDataType == EClothPatternVertexType::Render; } bool HasColors() const { return VertexDataType == EClothPatternVertexType::Render; } // -- Access to per-wedge attributes -- // void GetWedgeIDs(const TriIDType& TriID, WedgeIDType& WID0, WedgeIDType& WID1, WedgeIDType& WID2) const { check(VertexDataType == EClothPatternVertexType::Sim3D); const FIntVector& Face = Cloth.GetSimIndices2D()[TriID]; WID0 = Face[0]; WID1 = Face[1]; WID2 = Face[2]; } FVector2f GetWedgeUV(int32 UVLayerIndex, WedgeIDType WID) const { check(VertexDataType == EClothPatternVertexType::Sim3D); return Cloth.GetSimPosition2D()[WID]; } FVector3f GetWedgeNormal(WedgeIDType WID) const { checkf(false, TEXT("FClothPatternWrapper: ClothPatterns are not expected to use Wedges")); return FVector3f(); } FVector3f GetWedgeTangent(WedgeIDType WID) const { checkf(false, TEXT("FClothPatternWrapper: ClothPatterns are not expected to use Wedges")); return FVector3f(); } FVector3f GetWedgeBiTangent(WedgeIDType WID) const { checkf(false, TEXT("FClothPatternWrapper: ClothPatterns are not expected to use Wedges")); return FVector3f(); } FVector4f GetWedgeColor(WedgeIDType WID) const { checkf(false, TEXT("FClothPatternWrapper: ClothPatterns are not expected to use Wedges")); return FVector4f(); } // -- End of per-wedge attribute access -- // int32 GetMaterialIndex(TriIDType TriID) const { checkf(false, TEXT("FClothPatternWrapper: Material indexing should be accomplished by passing a function into Convert")); return 0; } int32 NumSkinWeightAttributes() const { return 1; } UE::AnimationCore::FBoneWeights GetVertexSkinWeight(int32 SkinWeightAttributeIndex, VertIDType InVertexID) const { using namespace UE::AnimationCore; checkfSlow(SkinWeightAttributeIndex == 0, TEXT("Cloth assets should only have one skin weight profile")); const bool bGetRenderMeshData = (VertexDataType == EClothPatternVertexType::Render); const TConstArrayView> BoneIndices = bGetRenderMeshData ? Cloth.GetRenderBoneIndices() : Cloth.GetSimBoneIndices(); const TConstArrayView> BoneWeights = bGetRenderMeshData ? Cloth.GetRenderBoneWeights() : Cloth.GetSimBoneWeights(); // All weights live on 3D indices. Need to convert 2D index to 3D index. const VertIDType VertexID = (VertexDataType == EClothPatternVertexType::Sim2D) ? Cloth.GetSimVertex3DLookup()[InVertexID] : InVertexID; if (ensure(VertexID >= 0 && VertexID < BoneIndices.Num())) { const TArray Indices = BoneIndices[VertexID]; const TArray Weights = BoneWeights[VertexID]; const int32 NumInfluences = Indices.Num(); check(Weights.Num() == NumInfluences); TArray BoneWeightArray; BoneWeightArray.SetNumUninitialized(NumInfluences); for (int32 Idx = 0; Idx < NumInfluences; ++Idx) { BoneWeightArray[Idx] = FBoneWeight(static_cast(Indices[Idx]), Weights[Idx]); } return FBoneWeights::Create(BoneWeightArray, FBoneWeightsSettings()); } else { return FBoneWeights(); } } FName GetSkinWeightAttributeName(int32 SkinWeightAttributeIndex) const { checkfSlow(SkinWeightAttributeIndex == 0, TEXT("Cloth assets should only have one skin weight profile")); return FSkeletalMeshAttributes::DefaultSkinWeightProfileName; } int32 GetNumBones() const { return RefSkeleton ? RefSkeleton->GetRawBoneNum() : 0; } FName GetBoneName(int32 BoneIdx) const { if (ensure(BoneIdx >= 0 && BoneIdx < GetNumBones()) && RefSkeleton) { return RefSkeleton->GetRawRefBoneInfo()[BoneIdx].Name; } return NAME_None; } int32 GetBoneParentIndex(int32 BoneIdx) const { if (ensure(BoneIdx >= 0 && BoneIdx < GetNumBones()) && RefSkeleton) { return RefSkeleton->GetRawRefBoneInfo()[BoneIdx].ParentIndex; } return INDEX_NONE; } FTransform GetBonePose(int32 BoneIdx) const { if (ensure(BoneIdx >= 0 && BoneIdx < GetNumBones()) && RefSkeleton) { return RefSkeleton->GetRawRefBonePose()[BoneIdx]; } return FTransform::Identity; } FVector4f GetBoneColor(int32 BoneIdx) const { return FVector4f::One(); } const TArray& GetNormalIDs() const { return NormalIDs; } FVector3f GetNormal(NormalIDType ID) const { return (VertexDataType == EClothPatternVertexType::Render) ? Cloth.GetRenderNormal()[ID] : Cloth.GetSimNormal()[ID]; } bool GetNormalTri(const TriIDType& TriID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { if (VertexDataType == EClothPatternVertexType::Sim2D) { // All normal data lives on 3D indices. const FIntVector Face = Cloth.GetSimIndices3D()[TriID]; ID0 = Face[0]; ID1 = Face[1]; ID2 = Face[2]; return true; } else { return GetTri(TriID, ID0, ID1, ID2); } } const TArray& GetUVIDs(int32 LayerID) const { return (VertexDataType == EClothPatternVertexType::Render) ? NormalIDs : EmptyArray; } FVector2f GetUV(int32 LayerID, UVIDType UVID) const { checkf(VertexDataType == EClothPatternVertexType::Render, TEXT("Requested UVs from a Sim mesh")); const TConstArrayView VertexUVs = Cloth.GetRenderUVs()[UVID]; return VertexUVs.IsValidIndex(LayerID) ? VertexUVs[LayerID] : FVector2f(0.f); } bool GetUVTri(int32 LayerID, const TriIDType& TriID, UVIDType& ID0, UVIDType& ID1, UVIDType& ID2) const { return GetTri(TriID, ID0, ID1, ID2); } const TArray& GetTangentIDs() const { return (VertexDataType == EClothPatternVertexType::Render) ? NormalIDs : EmptyArray; } FVector3f GetTangent(NormalIDType ID) const { checkf(VertexDataType == EClothPatternVertexType::Render, TEXT("Requested Tangent from a Sim mesh")); return Cloth.GetRenderTangentU()[ID]; } bool GetTangentTri(const TriIDType& TriID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { return GetNormalTri(TriID, ID0, ID1, ID2); } const TArray& GetBiTangentIDs() const { return (VertexDataType == EClothPatternVertexType::Render) ? NormalIDs : EmptyArray; } FVector3f GetBiTangent(NormalIDType ID) const { checkf(VertexDataType == EClothPatternVertexType::Render, TEXT("Requested Bitangent from a Sim mesh")); return Cloth.GetRenderTangentV()[ID]; } bool GetBiTangentTri(const TriIDType& TriID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { return GetNormalTri(TriID, ID0, ID1, ID2); } const TArray& GetColorIDs() const { return (VertexDataType == EClothPatternVertexType::Render) ? NormalIDs : EmptyArray; } FVector4f GetColor(ColorIDType VID) const { checkf(VertexDataType == EClothPatternVertexType::Render, TEXT("Requested color from a Sim mesh")); return Cloth.GetRenderColor()[VID]; } bool GetColorTri(const TriIDType& TriID, ColorIDType& ID0, ColorIDType& ID1, ColorIDType& ID2) const { return GetNormalTri(TriID, ID0, ID1, ID2); } private: const EClothPatternVertexType VertexDataType; const FCollectionClothConstFacade& Cloth; TArray TriIDs; // indices into Indices TArray VertIDs; // indices into Positions2D or Positions3D TArray NormalIDs; // indices into Normals (and all per vertex data other than positions) int32 NumTexCoords; TArray WeightMapNames; TArray EmptyArray; const FReferenceSkeleton* RefSkeleton = nullptr; }; void FClothPatternToDynamicMesh::Convert( const TSharedRef ClothCollection, int32 PatternIndex, EClothPatternVertexType VertexDataType, UE::Geometry::FDynamicMesh3& MeshOut, bool bDisableAttributes, int32 MaterialOffset) { const FCollectionClothConstFacade ClothFacade(ClothCollection); // Actual conversion UE::Geometry::TToDynamicMesh PatternToDynamicMesh; FClothPatternWrapper PatternWrapper(ClothFacade, PatternIndex, VertexDataType); auto TriangleToGroupFunction = [](FClothPatternWrapper::TriIDType) { return 0; }; if (bDisableAttributes) { PatternToDynamicMesh.ConvertWOAttributes(MeshOut, PatternWrapper, TriangleToGroupFunction); } else { constexpr bool bCopyTangents = false; const bool bIsRenderType = VertexDataType == EClothPatternVertexType::Render; auto TriangleToMaterialFunction = [PatternIndex, bIsRenderType, &ClothFacade](FClothPatternWrapper::TriIDType TriID)->int32 { if (bIsRenderType) { if (PatternIndex != INDEX_NONE) { return PatternIndex; } const int32 FoundPattern = ClothFacade.FindRenderPatternByFaceIndex(TriID); check(FoundPattern != INDEX_NONE); return FoundPattern; } // Sim meshes will have a default material with MaterialID zero applied return 0; }; PatternToDynamicMesh.Convert(MeshOut, PatternWrapper, TriangleToGroupFunction, TriangleToMaterialFunction, bCopyTangents); } // Add non-manifold mapping data if DynamicMesh indices don't match orig indices const int32 NumVertices = VertexDataType == EClothPatternVertexType::Render ? ClothFacade.GetNumRenderVertices() : VertexDataType == EClothPatternVertexType::Sim2D ? ClothFacade.GetNumSimVertices2D() : ClothFacade.GetNumSimVertices3D(); bool bVertexIndicesMatch = false; if (PatternToDynamicMesh.ToSrcVertIDMap.Num() == NumVertices) { bVertexIndicesMatch = true; for (int32 VertId = 0; VertId < NumVertices; ++VertId) { if (PatternToDynamicMesh.ToSrcVertIDMap[VertId] != VertId) { bVertexIndicesMatch = false; break; } } } if (!bVertexIndicesMatch) { MeshOut.EnableAttributes(); FClothPatternToDynamicMeshMappingSupport::AttachVertexMappingData(PatternToDynamicMesh.ToSrcVertIDMap, MeshOut); } const int32 NumTriangles = VertexDataType == EClothPatternVertexType::Render ? ClothFacade.GetNumRenderFaces() : ClothFacade.GetNumSimFaces(); bool bTriangleIndicesMatch = false; if (PatternToDynamicMesh.ToSrcTriIDMap.Num() == NumTriangles) { bTriangleIndicesMatch = true; for (int32 TriId = 0; TriId < NumTriangles; ++TriId) { if (PatternToDynamicMesh.ToSrcTriIDMap[TriId] != TriId) { bTriangleIndicesMatch = false; break; } } } if (!bTriangleIndicesMatch) { MeshOut.EnableAttributes(); FClothPatternToDynamicMeshMappingSupport::AttachTriangleMappingData(PatternToDynamicMesh.ToSrcTriIDMap, MeshOut); } // Fix up any triangles without valid material IDs and apply material offset if (VertexDataType == EClothPatternVertexType::Render && MaterialOffset != INDEX_NONE) { const TConstArrayView MaterialPaths = ClothFacade.GetRenderMaterialSoftObjectPathName(); int32 DefaultMaterialID = 0; for (const int32 TriID : MeshOut.TriangleIndicesItr()) { int32 MaterialID; MeshOut.Attributes()->GetMaterialID()->GetValue(TriID, &MaterialID); //const int32 MaterialID = MeshOut.Attributes()->GetMaterialID()->GetValue(TriID); if (!MaterialPaths.IsValidIndex(MaterialID)) { MeshOut.Attributes()->GetMaterialID()->SetValue(TriID, DefaultMaterialID); } else { MeshOut.Attributes()->GetMaterialID()->SetValue(TriID, MaterialID + MaterialOffset); } } } } void FClothPatternToDynamicMesh::Convert( const UChaosClothAsset* ClothAssetMeshIn, int32 LODIndex, int32 PatternIndex, EClothPatternVertexType VertexDataType, UE::Geometry::FDynamicMesh3& MeshOut, int32 MaterialOffset) { const TArray>& ClothCollections = ClothAssetMeshIn->GetClothCollections(); check(ClothCollections.IsValidIndex(LODIndex)); constexpr bool bDisableAttributes = false; Convert(ClothCollections[LODIndex], PatternIndex, VertexDataType, MeshOut, bDisableAttributes, MaterialOffset); } } // namespace UE::Chaos::ClothAsset #else namespace UE::Chaos::ClothAsset { void FClothPatternToDynamicMesh::Convert( const TSharedRef ClothCollection, int32 PatternIndex, EClothPatternVertexType VertexDataType, UE::Geometry::FDynamicMesh3& MeshOut, bool bDisableAttributes, int32 MaterialOffset) { // Conversion only supported with editor. check(0); } void FClothPatternToDynamicMesh::Convert( const UChaosClothAsset* ClothAssetMeshIn, FDynamicMesh3& MeshOut, int32 LODIndex, int32 PatternIndex, int32 MaterialOffset) { // Conversion only supported with editor. check(0); } } // namespace UE::Chaos::ClothAsset #endif // end with editor