Files
UnrealEngine/Engine/Plugins/ChaosClothAssetEditor/Source/ChaosClothAssetDataflowNodes/Private/ChaosClothAsset/ClothDataflowTools.cpp
2025-05-18 13:04:45 +08:00

896 lines
32 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChaosClothAsset/ClothDataflowTools.h"
#include "ChaosClothAsset/ClothGeometryTools.h"
#include "ChaosClothAsset/CollectionClothFacade.h"
#include "Dataflow/DataflowNode.h"
#include "DynamicMesh/NonManifoldMappingSupport.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Modules/ModuleManager.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "MeshUtilities.h"
#include "PropertyHandle.h"
#include "ReferenceSkeleton.h"
#include "SkeletalMeshAttributes.h"
#include "ToDynamicMesh.h"
DEFINE_LOG_CATEGORY(LogChaosClothAssetDataflowNodes);
namespace UE::Chaos::ClothAsset
{
namespace Private
{
//
// Wrapper for accessing a SkelMeshSection. Implements the interface expected by TToDynamicMesh<>.
// This will weld all vertices which are the same.
//
template<bool bHasTangents = false, bool bHasBiTangents = false, bool bHasColors = false>
struct FSkelMeshSectionWrapper
{
typedef int32 TriIDType;
typedef int32 VertIDType;
typedef int32 WedgeIDType;
typedef int32 UVIDType;
typedef int32 NormalIDType;
typedef int32 ColorIDType;
FSkelMeshSectionWrapper(const FSkeletalMeshLODModel& SkeletalMeshModel, const int32 SectionIndex, bool bInHasNormals)
: bHasNormals(bInHasNormals)
, SourceSection(SkeletalMeshModel.Sections[SectionIndex])
, IndexBuffer(SkeletalMeshModel.IndexBuffer.GetData() + SourceSection.BaseIndex, SourceSection.NumTriangles * 3)
{
const int32 NumVerts = SourceSection.SoftVertices.Num();
const int32 NumTriangles = SourceSection.NumTriangles;
// We need to weld the mesh verts to get rid of duplicates (happens for smoothing groups)
TArray<FVector> UniqueVerts;
OriginalToMerged.AddDefaulted(NumVerts);
constexpr float ThreshSq = UE_THRESH_POINTS_ARE_SAME * UE_THRESH_POINTS_ARE_SAME;
for (int32 VertIndex = 0; VertIndex < NumVerts; ++VertIndex)
{
const FSoftSkinVertex& SourceVert = SourceSection.SoftVertices[VertIndex];
bool bUnique = true;
int32 RemapIndex = INDEX_NONE;
const int32 NumUniqueVerts = UniqueVerts.Num();
for (int32 UniqueVertIndex = 0; UniqueVertIndex < NumUniqueVerts; ++UniqueVertIndex)
{
FVector& UniqueVert = UniqueVerts[UniqueVertIndex];
if ((UniqueVert - (FVector)SourceVert.Position).SizeSquared() <= ThreshSq)
{
// Not unique
bUnique = false;
RemapIndex = UniqueVertIndex;
break;
}
}
if (bUnique)
{
// Unique
UniqueVerts.Add((FVector)SourceVert.Position);
OriginalIndexes.Add(VertIndex);
OriginalToMerged[VertIndex] = VertIndex;
}
else
{
OriginalToMerged[VertIndex] = OriginalIndexes[RemapIndex];
}
}
TriIDs.SetNum(NumTriangles);
for (int32 Tri = 0; Tri < NumTriangles; ++Tri)
{
TriIDs[Tri] = Tri;
}
}
int32 NumTris() const
{
return TriIDs.Num();
}
int32 NumVerts() const
{
return OriginalIndexes.Num();
}
int32 NumUVLayers() const
{
return MAX_TEXCOORDS;
}
// --"Vertex Buffer" info
const TArray<int32>& GetVertIDs() const
{
return OriginalIndexes;
}
const FVector3d GetPosition(const VertIDType VtxID) const
{
return (FVector3d)SourceSection.SoftVertices[VtxID].Position;
}
// --"Index Buffer" info
const TArray<int32>& GetTriIDs() const
{
return TriIDs;
}
// return false if this TriID is not contained in mesh.
bool GetTri(const TriIDType TriID, VertIDType& VID0, VertIDType& VID1, VertIDType& VID2) const
{
if (TriID >= 0 && TriID < (int32)SourceSection.NumTriangles)
{
VID0 = OriginalToMerged[IndexBuffer[3 * TriID + 0] - SourceSection.BaseVertexIndex];
VID1 = OriginalToMerged[IndexBuffer[3 * TriID + 1] - SourceSection.BaseVertexIndex];
VID2 = OriginalToMerged[IndexBuffer[3 * TriID + 2] - SourceSection.BaseVertexIndex];
return true;
}
return false;
}
bool HasNormals() const
{
return bHasNormals;
}
bool HasTangents() const
{
return bHasTangents;
}
bool HasBiTangents() const
{
return bHasBiTangents;
}
bool HasColors() const
{
return bHasColors;
}
// Each triangle corner is a wedge. This will lookup into original unwelded soft verts
void GetWedgeIDs(const TriIDType& TriID, WedgeIDType& WID0, WedgeIDType& WID1, WedgeIDType& WID2) const
{
WID0 = IndexBuffer[3 * TriID + 0] - SourceSection.BaseVertexIndex;
WID1 = IndexBuffer[3 * TriID + 1] - SourceSection.BaseVertexIndex;
WID2 = IndexBuffer[3 * TriID + 2] - SourceSection.BaseVertexIndex;
}
// attribute access per-wedge
// NB: ToDynamicMesh will attempt to weld identical attributes that are associated with the same vertex
FVector2f GetWedgeUV(int32 UVLayerIndex, WedgeIDType WID) const
{
check(UVLayerIndex < MAX_TEXCOORDS);
return SourceSection.SoftVertices[WID].UVs[UVLayerIndex];
}
FVector3f GetWedgeNormal(WedgeIDType WID) const
{
return SourceSection.SoftVertices[WID].TangentZ;
}
FVector3f GetWedgeTangent(WedgeIDType WID) const
{
return SourceSection.SoftVertices[WID].TangentX;
}
FVector3f GetWedgeBiTangent(WedgeIDType WID) const
{
return SourceSection.SoftVertices[WID].TangentY;
}
FVector4f GetWedgeColor(WedgeIDType WID) const
{
return FLinearColor(SourceSection.SoftVertices[WID].Color);
}
// attribute access that exploits shared attributes.
// each group of shared attributes presents itself as a mesh with its own attribute vertex buffer.
// NB: If the mesh has no shared Attr attributes, then Get{Attr}IDs() should return an empty array.
// NB: Get{Attr}Tri() functions should return false if the triangle is not set in the attribute mesh.
const TArray<UVIDType>& GetUVIDs(int32 LayerID) const
{
return EmptyArray;
}
FVector2f GetUV(int32 LayerID, UVIDType UVID) const
{
check(false);
return FVector2f();
}
bool GetUVTri(int32 LayerID, const TriIDType& TID, UVIDType& ID0, UVIDType& ID1, UVIDType& ID2) const
{
return false;
}
const TArray<NormalIDType>& GetNormalIDs() const
{
if (bHasNormals)
{
return OriginalIndexes;
}
else
{
return EmptyArray;
}
}
FVector3f GetNormal(NormalIDType ID) const
{
check(bHasNormals);
return SourceSection.SoftVertices[ID].TangentZ;
}
bool GetNormalTri(const TriIDType& TriID, NormalIDType& NID0, NormalIDType& NID1, NormalIDType& NID2) const
{
if (bHasNormals)
{
return GetTri(TriID, NID0, NID1, NID2);
}
return false;
}
const TArray<NormalIDType>& GetTangentIDs() const
{
return EmptyArray;
}
FVector3f GetTangent(NormalIDType ID) const
{
check(false);
return FVector3f();
}
bool GetTangentTri(const TriIDType& TID, NormalIDType& NID0, NormalIDType& NID1, NormalIDType& NID2) const
{
return false;
}
const TArray<NormalIDType>& GetBiTangentIDs() const
{
return EmptyArray;
}
FVector3f GetBiTangent(NormalIDType ID) const
{
check(false);
return FVector3f();
}
bool GetBiTangentTri(const TriIDType& TID, NormalIDType& NID0, NormalIDType& NID1, NormalIDType& NID2) const
{
return false;
}
const TArray<ColorIDType>& GetColorIDs() const
{
return EmptyArray;
}
FVector4f GetColor(ColorIDType ID) const
{
check(false);
return FVector4f();
}
bool GetColorTri(const TriIDType& TID, ColorIDType& NID0, ColorIDType& NID1, ColorIDType& NID2) const
{
return false;
}
// weight maps information
int32 NumWeightMapLayers() const
{
return 0;
}
float GetVertexWeight(int32 WeightMapIndex, int32 SrcVertID) const
{
check(false);
return 0.f;
}
FName GetWeightMapName(int32 WeightMapIndex) const
{
check(false);
return FName();
}
// skin weight attributes information
int32 NumSkinWeightAttributes() const
{
return 1;
}
UE::AnimationCore::FBoneWeights GetVertexSkinWeight(int32 SkinWeightAttributeIndex, VertIDType VtxID) const
{
using namespace UE::AnimationCore;
check(SkinWeightAttributeIndex == 0);
const int32 NumInfluences = SourceSection.MaxBoneInfluences;
const FSoftSkinVertex& SoftVertex = SourceSection.SoftVertices[VtxID];
TArray<FBoneWeight> BoneWeightArray;
BoneWeightArray.SetNumUninitialized(NumInfluences);
for (int32 Idx = 0; Idx < NumInfluences; ++Idx)
{
BoneWeightArray[Idx] = FBoneWeight(SourceSection.BoneMap[SoftVertex.InfluenceBones[Idx]], (float)SoftVertex.InfluenceWeights[Idx] * UE::AnimationCore::InvMaxRawBoneWeightFloat);
}
return FBoneWeights::Create(BoneWeightArray, FBoneWeightsSettings());
}
FName GetSkinWeightAttributeName(int32 SkinWeightAttributeIndex) const
{
checkfSlow(SkinWeightAttributeIndex == 0, TEXT("Cloth assets should only have one skin weight profile"));
return FSkeletalMeshAttributes::DefaultSkinWeightProfileName;
}
// bone attributes information
int32 GetNumBones() const
{
return 0;
}
FName GetBoneName(int32 BoneIdx) const
{
check(false);
return FName();
}
int32 GetBoneParentIndex(int32 BoneIdx) const
{
check(false);
return INDEX_NONE;
}
FTransform GetBonePose(int32 BoneIdx) const
{
check(false);
return FTransform();
}
FVector4f GetBoneColor(int32 BoneIdx) const
{
check(false);
return FLinearColor::White;
}
const bool bHasNormals;
const FSkelMeshSection& SourceSection;
const TConstArrayView<uint32> IndexBuffer;
TArray<int32> OriginalIndexes; // UniqueIndex -> OrigIndex
TArray<int32> OriginalToMerged; // OriginalIndex -> OriginalIndexes[UniqueVertIndex]
TArray<int32> TriIDs;
TArray<int32> EmptyArray;
};
} // Private
void FClothDataflowTools::AddRenderPatternFromSkeletalMeshSection(const TSharedRef<FManagedArrayCollection>& ClothCollection, const FSkeletalMeshLODModel& SkeletalMeshModel, const int32 SectionIndex, const FString& RenderMaterialPathName)
{
check(SectionIndex < SkeletalMeshModel.Sections.Num());
FCollectionClothFacade Cloth(ClothCollection);
check(Cloth.IsValid());
FCollectionClothRenderPatternFacade ClothPatternFacade = Cloth.AddGetRenderPattern();
const FSkelMeshSection& Section = SkeletalMeshModel.Sections[SectionIndex];
ClothPatternFacade.SetNumRenderVertices(Section.NumVertices);
ClothPatternFacade.SetNumRenderFaces(Section.NumTriangles);
TArrayView<FVector3f> RenderPosition = ClothPatternFacade.GetRenderPosition();
TArrayView<FVector3f> RenderNormal = ClothPatternFacade.GetRenderNormal();
TArrayView<FVector3f> RenderTangentU = ClothPatternFacade.GetRenderTangentU();
TArrayView<FVector3f> RenderTangentV = ClothPatternFacade.GetRenderTangentV();
TArrayView<TArray<FVector2f>> RenderUVs = ClothPatternFacade.GetRenderUVs();
TArrayView<FLinearColor> RenderColor = ClothPatternFacade.GetRenderColor();
TArrayView<TArray<int32>> RenderBoneIndices = ClothPatternFacade.GetRenderBoneIndices();
TArrayView<TArray<float>> RenderBoneWeights = ClothPatternFacade.GetRenderBoneWeights();
const uint32 NumTexCoords = FMath::Min((uint32)MAX_TEXCOORDS, SkeletalMeshModel.NumTexCoords);
for (int32 VertexIndex = 0; VertexIndex < Section.NumVertices; ++VertexIndex)
{
const FSoftSkinVertex& SoftVertex = Section.SoftVertices[VertexIndex];
RenderPosition[VertexIndex] = SoftVertex.Position;
RenderNormal[VertexIndex] = SoftVertex.TangentZ;
RenderTangentU[VertexIndex] = SoftVertex.TangentX;
RenderTangentV[VertexIndex] = SoftVertex.TangentY;
RenderUVs[VertexIndex].SetNum(NumTexCoords);
for (uint32 TexCoordIndex = 0; TexCoordIndex < NumTexCoords; ++TexCoordIndex)
{
RenderUVs[VertexIndex][TexCoordIndex] = SoftVertex.UVs[TexCoordIndex];
}
RenderColor[VertexIndex] = FLinearColor(SoftVertex.Color);
const int32 NumBones = Section.MaxBoneInfluences;
RenderBoneIndices[VertexIndex].SetNum(NumBones);
RenderBoneWeights[VertexIndex].SetNum(NumBones);
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
RenderBoneIndices[VertexIndex][BoneIndex] = (int32)Section.BoneMap[(int32)SoftVertex.InfluenceBones[BoneIndex]];
RenderBoneWeights[VertexIndex][BoneIndex] = (float)SoftVertex.InfluenceWeights[BoneIndex] * UE::AnimationCore::InvMaxRawBoneWeightFloat;
}
}
const int32 VertexOffset = ClothPatternFacade.GetRenderVerticesOffset();
TArrayView<FIntVector3> RenderIndices = ClothPatternFacade.GetRenderIndices();
for (uint32 FaceIndex = 0; FaceIndex < Section.NumTriangles; ++FaceIndex)
{
const uint32 IndexOffset = Section.BaseIndex + FaceIndex * 3;
RenderIndices[FaceIndex] = FIntVector3(
SkeletalMeshModel.IndexBuffer[IndexOffset + 0] - Section.BaseVertexIndex + VertexOffset,
SkeletalMeshModel.IndexBuffer[IndexOffset + 1] - Section.BaseVertexIndex + VertexOffset,
SkeletalMeshModel.IndexBuffer[IndexOffset + 2] - Section.BaseVertexIndex + VertexOffset
);
}
ClothPatternFacade.SetRenderMaterialPathName(RenderMaterialPathName);
}
void FClothDataflowTools::AddSimPatternsFromSkeletalMeshSection(const TSharedRef<FManagedArrayCollection>& ClothCollection, const FSkeletalMeshLODModel& SkeletalMeshModel, const int32 SectionIndex, const int32 UVChannelIndex, const FVector2f& UVScale, bool bImportNormals, TArray<int32>* OutSim2DToSourceVertex)
{
check(SectionIndex < SkeletalMeshModel.Sections.Num());
// Convert to DynamicMesh and then use that to create patterns.
UE::Geometry::TToDynamicMesh<Private::FSkelMeshSectionWrapper<>> SkelMeshSectionToDynamicMesh;
Private::FSkelMeshSectionWrapper<> SectionWrapper(SkeletalMeshModel, SectionIndex, bImportNormals);
UE::Geometry::FDynamicMesh3 DynamicMesh;
DynamicMesh.EnableAttributes();
constexpr bool bCopyTangents = false;
SkelMeshSectionToDynamicMesh.Convert(DynamicMesh, SectionWrapper, [](int32) { return 0; }, [](int32) { return INDEX_NONE; }, bCopyTangents);
// Set ToSrcVertIDMap as an overlay that the build sim mesh code expects.
UE::Geometry::FNonManifoldMappingSupport::AttachNonManifoldVertexMappingData(SkelMeshSectionToDynamicMesh.ToSrcVertIDMap, DynamicMesh);
constexpr bool bAppend = true;
const int32 OrigNumSimVertices2D = FCollectionClothConstFacade(ClothCollection).GetNumSimVertices2D();
check(!OutSim2DToSourceVertex || OutSim2DToSourceVertex->Num() == OrigNumSimVertices2D);
FClothGeometryTools::BuildSimMeshFromDynamicMesh(ClothCollection, DynamicMesh, UVChannelIndex, UVScale, bAppend, bImportNormals, OutSim2DToSourceVertex);
if (OutSim2DToSourceVertex)
{
// SrcVertID doesn't include SourceSection.BaseVertexIndex.
for (int32 Index = OrigNumSimVertices2D; Index < OutSim2DToSourceVertex->Num(); ++Index)
{
(*OutSim2DToSourceVertex)[Index] += SkeletalMeshModel.Sections[SectionIndex].BaseVertexIndex;
}
}
}
void FClothDataflowTools::LogAndToastWarning(const FDataflowNode& DataflowNode, const FText& Headline, const FText& Details)
{
static const FTextFormat TextFormat = FTextFormat::FromString(TEXT("{0}: {1}\n{2}"));
const FText NodeName = FText::FromName(DataflowNode.GetName());
const FText Text = FText::Format(TextFormat, NodeName, Headline, Details);
FNotificationInfo NotificationInfo(Text);
NotificationInfo.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("%s"), *Text.ToString());
}
bool FClothDataflowTools::MakeCollectionName(FString& InOutString)
{
const FString SourceString = InOutString;
InOutString = SlugStringForValidName(InOutString, TEXT("_")).Replace(TEXT("\\"), TEXT("_"));
bool bCharsWereRemoved;
do { InOutString.TrimCharInline(TEXT('_'), &bCharsWereRemoved); } while (bCharsWereRemoved);
return InOutString.Equals(SourceString);
}
static void CopyBuildSettings(const FMeshBuildSettings& InStaticMeshBuildSettings, FSkeletalMeshBuildSettings& OutSkeletalMeshBuildSettings)
{
OutSkeletalMeshBuildSettings.bRecomputeNormals = InStaticMeshBuildSettings.bRecomputeNormals;
OutSkeletalMeshBuildSettings.bRecomputeTangents = InStaticMeshBuildSettings.bRecomputeTangents;
OutSkeletalMeshBuildSettings.bUseMikkTSpace = InStaticMeshBuildSettings.bUseMikkTSpace;
OutSkeletalMeshBuildSettings.bComputeWeightedNormals = InStaticMeshBuildSettings.bComputeWeightedNormals;
OutSkeletalMeshBuildSettings.bRemoveDegenerates = InStaticMeshBuildSettings.bRemoveDegenerates;
OutSkeletalMeshBuildSettings.bUseHighPrecisionTangentBasis = InStaticMeshBuildSettings.bUseHighPrecisionTangentBasis;
OutSkeletalMeshBuildSettings.bUseFullPrecisionUVs = InStaticMeshBuildSettings.bUseFullPrecisionUVs;
OutSkeletalMeshBuildSettings.bUseBackwardsCompatibleF16TruncUVs = InStaticMeshBuildSettings.bUseBackwardsCompatibleF16TruncUVs;
// The rest we leave at defaults.
}
bool FClothDataflowTools::BuildSkeletalMeshModelFromMeshDescription(const FMeshDescription* const InMeshDescription, const FMeshBuildSettings& InBuildSettings, FSkeletalMeshLODModel& SkeletalMeshModel)
{
// This is following StaticToSkeletalMeshConverter.cpp::AddLODFromStaticMeshSourceModel
FSkeletalMeshBuildSettings BuildSettings;
CopyBuildSettings(InBuildSettings, BuildSettings);
FMeshDescription SkeletalMeshGeometry = *InMeshDescription;
FSkeletalMeshAttributes SkeletalMeshAttributes(SkeletalMeshGeometry);
SkeletalMeshAttributes.Register();
// Full binding to the root bone.
constexpr int32 RootBoneIndex = 0;
FSkinWeightsVertexAttributesRef SkinWeights = SkeletalMeshAttributes.GetVertexSkinWeights();
UE::AnimationCore::FBoneWeight RootInfluence(RootBoneIndex, 1.0f);
UE::AnimationCore::FBoneWeights RootBinding = UE::AnimationCore::FBoneWeights::Create({ RootInfluence });
for (const FVertexID VertexID : SkeletalMeshGeometry.Vertices().GetElementIDs())
{
SkinWeights.Set(VertexID, RootBinding);
}
FSkeletalMeshImportData SkeletalMeshImportGeometry = FSkeletalMeshImportData::CreateFromMeshDescription(SkeletalMeshGeometry);
// Data needed by BuildSkeletalMesh
TArray<FVector3f> LODPoints;
TArray<SkeletalMeshImportData::FMeshWedge> LODWedges;
TArray<SkeletalMeshImportData::FMeshFace> LODFaces;
TArray<SkeletalMeshImportData::FVertInfluence> LODInfluences;
TArray<int32> LODPointToRawMap;
SkeletalMeshImportGeometry.CopyLODImportData(LODPoints, LODWedges, LODFaces, LODInfluences, LODPointToRawMap);
IMeshUtilities::MeshBuildOptions BuildOptions;
BuildOptions.TargetPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform();
BuildOptions.FillOptions(BuildSettings);
SkeletalMeshModel.NumTexCoords = SkeletalMeshImportGeometry.NumTexCoords;
static const FString SkeletalMeshName("ClothAssetStaticMeshImportConvert"); // This is only used by warning messages in the mesh builder.
// Build a RefSkeleton with just a root bone. The BuildSkeletalMesh code expects you have a reference skeleton with at least one bone to work.
FReferenceSkeleton RootBoneRefSkeleton;
FReferenceSkeletonModifier SkeletonModifier(RootBoneRefSkeleton, nullptr);
FMeshBoneInfo RootBoneInfo;
RootBoneInfo.Name = FName("Root");
SkeletonModifier.Add(RootBoneInfo, FTransform());
RootBoneRefSkeleton.RebuildRefSkeleton(nullptr, true);
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
TArray<FText> WarningMessages;
if (!MeshUtilities.BuildSkeletalMesh(SkeletalMeshModel, SkeletalMeshName, RootBoneRefSkeleton, LODInfluences, LODWedges, LODFaces, LODPoints, LODPointToRawMap, BuildOptions, &WarningMessages))
{
for (const FText& Message : WarningMessages)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("%s"), *Message.ToString());
}
return false;
}
return true;
}
FDataflowNode* FClothDataflowTools::GetPropertyOwnerDataflowNode(const TSharedPtr<IPropertyHandle>& PropertyHandle, const UStruct* DataflowNodeStruct)
{
for (TSharedPtr<IPropertyHandle> OwnerHandle = PropertyHandle->GetParentHandle(); OwnerHandle; OwnerHandle = OwnerHandle->GetParentHandle())
{
if (const TSharedPtr<IPropertyHandleStruct> OwnerHandleStruct = OwnerHandle->AsStruct())
{
if (TSharedPtr<FStructOnScope> StructOnScope = OwnerHandleStruct->GetStructData())
{
if (StructOnScope->GetStruct()->IsChildOf(DataflowNodeStruct))
{
return reinterpret_cast<FDataflowNode*>(StructOnScope->GetStructMemory());
}
}
}
}
return nullptr;
}
FClothDataflowTools::FSimMeshCleanup::FSimMeshCleanup(const TArray<FIntVector3>& InTriangleToVertexIndex, const TArray<FVector2f>& InRestPositions2D, const TArray<FVector3f>& InDrapedPositions3D)
: TriangleToVertexIndex(InTriangleToVertexIndex)
, RestPositions2D(InRestPositions2D)
, DrapedPositions3D(InDrapedPositions3D)
{
check(RestPositions2D.Num() == DrapedPositions3D.Num());
OriginalTriangles.SetNum(TriangleToVertexIndex.Num());
for (int32 Index = 0; Index < OriginalTriangles.Num(); ++Index)
{
OriginalTriangles[Index].Add(Index);
}
OriginalVertices.SetNum(DrapedPositions3D.Num());
for (int32 Index = 0; Index < OriginalVertices.Num(); ++Index)
{
OriginalVertices[Index].Add(Index);
}
}
bool FClothDataflowTools::FSimMeshCleanup::RemoveDegenerateTriangles()
{
check(RestPositions2D.Num() == DrapedPositions3D.Num());
bool bHasDegenerateTriangles = false;
const int32 VertexCount = RestPositions2D.Num();
// Remap[Index] is the index of the first vertex in a group of degenerated triangles to be callapsed.
// When two groups of collapsed vertices are merged, the group with the greatest Remap[index] value must adopt the one from the other group.
// For Example:
// 1. For all i, Remap[i] = i
// 2. Finds one degenerated triangle (7, 9, 4) with collapsed edges (7, 9), (9, 4), and (7, 4) -> Remap[4] = 4, Remap[7] = 4, and Remap[9] = 4
// 3. Finds another degenerated triangle (2, 3, 4) with collapsed edges (2, 4) -> Remap[2] = 2, Remap[4] = 2, Remap[7] = 2, and Remap[9] = 2
TArray<int32> Remap;
Remap.SetNumUninitialized(VertexCount);
for (int32 Index = 0; Index < VertexCount; ++Index)
{
Remap[Index] = Index;
}
int32 OutVertexCount = VertexCount;
auto RemapAndPropagateIndex = [&Remap, &OutVertexCount](int32 Index0, int32 Index1)
{
if (Remap[Index0] != Remap[Index1])
{
if (Remap[Index0] > Remap[Index1]) // Always remap from the lowest index to ensure the earlier index is always kept
{
Swap(Index0, Index1);
}
// Merge groups with this new first index Remap[Index0]
const int32 PrevRemapIndex = Remap[Index1];
for (int32 Index = PrevRemapIndex; Index < Remap.Num(); ++Index) // Only need to start from the first index of the group to merge
{
if (Remap[Index] == PrevRemapIndex)
{
Remap[Index] = Remap[Index0];
}
}
--OutVertexCount;
}
};
const int32 TriangleCount = TriangleToVertexIndex.Num();
TArray<FIntVector3> OutTriangleToVertexIndex;
OutTriangleToVertexIndex.Reserve(TriangleCount);
TArray<TSet<int32>> OutOriginalTriangles;
OutOriginalTriangles.Reserve(TriangleCount);
for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
const int32 Index0 = TriangleToVertexIndex[TriangleIndex][0];
const int32 Index1 = TriangleToVertexIndex[TriangleIndex][1];
const int32 Index2 = TriangleToVertexIndex[TriangleIndex][2];
const FVector3f& P0 = DrapedPositions3D[Index0];
const FVector3f& P1 = DrapedPositions3D[Index1];
const FVector3f& P2 = DrapedPositions3D[Index2];
const FVector3f P0P1 = P1 - P0;
const FVector3f P0P2 = P2 - P0;
const float TriNormSizeSquared = (P0P1 ^ P0P2).SizeSquared();
if (TriNormSizeSquared <= UE_SMALL_NUMBER)
{
const FVector3f P1P2 = P2 - P1;
if (P0P1.SquaredLength() <= UE_SMALL_NUMBER)
{
RemapAndPropagateIndex(Index0, Index1);
}
if (P0P2.SquaredLength() <= UE_SMALL_NUMBER)
{
RemapAndPropagateIndex(Index0, Index2);
}
if (P1P2.SquaredLength() <= UE_SMALL_NUMBER)
{
RemapAndPropagateIndex(Index1, Index2);
}
}
else
{
OutTriangleToVertexIndex.Emplace(TriangleToVertexIndex[TriangleIndex]);
OutOriginalTriangles.Emplace(OriginalTriangles[TriangleIndex]);
}
}
TriangleToVertexIndex = MoveTemp(OutTriangleToVertexIndex);
OriginalTriangles = MoveTemp(OutOriginalTriangles);
const int32 OutTriangleCount = TriangleToVertexIndex.Num();
bHasDegenerateTriangles = (TriangleCount != OutTriangleCount);
UE_CLOG(bHasDegenerateTriangles, LogChaosClothAssetDataflowNodes, Display,
TEXT("USD import found and removed %d degenerated triangles out of %d source triangles."), TriangleCount - OutTriangleCount, TriangleCount);
// Reconstruct vertices
TArray<FVector2f> OutRestPositions2D;
OutRestPositions2D.Reserve(OutVertexCount);
TArray<FVector3f> OutDrapedPositions3D;
OutDrapedPositions3D.Reserve(OutVertexCount);
TArray<TSet<int32>> OutOriginalVertices;
OutOriginalVertices.Reserve(OutVertexCount);
TArray<int32> OutIndices;
OutIndices.Reserve(VertexCount);
int32 OutIndex = -1;
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
{
if (Remap[VertexIndex] == VertexIndex)
{
OutRestPositions2D.Emplace(RestPositions2D[VertexIndex]);
OutDrapedPositions3D.Emplace(DrapedPositions3D[VertexIndex]);
OutOriginalVertices.Emplace(OriginalVertices[VertexIndex]);
OutIndices.Emplace(++OutIndex);
}
else
{
const int32 OutRemappedIndex = OutIndices[Remap[VertexIndex]];
OutOriginalVertices[OutRemappedIndex].Append(OriginalVertices[VertexIndex]);
OutIndices.Emplace(OutRemappedIndex);
}
}
ensure(OutIndex + 1 == OutVertexCount);
RestPositions2D = MoveTemp(OutRestPositions2D);
DrapedPositions3D = MoveTemp(OutDrapedPositions3D);
OriginalVertices = MoveTemp(OutOriginalVertices);
// Remap final triangles
for (int32 TriangleIndex = 0; TriangleIndex < OutTriangleCount; ++TriangleIndex)
{
int32& Index0 = TriangleToVertexIndex[TriangleIndex][0];
int32& Index1 = TriangleToVertexIndex[TriangleIndex][1];
int32& Index2 = TriangleToVertexIndex[TriangleIndex][2];
Index0 = OutIndices[Index0];
Index1 = OutIndices[Index1];
Index2 = OutIndices[Index2];
checkSlow(Index0 != Index1);
checkSlow(Index0 != Index2);
checkSlow(Index1 != Index2);
checkSlow((OutDrapedPositions3D[Index0] - OutDrapedPositions3D[Index1]).SquaredLength() > UE_SMALL_NUMBER);
checkSlow((OutDrapedPositions3D[Index0] - OutDrapedPositions3D[Index2]).SquaredLength() > UE_SMALL_NUMBER);
checkSlow((OutDrapedPositions3D[Index1] - OutDrapedPositions3D[Index2]).SquaredLength() > UE_SMALL_NUMBER);
}
return bHasDegenerateTriangles;
}
bool FClothDataflowTools::FSimMeshCleanup::RemoveDuplicateTriangles()
{
bool bHasDuplicatedTriangles = false;
const int32 TriangleCount = TriangleToVertexIndex.Num();
TMap<FIntVector3, int32> Triangles;
Triangles.Reserve(TriangleCount);
TArray<FIntVector3> OutTriangleToVertexIndex;
OutTriangleToVertexIndex.Reserve(TriangleCount);
TArray<TSet<int32>> OutOriginalTriangles;
OutOriginalTriangles.Reserve(TriangleCount);
auto GetSortedIndices = [](const FIntVector3& TriangleIndices)->FIntVector3
{
const int32 Index0 = TriangleIndices[0];
const int32 Index1 = TriangleIndices[1];
const int32 Index2 = TriangleIndices[2];
return (Index0 < Index1) ?
(Index1 < Index2) ? FIntVector3(Index0, Index1, Index2) : (Index0 < Index2) ? FIntVector3(Index0, Index2, Index1) : FIntVector3(Index2, Index0, Index1) :
(Index0 < Index2) ? FIntVector3(Index1, Index0, Index2) : (Index1 < Index2) ? FIntVector3(Index1, Index2, Index0) : FIntVector3(Index2, Index1, Index0);
};
for (int32 Index = 0; Index < TriangleCount; ++Index)
{
const FIntVector3& TriangleIndices = TriangleToVertexIndex[Index];
const FIntVector3 TriangleSortedIndices = GetSortedIndices(TriangleIndices);
if (int32* NewTriangle = Triangles.Find(TriangleSortedIndices))
{
bHasDuplicatedTriangles = true;
OutOriginalTriangles[*NewTriangle].Append(OriginalTriangles[Index]);
}
else
{
NewTriangle = &Triangles.Emplace(TriangleSortedIndices);
*NewTriangle = OutTriangleToVertexIndex.Emplace(TriangleIndices);
OutOriginalTriangles.Emplace(OriginalTriangles[Index]);
}
}
TriangleToVertexIndex = MoveTemp(OutTriangleToVertexIndex);
OriginalTriangles = MoveTemp(OutOriginalTriangles);
UE_CLOG(bHasDuplicatedTriangles, LogChaosClothAssetDataflowNodes, Display,
TEXT("USD import found and removed %d duplicated triangles out of %d source triangles."), TriangleCount - TriangleToVertexIndex.Num(), TriangleCount);
return bHasDuplicatedTriangles;
}
template<typename T UE_REQUIRES_DEFINITION(std::is_same_v<T, TArray<int32>> || std::is_same_v<T, TSet<int32>>)>
TArray<int32> FClothDataflowTools::GetOriginalToNewIndices(const TConstArrayView<T>& NewToOriginals, int32 NumOriginalIndices)
{
TArray<int32> OriginalToNewIndices;
OriginalToNewIndices.Init(INDEX_NONE, NumOriginalIndices);
for (int32 NewIndex = 0; NewIndex < NewToOriginals.Num(); ++NewIndex)
{
for (const int32 OriginalIndex : NewToOriginals[NewIndex])
{
check(OriginalToNewIndices.IsValidIndex(OriginalIndex));
check(OriginalToNewIndices[OriginalIndex] == INDEX_NONE);
OriginalToNewIndices[OriginalIndex] = NewIndex;
}
}
return OriginalToNewIndices;
}
template TArray<int32> FClothDataflowTools::GetOriginalToNewIndices<TArray<int32>>(const TConstArrayView<TArray<int32>>& NewToOriginals, int32 NumOriginalIndices);
template TArray<int32> FClothDataflowTools::GetOriginalToNewIndices<TSet<int32>>(const TConstArrayView<TSet<int32>>& NewToOriginals, int32 NumOriginalIndices);
bool FClothDataflowTools::RemoveDegenerateTriangles(
const TArray<FIntVector3>& TriangleToVertexIndex,
const TArray<FVector2f>& RestPositions2D,
const TArray<FVector3f>& DrapedPositions3D,
TArray<FIntVector3>& OutTriangleToVertexIndex,
TArray<FVector2f>& OutRestPositions2D,
TArray<FVector3f>& OutDrapedPositions3D,
TArray<int32>& OutIndices)
{
FSimMeshCleanup SimMeshCleanup(TriangleToVertexIndex, RestPositions2D, DrapedPositions3D);
const bool bHasDegenerateTriangles = SimMeshCleanup.RemoveDegenerateTriangles();
OutIndices = GetOriginalToNewIndices<TSet<int32>>(SimMeshCleanup.OriginalVertices, DrapedPositions3D.Num());
OutTriangleToVertexIndex = MoveTemp(SimMeshCleanup.TriangleToVertexIndex);
OutRestPositions2D = MoveTemp(SimMeshCleanup.RestPositions2D);
OutDrapedPositions3D = MoveTemp(SimMeshCleanup.DrapedPositions3D);
return bHasDegenerateTriangles;
}
bool FClothDataflowTools::RemoveDuplicateStitches(TArray<TArray<FIntVector2>>& SeamStitches)
{
bool bHasDuplicateStitches = false;
const int32 NumSeamStitches = SeamStitches.Num();
// Calculate the total number of stitches
int32 NumStitches = 0;
for (const TArray<FIntVector2>& Stitches : SeamStitches)
{
NumStitches += Stitches.Num();
}
TSet<FIntVector2> StichSet;
StichSet.Reserve(NumStitches);
int32 OutNumStitches = 0;
TArray<TArray<FIntVector2>> OutSeamStitches;
OutSeamStitches.Reserve(NumSeamStitches);
for (const TArray<FIntVector2>& Stitches : SeamStitches)
{
TArray<FIntVector2> OutStitches;
OutStitches.Reserve(Stitches.Num());
for (const FIntVector2& Stitch : Stitches)
{
const FIntVector2 SortedStitch = Stitch[0] < Stitch[1] ?
FIntVector2(Stitch[0], Stitch[1]) :
FIntVector2(Stitch[1], Stitch[0]);
bool bIsAlreadyInSet;
StichSet.FindOrAdd(SortedStitch, &bIsAlreadyInSet);
if (bIsAlreadyInSet)
{
bHasDuplicateStitches = true;
}
else
{
OutStitches.Emplace(Stitch);
}
}
if (OutStitches.Num())
{
OutSeamStitches.Emplace(OutStitches);
OutNumStitches += OutStitches.Num();
}
}
UE_CLOG(bHasDuplicateStitches, LogChaosClothAssetDataflowNodes, Display,
TEXT("USD import found and removed %d duplicated stitches out of %d source stitches."), NumStitches - OutNumStitches, NumStitches);
SeamStitches = MoveTemp(OutSeamStitches);
return bHasDuplicateStitches;
}
} // End namespace UE::Chaos::ClothAsset