680 lines
21 KiB
C++
680 lines
21 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "ClothTetherData.h"
|
|
#include "Async/ParallelFor.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(ClothTetherData)
|
|
|
|
class FClothTetherDataPrivate
|
|
{
|
|
public:
|
|
static constexpr int32 MaxNumAttachments = 4; // Max recommended number of tethers per point, could eventually been specified by config
|
|
static constexpr float KinematicDistanceThreshold = 0.1f; // Particles which are barely allowed to move around should be considered kinematic
|
|
|
|
typedef TTuple<int32, int32, float> FTether;
|
|
|
|
FClothTetherDataPrivate(
|
|
const TConstArrayView<FVector3f>& Points,
|
|
const TConstArrayView<uint32>& Indices,
|
|
TFunctionRef<bool(int32 Index)> IsKinematic,
|
|
TFunctionRef<bool(int32 Index)> IsDynamicTetherEnd,
|
|
TFunctionRef<bool(int32 Index)> IsKinematicTetherEnd,
|
|
bool bUseGeodesicDistance);
|
|
|
|
FClothTetherDataPrivate(
|
|
TArray<TArray<TPair<float, int32>>>&& PerDynamicNodeTethers);
|
|
|
|
void GetBatchedTetherData(TArray<TArray<TTuple<int32, int32, float>>>& Tethers) const;
|
|
|
|
private:
|
|
|
|
// Generate an array of unconnected islands from a selection of points.
|
|
void ComputeKinematicNodeIslands();
|
|
|
|
// Generate a map of tethers by following the triangle mesh network
|
|
// The choice of the tether is determined by the shortest euclidean (beeline) distance.
|
|
void GenerateEuclideanTethers(const TConstArrayView<FVector3f>& Points);
|
|
|
|
// Generate a map of tethers by following the triangle mesh network
|
|
// The choice of the tether is determined by the shortest geodesic (curvature) distance.
|
|
void GenerateGeodesicTethers(const TConstArrayView<FVector3f>& Points,
|
|
TFunctionRef<bool(int32 Index)> IsKinematic);
|
|
|
|
// Update TetherNums after computing tethers.
|
|
void UpdateCounts();
|
|
|
|
private:
|
|
TMap<int32, TSet<int32>> NodeToNeighbors;
|
|
TArray<int32> AllDynamicNodes;
|
|
TArray<int32> DynamicTetherNodesDynamicIndex; // Index into AllDynamicNodes, not Nodes.
|
|
TArray<int32> KinematicTetherNodes;
|
|
TArray<TArray<int32>> KinematicNodeIslands;
|
|
TArray<TArray<FTether>> TetherSlots; // Each dynamic node has between 0 and MaxNumAttachments slots
|
|
TArray<int32> TetherNums;
|
|
};
|
|
|
|
void FClothTetherData::GenerateTethers(
|
|
const TConstArrayView<FVector3f>& Points,
|
|
const TConstArrayView<uint32>& Indices,
|
|
const TConstArrayView<float>& MaxDistances,
|
|
bool bUseGeodesicDistance)
|
|
{
|
|
// Early exit if there is no MaxDistances mask
|
|
if (Points.Num() != MaxDistances.Num())
|
|
{
|
|
Tethers.Empty();
|
|
return;
|
|
}
|
|
|
|
// Calculate the tethers
|
|
const FClothTetherDataPrivate ClothTetherData(Points, Indices,
|
|
[&MaxDistances](int32 Node)
|
|
{
|
|
return MaxDistances[Node] < FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
[&MaxDistances](int32 Node)
|
|
{
|
|
return MaxDistances[Node] >= FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
[&MaxDistances](int32 Node)
|
|
{
|
|
return MaxDistances[Node] < FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
bUseGeodesicDistance);
|
|
ClothTetherData.GetBatchedTetherData(Tethers);
|
|
}
|
|
|
|
void FClothTetherData::GenerateTethers(
|
|
const TConstArrayView<FVector3f>& Points,
|
|
const TConstArrayView<uint32>& Indices,
|
|
const TConstArrayView<float>& MaxDistances,
|
|
const TConstArrayView<float>& TetherEnds,
|
|
bool bUseGeodesicDistance)
|
|
{
|
|
// Early exit if there is no MaxDistances mask
|
|
if (Points.Num() != MaxDistances.Num())
|
|
{
|
|
Tethers.Empty();
|
|
return;
|
|
}
|
|
|
|
// Calculate the tethers
|
|
const FClothTetherDataPrivate ClothTetherData(Points, Indices,
|
|
[&MaxDistances](int32 Node)
|
|
{
|
|
return MaxDistances[Node] < FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
[&MaxDistances](int32 Node)
|
|
{
|
|
return MaxDistances[Node] >= FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
[&TetherEnds](int32 Node)
|
|
{
|
|
return TetherEnds[Node] < FClothTetherDataPrivate::KinematicDistanceThreshold;
|
|
},
|
|
bUseGeodesicDistance);
|
|
ClothTetherData.GetBatchedTetherData(Tethers);
|
|
}
|
|
|
|
void FClothTetherData::GenerateTethers(
|
|
const TConstArrayView<FVector3f>& Points,
|
|
const TConstArrayView<uint32>& Indices,
|
|
const TSet<int32>& KinematicNodes,
|
|
bool bUseGeodesicDistance)
|
|
{
|
|
// Early exit if there is no KinematicNodes set
|
|
if (KinematicNodes.IsEmpty())
|
|
{
|
|
Tethers.Empty();
|
|
return;
|
|
}
|
|
|
|
// Calculate the tethers
|
|
const FClothTetherDataPrivate ClothTetherData(Points, Indices,
|
|
[&KinematicNodes](int32 Node)
|
|
{
|
|
return KinematicNodes.Contains(Node);
|
|
},
|
|
[&KinematicNodes](int32 Node)
|
|
{
|
|
return !KinematicNodes.Contains(Node);
|
|
},
|
|
[&KinematicNodes](int32 Node)
|
|
{
|
|
return KinematicNodes.Contains(Node);
|
|
},
|
|
bUseGeodesicDistance);
|
|
ClothTetherData.GetBatchedTetherData(Tethers);
|
|
}
|
|
|
|
void FClothTetherData::GenerateTethers(
|
|
const TConstArrayView<FVector3f>& Points, // Reference pose
|
|
const TConstArrayView<uint32>& Indices, // Triangle mesh
|
|
const TSet<int32>& KinematicNodes, // all kinematic indices
|
|
const TSet<int32>& FixedEndNodes, // subset of kinematic indices used for fixed end.
|
|
const TSet<int32>& DynamicEndNodes, // subset of dynamic indices to create tethers for.
|
|
bool bUseGeodesicDistance)
|
|
{
|
|
// Early exit if there is no KinematicNodes or DynamicNodes set
|
|
if (FixedEndNodes.IsEmpty() || DynamicEndNodes.IsEmpty() || KinematicNodes.IsEmpty())
|
|
{
|
|
Tethers.Empty();
|
|
return;
|
|
}
|
|
|
|
// Calculate the tethers
|
|
const FClothTetherDataPrivate ClothTetherData(Points, Indices,
|
|
[&KinematicNodes](int32 Node)
|
|
{
|
|
return KinematicNodes.Contains(Node);
|
|
},
|
|
[&KinematicNodes, &DynamicEndNodes](int32 Node)
|
|
{
|
|
return DynamicEndNodes.Contains(Node) && !KinematicNodes.Contains(Node);
|
|
},
|
|
[&KinematicNodes, &FixedEndNodes](int32 Node)
|
|
{
|
|
return FixedEndNodes.Contains(Node) && KinematicNodes.Contains(Node);
|
|
},
|
|
bUseGeodesicDistance);
|
|
ClothTetherData.GetBatchedTetherData(Tethers);
|
|
|
|
}
|
|
|
|
void FClothTetherData::GenerateTethers(
|
|
TArray<TArray<TPair<float, int32>>>&& PerDynamicNodeTethers)
|
|
{
|
|
const FClothTetherDataPrivate ClothTetherData(MoveTemp(PerDynamicNodeTethers));
|
|
ClothTetherData.GetBatchedTetherData(Tethers);
|
|
}
|
|
|
|
bool FClothTetherData::Serialize(FArchive& Ar)
|
|
{
|
|
// Serialize normal tagged property data
|
|
if (Ar.IsLoading() || Ar.IsSaving())
|
|
{
|
|
UScriptStruct* const Struct = FClothTetherData::StaticStruct();
|
|
Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr);
|
|
}
|
|
|
|
// Serialize the tethers
|
|
Ar << Tethers;
|
|
|
|
return true;
|
|
}
|
|
|
|
FClothTetherDataPrivate::FClothTetherDataPrivate(
|
|
const TConstArrayView<FVector3f>& Points,
|
|
const TConstArrayView<uint32>& Indices,
|
|
TFunctionRef<bool(int32 Index)> IsKinematic,
|
|
TFunctionRef<bool(int32 Index)> IsDynamicTetherEnd,
|
|
TFunctionRef<bool(int32 Index)> IsKinematicTetherEnd,
|
|
bool bUseGeodesicDistance)
|
|
{
|
|
// Calculate the points' neighbors map
|
|
NodeToNeighbors.Reserve(Points.Num());
|
|
|
|
for (int32 i = 0; i < Indices.Num(); i += 3)
|
|
{
|
|
const int32 Index0 = Indices[i];
|
|
const int32 Index1 = Indices[i + 1];
|
|
const int32 Index2 = Indices[i + 2];
|
|
|
|
NodeToNeighbors.FindOrAdd(Index0).Append({ Index1, Index2 });
|
|
NodeToNeighbors.FindOrAdd(Index1).Append({ Index0, Index2 });
|
|
NodeToNeighbors.FindOrAdd(Index2).Append({ Index0, Index1 });
|
|
}
|
|
|
|
// Fill up the list of all used indices
|
|
TArray<int32> Nodes;
|
|
NodeToNeighbors.GenerateKeyArray(Nodes);
|
|
|
|
// Find all kinematic points to use as anchor points for the tethers and seed the path finding
|
|
AllDynamicNodes.Reserve(Points.Num());
|
|
DynamicTetherNodesDynamicIndex.Reserve(Points.Num());
|
|
KinematicTetherNodes.Reserve(Points.Num());
|
|
for (const int32 Node : Nodes)
|
|
{
|
|
if (!IsKinematic(Node))
|
|
{
|
|
const int32 DynamicIndex = AllDynamicNodes.Add(Node);
|
|
if (IsDynamicTetherEnd(Node))
|
|
{
|
|
DynamicTetherNodesDynamicIndex.Add(DynamicIndex);
|
|
}
|
|
|
|
}
|
|
else if (IsKinematicTetherEnd(Node))
|
|
{
|
|
KinematicTetherNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
if (KinematicTetherNodes.Num())
|
|
{
|
|
// Compute the islands of kinematic particles
|
|
ComputeKinematicNodeIslands();
|
|
|
|
// Allocate the tether batches, each node has a maximum of MaxNumAttachments slots
|
|
TetherSlots.SetNum(DynamicTetherNodesDynamicIndex.Num());
|
|
|
|
for (TArray<FTether>& TetherSlot : TetherSlots)
|
|
{
|
|
TetherSlot.Reserve(MaxNumAttachments);
|
|
}
|
|
|
|
// Find the tethers
|
|
if (!bUseGeodesicDistance)
|
|
{
|
|
GenerateEuclideanTethers(Points);
|
|
}
|
|
else
|
|
{
|
|
GenerateGeodesicTethers(Points, IsKinematic);
|
|
}
|
|
|
|
UpdateCounts();
|
|
}
|
|
}
|
|
|
|
FClothTetherDataPrivate::FClothTetherDataPrivate(TArray<TArray<TPair<float, int32>>>&& PerDynamicNodeTethers)
|
|
{
|
|
TetherSlots.SetNum(PerDynamicNodeTethers.Num());
|
|
|
|
// For each dynamic node
|
|
ParallelFor(PerDynamicNodeTethers.Num(), [this, &PerDynamicNodeTethers](int32 Index)
|
|
{
|
|
const int32 DynamicNode = Index;
|
|
TArray<TPair<float, int32>>& ClosestKinematicNodes = PerDynamicNodeTethers[Index];
|
|
// Only keep the first MaxNumAttachments closest kinematic nodes
|
|
if (ClosestKinematicNodes.Num() > MaxNumAttachments)
|
|
{
|
|
// Order all by distance, smallest first
|
|
ClosestKinematicNodes.Sort();
|
|
|
|
// Shrink the list... but not the array
|
|
ClosestKinematicNodes.SetNum(MaxNumAttachments, EAllowShrinking::No);
|
|
}
|
|
// Finally create the tethers between this dynamic node and the N closests kinematic ones
|
|
TetherSlots[Index].Reserve(ClosestKinematicNodes.Num());
|
|
|
|
for (const TPair<float, int32> ClosestKinematicNode : ClosestKinematicNodes)
|
|
{
|
|
const float Length = ClosestKinematicNode.Key;
|
|
const int32 KinematicNode = ClosestKinematicNode.Value;
|
|
|
|
TetherSlots[Index].Emplace(KinematicNode, DynamicNode, Length);
|
|
}
|
|
});
|
|
|
|
UpdateCounts();
|
|
}
|
|
|
|
void FClothTetherDataPrivate::GetBatchedTetherData(TArray<TArray<TTuple<int32, int32, float>>>& Tethers) const
|
|
{
|
|
// Reorganize the multiple tethers per node array into a single sequential batches of tethers more suitable for parallel processing
|
|
const int32 NumDynamicNodes = TetherSlots.Num();
|
|
const int32 MaxNumUsedSlots = TetherNums.Num();
|
|
Tethers.Reset(MaxNumUsedSlots);
|
|
Tethers.SetNum(MaxNumUsedSlots);
|
|
|
|
for (int32 Slot = 0; Slot < MaxNumUsedSlots; ++Slot)
|
|
{
|
|
Tethers[Slot].Reserve(TetherNums[Slot]);
|
|
|
|
for (int32 Index = 0; Index < NumDynamicNodes; ++Index)
|
|
{
|
|
if (TetherSlots[Index].IsValidIndex(Slot))
|
|
{
|
|
Tethers[Slot].Emplace(TetherSlots[Index][Slot]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FClothTetherDataPrivate::ComputeKinematicNodeIslands()
|
|
{
|
|
KinematicNodeIslands.Reset();
|
|
|
|
int32 NextIslandIndex = 0;
|
|
TArray<int32> FreeIslandIndices;
|
|
|
|
TMap<int32, int32> NodeToIslands;
|
|
NodeToIslands.Reserve(KinematicTetherNodes.Num());
|
|
|
|
for (const int32 KinematicNode : KinematicTetherNodes)
|
|
{
|
|
// Assign this point an island, possibly unionizing existing islands
|
|
int32 IslandIndex = TNumericLimits<int32>::Max();
|
|
|
|
const TSet<int32>& NeighborNodes = NodeToNeighbors[KinematicNode];
|
|
for (const int32 NeighborNode : NeighborNodes)
|
|
{
|
|
if (const int32* const NeighborIslandIndexPtr = NodeToIslands.Find(NeighborNode))
|
|
{
|
|
const int32 NeighborIslandIndex = *NeighborIslandIndexPtr;
|
|
|
|
if (IslandIndex == TNumericLimits<int32>::Max())
|
|
{
|
|
// No assigned island yet, join with the neighbor's island
|
|
IslandIndex = NeighborIslandIndex;
|
|
}
|
|
else if (NeighborIslandIndex != IslandIndex)
|
|
{
|
|
// This point is connected to multiple islands, so union them
|
|
TArray<int32>& NeighborIsland = KinematicNodeIslands[NeighborIslandIndex];
|
|
for (const int32 NeighborIslandNode : NeighborIsland)
|
|
{
|
|
check(NodeToIslands[NeighborIslandNode] == NeighborIslandIndex);
|
|
NodeToIslands[NeighborIslandNode] = IslandIndex;
|
|
}
|
|
KinematicNodeIslands[IslandIndex].Append(NeighborIsland); // Union the two islands
|
|
NeighborIsland.Reset(); // Don't deallocate, to allow for reusing the free island
|
|
FreeIslandIndices.AddUnique(NeighborIslandIndex);
|
|
}
|
|
// Else this neighbor is already in the same island as the previous neighbors
|
|
}
|
|
}
|
|
|
|
// If no connected IslandIndex was found, create a new one (or reuse an old vacated one)
|
|
if (IslandIndex == TNumericLimits<int32>::Max())
|
|
{
|
|
if (!FreeIslandIndices.Num())
|
|
{
|
|
IslandIndex = NextIslandIndex++;
|
|
KinematicNodeIslands.SetNum(NextIslandIndex);
|
|
}
|
|
else
|
|
{
|
|
// Reuse a previously allocated, but currently unused, island
|
|
IslandIndex = FreeIslandIndices.Pop();
|
|
}
|
|
}
|
|
|
|
NodeToIslands.FindOrAdd(KinematicNode) = IslandIndex;
|
|
check(KinematicNodeIslands.IsValidIndex(IslandIndex));
|
|
KinematicNodeIslands[IslandIndex].Add(KinematicNode);
|
|
}
|
|
|
|
// Remove empty KinematicNodeIslands
|
|
int32 TotalNumIslandKinematicNodes = 0;
|
|
for (int32 IslandIndex = 0; IslandIndex < KinematicNodeIslands.Num(); )
|
|
{
|
|
const int32 NumIslandKinematicNodes = KinematicNodeIslands[IslandIndex].Num();
|
|
if (!NumIslandKinematicNodes)
|
|
{
|
|
KinematicNodeIslands.RemoveAtSwap(IslandIndex, EAllowShrinking::No);
|
|
// RemoveAtSwap takes the last elements to replace the current one, do not increment the index in this case
|
|
}
|
|
else
|
|
{
|
|
TotalNumIslandKinematicNodes += NumIslandKinematicNodes;
|
|
++IslandIndex;
|
|
}
|
|
}
|
|
|
|
// Final sanity check
|
|
check(TotalNumIslandKinematicNodes == KinematicTetherNodes.Num());
|
|
}
|
|
|
|
void FClothTetherDataPrivate::GenerateEuclideanTethers(const TConstArrayView<FVector3f>& Points)
|
|
{
|
|
check(KinematicNodeIslands.Num());
|
|
|
|
// For each dynamic node
|
|
ParallelFor(DynamicTetherNodesDynamicIndex.Num(), [this, &Points](int32 Index)
|
|
{
|
|
const int32 DynamicNode = AllDynamicNodes[DynamicTetherNodesDynamicIndex[Index]];
|
|
|
|
// Measure the distance to all kinematic nodes, and keep the closest from each island
|
|
TArray<TPair<float, int32>> ClosestKinematicNodes;
|
|
ClosestKinematicNodes.Reserve(KinematicNodeIslands.Num());
|
|
|
|
for (const TArray<int32>& KinematicNodeIsland : KinematicNodeIslands)
|
|
{
|
|
check(KinematicNodeIsland.Num()); // Island must not be an empty array
|
|
|
|
int32 ClosestKinematicNode = TNumericLimits<int32>::Max();
|
|
float ClosestSquareDistance = TNumericLimits<float>::Max();
|
|
for (const int32 KinematicNode : KinematicNodeIsland)
|
|
{
|
|
const float SquareDistance = (Points[KinematicNode] - Points[DynamicNode]).SizeSquared();
|
|
if (SquareDistance < ClosestSquareDistance)
|
|
{
|
|
ClosestKinematicNode = KinematicNode;
|
|
ClosestSquareDistance = SquareDistance;
|
|
}
|
|
}
|
|
check(ClosestKinematicNode != TNumericLimits<int32>::Max());
|
|
|
|
ClosestKinematicNodes.Emplace(FMath::Sqrt(ClosestSquareDistance), ClosestKinematicNode);
|
|
}
|
|
|
|
// Only keep the first MaxNumAttachments closest kinematic nodes
|
|
if (ClosestKinematicNodes.Num() > MaxNumAttachments)
|
|
{
|
|
// Order all by distance, smallest first
|
|
ClosestKinematicNodes.Sort();
|
|
|
|
// Shrink the list... but not the array
|
|
ClosestKinematicNodes.SetNum(MaxNumAttachments, EAllowShrinking::No);
|
|
}
|
|
|
|
// Finally create the tethers between this dynamic node and the N closests kinematic ones
|
|
TetherSlots[Index].Reserve(ClosestKinematicNodes.Num());
|
|
|
|
for (const TPair<float, int32> ClosestKinematicNode : ClosestKinematicNodes)
|
|
{
|
|
const float Length = ClosestKinematicNode.Key;
|
|
const int32 KinematicNode = ClosestKinematicNode.Value;
|
|
|
|
TetherSlots[Index].Emplace(KinematicNode, DynamicNode, Length);
|
|
}
|
|
});
|
|
}
|
|
|
|
void FClothTetherDataPrivate::GenerateGeodesicTethers(const TConstArrayView<FVector3f>& Points,
|
|
TFunctionRef<bool(int32 Index)> IsKinematic)
|
|
{
|
|
check(KinematicNodeIslands.Num());
|
|
|
|
// Find all seeds in each island, kinematic tether end nodes connected to at least one non-tether end
|
|
int32 NumSeeds = 0;
|
|
TArray<TArray<int32>> SeedIslands;
|
|
SeedIslands.SetNum(KinematicNodeIslands.Num());
|
|
|
|
for (int32 IslandIndex = 0; IslandIndex < KinematicNodeIslands.Num(); ++IslandIndex)
|
|
{
|
|
const TArray<int32>& KinematicNodeIsland = KinematicNodeIslands[IslandIndex];
|
|
SeedIslands[IslandIndex].Reserve(KinematicNodeIsland.Num());
|
|
|
|
for (const int32 KinematicNode : KinematicNodeIsland)
|
|
{
|
|
const TSet<int32>& Neighbors = NodeToNeighbors[KinematicNode];
|
|
for (const int32 Neighbor : Neighbors)
|
|
{
|
|
if (!IsKinematic(Neighbor))
|
|
{
|
|
SeedIslands[IslandIndex].Add(KinematicNode);
|
|
++NumSeeds;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Consolidate into a single array for ease of iteration
|
|
TArray<int32> Seeds;
|
|
Seeds.Reserve(NumSeeds);
|
|
|
|
for (const TArray<int32>& SeedIsland : SeedIslands)
|
|
{
|
|
Seeds.Append(SeedIsland);
|
|
}
|
|
check(Seeds.Num() == NumSeeds);
|
|
|
|
// Dijkstra for each Kinematic Particle (assume a small number of kinematic points) - note this is N^2 log N with N kinematic points
|
|
// Find shortest paths from each seed node to all dynamic nodes
|
|
TMap<int32, TMap<int32, float>> GeodesicDistances; // Uses two maps instead of one map addressed by Seed and Node to avoid data races
|
|
GeodesicDistances.Reserve(Seeds.Num());
|
|
|
|
for (const int32 Seed : Seeds)
|
|
{
|
|
TMap<int32, float>& SeedGeodesicDistances = GeodesicDistances.Emplace(Seed);
|
|
SeedGeodesicDistances.Reserve(AllDynamicNodes.Num());
|
|
|
|
for (const int32 DynamicNode : AllDynamicNodes)
|
|
{
|
|
SeedGeodesicDistances.Emplace(DynamicNode, TNumericLimits<float>::Max());
|
|
}
|
|
}
|
|
|
|
ParallelFor(Seeds.Num(), [this, &Seeds, &GeodesicDistances, &Points, &IsKinematic](int32 Index)
|
|
{
|
|
const int32 Seed = Seeds[Index];
|
|
TMap<int32, float>& SeedGeodesicDistances = GeodesicDistances[Seed];
|
|
|
|
// Keep track of all visited nodes in a bit array
|
|
TBitArray<> VisitedNodes(false, Points.Num());
|
|
|
|
// Priority queue based implementation of the Dijkstra algorithm
|
|
#define CLOTHTETHERDATA_PROFILE_HEAPSIZE 0
|
|
#if CLOTHTETHERDATA_PROFILE_HEAPSIZE
|
|
static int32 MaxHeapSize = 1; // Record the max heap size
|
|
#else
|
|
static const int32 MaxHeapSize = 512; // Set the queue size to something large enough to avoid reallocations in most cases
|
|
#endif
|
|
auto LessPredicate = [&SeedGeodesicDistances](int32 Node1, int32 Node2) -> bool
|
|
{
|
|
return SeedGeodesicDistances[Node1] < SeedGeodesicDistances[Node2]; // Less for node priority
|
|
};
|
|
|
|
TArray<int32> Queue;
|
|
Queue.Reserve(MaxHeapSize);
|
|
Queue.Heapify(LessPredicate); // Turn the array into a priority queue
|
|
|
|
// Initiate the graph progression
|
|
VisitedNodes[Seed] = true;
|
|
Queue.HeapPush(Seed, LessPredicate);
|
|
|
|
do
|
|
{
|
|
int32 ParentNode;
|
|
Queue.HeapPop(ParentNode, LessPredicate, EAllowShrinking::No);
|
|
|
|
check(VisitedNodes[ParentNode]);
|
|
|
|
const float ParentDistance = (ParentNode != Seed) ? SeedGeodesicDistances[ParentNode] : 0.f;
|
|
|
|
const TSet<int32>& NeighborNodes = NodeToNeighbors[ParentNode];
|
|
for (const int32 NeighborNode : NeighborNodes)
|
|
{
|
|
check(NeighborNode != ParentNode);
|
|
|
|
// Do not progress onto kinematic nodes
|
|
if (IsKinematic(NeighborNode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Update the geodesic distance if this path is a shorter one
|
|
const float NewDistance = ParentDistance + (Points[NeighborNode] - Points[ParentNode]).Size();
|
|
|
|
float& GeodesicDistance = SeedGeodesicDistances[NeighborNode];
|
|
|
|
if (NewDistance < GeodesicDistance)
|
|
{
|
|
// Update this path distance
|
|
GeodesicDistance = NewDistance;
|
|
|
|
// Progress to this node position if it hasn't yet been visited
|
|
if (!VisitedNodes[NeighborNode])
|
|
{
|
|
VisitedNodes[NeighborNode] = true;
|
|
|
|
Queue.HeapPush(NeighborNode, LessPredicate);
|
|
|
|
#if CLOTHTETHERDATA_PROFILE_HEAPSIZE
|
|
MaxHeapSize = FMath::Max(Queue.Num(), MaxHeapSize);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
} while (Queue.Num());
|
|
});
|
|
|
|
// Initialize the tether slot arrays
|
|
TetherSlots.SetNum(DynamicTetherNodesDynamicIndex.Num());
|
|
|
|
for (TArray<FTether>& NewTethers : TetherSlots)
|
|
{
|
|
NewTethers.Reserve(MaxNumAttachments);
|
|
}
|
|
|
|
// Find the tether constraints starting from each dynamic node
|
|
ParallelFor(DynamicTetherNodesDynamicIndex.Num(), [this, &SeedIslands, &GeodesicDistances](int32 Index)
|
|
{
|
|
const int32 DynamicNode = AllDynamicNodes[DynamicTetherNodesDynamicIndex[Index]];
|
|
|
|
// Find the closest seeds in each island
|
|
TArray<int32, TInlineAllocator<32>> ClosestSeeds;
|
|
ClosestSeeds.Reserve(KinematicNodeIslands.Num());
|
|
|
|
for (const TArray<int32>& SeedIsland : SeedIslands)
|
|
{
|
|
int32 ClosestSeed = INDEX_NONE;
|
|
float ClosestDistance = TNumericLimits<float>::Max();
|
|
|
|
for (const int32 Seed : SeedIsland)
|
|
{
|
|
const float Distance = GeodesicDistances[Seed][DynamicNode];
|
|
if (Distance < ClosestDistance)
|
|
{
|
|
ClosestDistance = Distance;
|
|
ClosestSeed = Seed;
|
|
}
|
|
}
|
|
if (ClosestSeed != INDEX_NONE)
|
|
{
|
|
ClosestSeeds.Add(ClosestSeed);
|
|
}
|
|
}
|
|
|
|
// Sort all the tethers for this node based on smallest distance
|
|
ClosestSeeds.Sort([&GeodesicDistances, DynamicNode](int32 Seed1, int32 Seed2)
|
|
{
|
|
return GeodesicDistances[Seed1][DynamicNode] < GeodesicDistances[Seed2][DynamicNode];
|
|
});
|
|
|
|
// Keep only N closest tethers
|
|
if (ClosestSeeds.Num() > MaxNumAttachments)
|
|
{
|
|
ClosestSeeds.SetNum(MaxNumAttachments);
|
|
}
|
|
|
|
// Add these tethers to the N (or less) available slots
|
|
for (const int32 Seed : ClosestSeeds)
|
|
{
|
|
const float RefLength = GeodesicDistances[Seed][DynamicNode];
|
|
TetherSlots[Index].Emplace(Seed, DynamicNode, RefLength);
|
|
}
|
|
check(TetherSlots[Index].Num() <= MaxNumAttachments);
|
|
});
|
|
}
|
|
|
|
void FClothTetherDataPrivate::UpdateCounts()
|
|
{
|
|
// Update counts
|
|
TetherNums.Reserve(MaxNumAttachments);
|
|
for (const TArray<FTether>& TetherSlot : TetherSlots)
|
|
{
|
|
// Resize the count array whenever needed
|
|
const int32 NumUsedSlots = TetherSlot.Num();
|
|
TetherNums.SetNum(FMath::Max(TetherNums.Num(), NumUsedSlots));
|
|
|
|
// Increment each used slot count
|
|
for (int32 UsedSlot = 0; UsedSlot < NumUsedSlots; ++UsedSlot)
|
|
{
|
|
++TetherNums[UsedSlot];
|
|
}
|
|
}
|
|
} |