Files
UnrealEngine/Engine/Source/Runtime/ClothingSystemRuntimeCommon/Private/ClothPhysicalMeshData.cpp
2025-05-18 13:04:45 +08:00

275 lines
8.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ClothPhysicalMeshData.h"
#include "ClothConfigBase.h"
#include "ClothPhysicalMeshDataBase_Legacy.h"
#include "GPUSkinPublicDefs.h" // For MAX_TOTAL_INFLUENCES
#include "ClothTetherData.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ClothPhysicalMeshData)
FClothPhysicalMeshData::FClothPhysicalMeshData()
: MaxBoneWeights(0)
, NumFixedVerts(0)
{
ClearWeightMaps();
}
void FClothPhysicalMeshData::MigrateFrom(FClothPhysicalMeshData& ClothPhysicalMeshData)
{
if (this != &ClothPhysicalMeshData)
{
Vertices = MoveTemp(ClothPhysicalMeshData.Vertices);
Normals = MoveTemp(ClothPhysicalMeshData.Normals);
#if WITH_EDITORONLY_DATA
VertexColors = MoveTemp(ClothPhysicalMeshData.VertexColors);
#endif
Indices = MoveTemp(ClothPhysicalMeshData.Indices);
WeightMaps = MoveTemp(ClothPhysicalMeshData.WeightMaps);
InverseMasses = MoveTemp(ClothPhysicalMeshData.InverseMasses);
BoneData = MoveTemp(ClothPhysicalMeshData.BoneData);
NumFixedVerts = ClothPhysicalMeshData.NumFixedVerts;
MaxBoneWeights = ClothPhysicalMeshData.MaxBoneWeights;
SelfCollisionVertexSet = MoveTemp(ClothPhysicalMeshData.SelfCollisionVertexSet);
}
}
void FClothPhysicalMeshData::MigrateFrom(UClothPhysicalMeshDataBase_Legacy* ClothPhysicalMeshDataBase)
{
Vertices = MoveTemp(ClothPhysicalMeshDataBase->Vertices);
Normals = MoveTemp(ClothPhysicalMeshDataBase->Normals);
#if WITH_EDITORONLY_DATA
VertexColors = MoveTemp(ClothPhysicalMeshDataBase->VertexColors);
#endif
Indices = MoveTemp(ClothPhysicalMeshDataBase->Indices);
InverseMasses = MoveTemp(ClothPhysicalMeshDataBase->InverseMasses);
BoneData = MoveTemp(ClothPhysicalMeshDataBase->BoneData);
NumFixedVerts = ClothPhysicalMeshDataBase->NumFixedVerts;
MaxBoneWeights = ClothPhysicalMeshDataBase->MaxBoneWeights;
SelfCollisionVertexSet.Empty(ClothPhysicalMeshDataBase->SelfCollisionIndices.Num());
for (uint32 Index : ClothPhysicalMeshDataBase->SelfCollisionIndices)
{
SelfCollisionVertexSet.Add((int32)Index);
}
const TArray<uint32> FloatArrayIds = ClothPhysicalMeshDataBase->GetFloatArrayIds();
for (uint32 FloatArrayId : FloatArrayIds)
{
if (TArray<float>* const FloatArray = ClothPhysicalMeshDataBase->GetFloatArray(FloatArrayId))
{
FindOrAddWeightMap(FloatArrayId).Values = MoveTemp(*FloatArray);
}
}
}
void FClothPhysicalMeshData::Reset(const int32 InNumVerts, const int32 InNumIndices)
{
Vertices.Init(FVector3f::ZeroVector, InNumVerts);
Normals.Init(FVector3f::ZeroVector, InNumVerts);
#if WITH_EDITORONLY_DATA
VertexColors.Init(FColor::Black, InNumVerts);
#endif //#if WITH_EDITORONLY_DATA
InverseMasses.Init(0.f, InNumVerts);
BoneData.Reset(InNumVerts);
BoneData.AddDefaulted(InNumVerts);
Indices.Init(0, InNumIndices);
SelfCollisionVertexSet.Reset();
NumFixedVerts = 0;
MaxBoneWeights = 0;
ClearWeightMaps();
}
void FClothPhysicalMeshData::ClearWeightMaps()
{
// Clear all weight maps (and reserve a few slots)
WeightMaps.Empty(4);
// Add default (empty) optional maps, as these are always expected to be found
AddWeightMap(EWeightMapTargetCommon::MaxDistance);
AddWeightMap(EWeightMapTargetCommon::BackstopDistance);
AddWeightMap(EWeightMapTargetCommon::BackstopRadius);
AddWeightMap(EWeightMapTargetCommon::AnimDriveStiffness);
}
void FClothPhysicalMeshData::BuildSelfCollisionData(float SelfCollisionRadius)
{
const float SelfCollisionDiamSq = 4.f *SelfCollisionRadius * SelfCollisionRadius;
// Start with the full set
const int32 NumVerts = Vertices.Num();
TArray<bool> VertexIsValid;
VertexIsValid.Init(true, NumVerts);
const FPointWeightMap& MaxDistances = GetWeightMap(EWeightMapTargetCommon::MaxDistance);
for (int32 Index = 0; Index < NumVerts; ++Index)
{
if (MaxDistances.IsBelowThreshold(Index))
{
VertexIsValid[Index] = false;
}
}
// Now start aggressively culling verts that are near others that we have accepted
SelfCollisionVertexSet.Reset();
for (int32 V0Index = 0; V0Index < NumVerts; ++V0Index)
{
if (!VertexIsValid[V0Index])
{
continue;
}
SelfCollisionVertexSet.Add(V0Index);
const FVector& V0Pos = (FVector)Vertices[V0Index];
// Start one after our current V0, we've done the other checks
for (int32 V1Index = V0Index + 1; V1Index < NumVerts; ++V1Index)
{
if (!VertexIsValid[V1Index])
{
continue;
}
const FVector& V1Pos = (FVector)Vertices[V1Index];
const float V0ToV1DistSq = (V1Pos - V0Pos).SizeSquared();
if (V0ToV1DistSq < SelfCollisionDiamSq)
{
// Points are in contact in the rest state. Remove it.
//
// It's worth noting that this biases towards removing indices
// of later in the list, and keeping ones earlier. That's not
// a great criteria for choosing which one is more important.
VertexIsValid[V1Index] = false;
continue;
}
}
}
}
void FClothPhysicalMeshData::CalculateInverseMasses()
{
// Recalculate inverse masses for the physical mesh particles
check(Indices.Num() % 3 == 0);
const int32 NumVerts = Vertices.Num();
InverseMasses.Empty(NumVerts);
InverseMasses.AddZeroed(NumVerts);
for (int32 TriBaseIndex = 0; TriBaseIndex < Indices.Num(); TriBaseIndex += 3)
{
const int32 Index0 = Indices[TriBaseIndex];
const int32 Index1 = Indices[TriBaseIndex + 1];
const int32 Index2 = Indices[TriBaseIndex + 2];
const FVector AB = FVector(Vertices[Index1] - Vertices[Index0]);
const FVector AC = FVector(Vertices[Index2] - Vertices[Index0]);
const float TriArea = FVector::CrossProduct(AB, AC).Size();
InverseMasses[Index0] += TriArea;
InverseMasses[Index1] += TriArea;
InverseMasses[Index2] += TriArea;
}
NumFixedVerts = 0;
const FPointWeightMap* const MaxDistances = FindWeightMap(EWeightMapTargetCommon::MaxDistance);
const TFunction<bool(int32)> IsKinematic = (!MaxDistances || !MaxDistances->Num()) ?
TFunction<bool(int32)>([](int32)->bool { return false; }) :
TFunction<bool(int32)>([&MaxDistances](int32 Index)->bool { return (*MaxDistances)[Index] < SMALL_NUMBER; }); // For consistency, the default Threshold should be 0.1, not SMALL_NUMBER. But for backward compatibility it needs to be SMALL_NUMBER for now.
float MassSum = 0.0f;
for (int32 CurrVertIndex = 0; CurrVertIndex < NumVerts; ++CurrVertIndex)
{
float& InverseMass = InverseMasses[CurrVertIndex];
if (IsKinematic(CurrVertIndex))
{
InverseMass = 0.0f;
++NumFixedVerts;
}
else
{
MassSum += InverseMass;
}
}
if (MassSum > 0.0f)
{
const float MassScale = (float)(NumVerts - NumFixedVerts) / MassSum;
for (float& InverseMass : InverseMasses)
{
if (InverseMass != 0.0f)
{
InverseMass *= MassScale;
InverseMass = 1.0f / InverseMass;
}
}
}
// TODO: Cache config base (Chaos) mass data
// Note that multiple configs with different mass modes will require a different
// structure than this one to store the mass.
}
void FClothPhysicalMeshData::CalculateNumInfluences()
{
// Needed by all cloth implementations got skinning
for (int32 VertIndex = 0; VertIndex < Vertices.Num(); ++VertIndex)
{
FClothVertBoneData& BoneDatum = BoneData[VertIndex];
const uint16* BoneIndices = BoneDatum.BoneIndices;
const float* BoneWeights = BoneDatum.BoneWeights;
BoneDatum.NumInfluences = MAX_TOTAL_INFLUENCES;
int32 NumInfluences = 0;
for (int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
{
if (BoneWeights[InfluenceIndex] == 0.0f || BoneIndices[InfluenceIndex] == INDEX_NONE)
{
BoneDatum.NumInfluences = NumInfluences;
break;
}
++NumInfluences;
}
}
}
void FClothPhysicalMeshData::CalculateTethers(bool bUseEuclideanDistance, bool bUseGeodesicDistance)
{
const FPointWeightMap* const MaxDistances = FindWeightMap(EWeightMapTargetCommon::MaxDistance);
const FPointWeightMap* const TetherEnds = FindWeightMap(EWeightMapTargetCommon::TetherEndsMask);
if (TetherEnds)
{
// Note: Technically there could be two different configs requiring different flavour of tethers,
// in reality at the time of writing there is only one type of tether involved at any one type.
if (bUseEuclideanDistance)
{
constexpr bool bGenerateGeodesic = false;
EuclideanTethers.GenerateTethers(Vertices, Indices, TetherEnds->Values, bGenerateGeodesic);
}
if (bUseGeodesicDistance)
{
constexpr bool bGenegateGeodesic = true;
GeodesicTethers.GenerateTethers(Vertices, Indices, TetherEnds->Values, bGenegateGeodesic);
}
}
else if (MaxDistances)
{
// Note: Technically there could be two different configs requiring different flavour of tethers,
// in reality at the time of writing there is only one type of tether involved at any one type.
if (bUseEuclideanDistance)
{
constexpr bool bGenerateGeodesic = false;
EuclideanTethers.GenerateTethers(Vertices, Indices, MaxDistances->Values, bGenerateGeodesic);
}
if (bUseGeodesicDistance)
{
constexpr bool bGenegateGeodesic = true;
GeodesicTethers.GenerateTethers(Vertices, Indices, MaxDistances->Values, bGenegateGeodesic);
}
}
}