Files
UnrealEngine/Engine/Plugins/ChaosClothAssetEditor/Source/ChaosClothAssetDataflowNodes/Private/ChaosClothAsset/TransferSkinWeightsNode.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1044 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChaosClothAsset/TransferSkinWeightsNode.h"
#include "ChaosClothAsset/ClothPatternToDynamicMesh.h"
#include "ChaosClothAsset/ClothDataflowTools.h"
#include "ChaosClothAsset/ClothGeometryTools.h"
#include "ChaosClothAsset/CollectionClothFacade.h"
#include "Animation/Skeleton.h"
#include "Dataflow/DataflowInputOutput.h"
#include "DynamicMesh/DynamicBoneAttribute.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAABBTree3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h"
#include "DynamicMesh/MeshNormals.h"
#include "DynamicMesh/MeshTransforms.h"
#include "DynamicMesh/NonManifoldMappingSupport.h"
#include "Engine/SkeletalMesh.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "Operations/TransferBoneWeights.h"
#include "Rendering/SkeletalMeshLODImporterData.h"
#include "Rendering/SkeletalMeshModel.h"
#include "SkeletalMeshAttributes.h"
#include "Selections/MeshConnectedComponents.h"
#include "BoneWeights.h"
#include "ChaosClothAsset/ClothCollectionGroup.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "SkeletalMeshLODRenderDataToDynamicMesh.h"
#include "Tasks/Task.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(TransferSkinWeightsNode)
#define LOCTEXT_NAMESPACE "ChaosClothAssetTransferSkinWeightsNode"
static const FName InpaintWeightMaskName(TEXT("_InpaintWeightMask"));
namespace UE::Geometry
{
class FDynamicMesh3;
template<typename MeshType> class TMeshAABBTree3;
typedef TMeshAABBTree3<FDynamicMesh3> FDynamicMeshAABBTree3;
}
namespace UE::Chaos::ClothAsset::Private
{
/** Helper struct to pass the transfer settings around */
struct TransferBoneWeightsSettings
{
/**
* Settings for controlling which meshes to transfer to and from.
*/
bool bTransferToSim = true; // if true, transfer to sim mesh, otherwise skip sim mesh
bool bTransferToRender = true; // if true, transfer to render mesh, otherwise skip render mesh
bool bTransferToRenderFromSim = true; // if true, for render mesh only, transfer from the sim mesh, otherwise transfer from the source skeletal mesh
/**
* Shared Transfer Operator Settings
*/
bool bUseParallel = false;
int32 MaxNumInfluences = 8;
UE::Geometry::FTransferBoneWeights::ETransferBoneWeightsMethod TransferMethod = UE::Geometry::FTransferBoneWeights::ETransferBoneWeightsMethod::ClosestPointOnSurface;
/**
* Settings for the ETransferBoneWeightsMethod::InpaintWeights transfer method
*/
double NormalThreshold = 0;
double RadiusPercentage = 0;
bool LayeredMeshSupport = false;
int32 NumSmoothingIterations = 0;
double SmoothingStrength = 0;
FString InpaintMaskWeightMapName;
};
UE::Geometry::FDynamicMeshVertexSkinWeightsAttribute* GetOrCreateSkinWeightsAttribute(FDynamicMesh3& InMesh, const FName& InProfileName="Default")
{
checkSlow(InMesh.HasAttributes());
UE::Geometry::FDynamicMeshVertexSkinWeightsAttribute* Attribute = InMesh.Attributes()->GetSkinWeightsAttribute(InProfileName);
if (Attribute == nullptr)
{
Attribute = new UE::Geometry::FDynamicMeshVertexSkinWeightsAttribute(&InMesh);
InMesh.Attributes()->AttachSkinWeightsAttribute(InProfileName, Attribute);
}
return Attribute;
}
/**
* Convert the USkeletalMesh to DynamicMesh. If requested LOD was auto-generated, will convert render data instead of the mesh description.
*/
static bool SkeletalMeshToDynamicMesh(const USkeletalMesh* SkeletalMesh, const int32 LodIndex, FDynamicMesh3& ToDynamicMesh)
{
if (SkeletalMesh->HasMeshDescription(LodIndex))
{
const FMeshDescription* SourceMesh = SkeletalMesh->GetMeshDescription(LodIndex);
if (!SourceMesh)
{
return false;
}
FMeshDescriptionToDynamicMesh Converter;
Converter.Convert(SourceMesh, ToDynamicMesh);
}
else
{
const FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering();
if (!RenderData)
{
return false;
}
if (!RenderData->LODRenderData.IsValidIndex(LodIndex))
{
return false;
}
const FSkeletalMeshLODRenderData* SkeletalMeshLODRenderData = &(RenderData->LODRenderData[LodIndex]);
UE::Geometry::FSkeletalMeshLODRenderDataToDynamicMesh::ConversionOptions ConversionOptions;
ConversionOptions.bWantUVs = false;
ConversionOptions.bWantVertexColors = false;
ConversionOptions.bWantMaterialIDs = false;
ConversionOptions.bWantSkinWeights = true;
UE::Geometry::FSkeletalMeshLODRenderDataToDynamicMesh::Convert(SkeletalMeshLODRenderData, SkeletalMesh->GetRefSkeleton(), ConversionOptions, ToDynamicMesh);
}
return true;
}
/** Convert the ClothCollection to DynamicMesh. */
static bool ClothToDynamicMesh(
const TSharedRef<const FManagedArrayCollection>& ClothCollection,
const FReferenceSkeleton& TargetRefSkeleton, // the reference skeleton to add to the dynamic mesh
const bool bIsSim, // if true, Mesh will contain the sim mesh, otherwise the render mesh
UE::Geometry::FDynamicMesh3& Mesh) // the resulting sim or render mesh
{
using namespace UE::Geometry;
using namespace UE::Chaos::ClothAsset;
// Check if ClothCollection is empty
FCollectionClothConstFacade ClothFacade(ClothCollection);
const int32 NumVertices = bIsSim ? ClothFacade.GetNumSimVertices3D() : ClothFacade.GetNumRenderVertices();
const int32 NumFaces = bIsSim ? ClothFacade.GetNumSimFaces() : ClothFacade.GetNumRenderFaces();
if (NumVertices <= 0 || NumFaces <= 0)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to convert the Cloth Collection to Dynamic Mesh. Cloth Collection is empty."));
return false;
}
// Convert the sim mesh to DynamicMesh.
FClothPatternToDynamicMesh PatternToDynamicMesh;
constexpr bool bDisableAttributes = false;
const EClothPatternVertexType PatternType = bIsSim ? EClothPatternVertexType::Sim3D : EClothPatternVertexType::Render;
PatternToDynamicMesh.Convert(ClothCollection, INDEX_NONE, PatternType, Mesh, bDisableAttributes);
// Setup the skeleton.
// @note we can't simply copy the bone attributes from the source USkeletalMesh because the cloth
// asset reference skeleton comes from the USkeleton not the USkeletalMesh
Mesh.Attributes()->EnableBones(TargetRefSkeleton.GetRawBoneNum());
for (int32 BoneIdx = 0; BoneIdx < TargetRefSkeleton.GetRawBoneNum(); ++BoneIdx)
{
Mesh.Attributes()->GetBoneNames()->SetValue(BoneIdx, TargetRefSkeleton.GetRawRefBoneInfo()[BoneIdx].Name);
}
return true;
}
/** Copy the skin weights from DynamicMesh to Render Collection. */
static void CopySkinWeightsFromDynamicMeshToRenderCloth(const FDynamicMesh3& RenderMesh,
const bool bUseParallel,
const TSharedRef<FManagedArrayCollection>& ClothCollection)
{
using namespace UE::Geometry;
UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(ClothCollection);
ParallelFor(RenderMesh.MaxVertexID(), [&ClothFacade, &RenderMesh](int32 VertexID)
{
const FDynamicMeshVertexSkinWeightsAttribute* OutAttribute = RenderMesh.Attributes()->GetSkinWeightsAttribute(FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
checkSlow(OutAttribute);
checkSlow(RenderMesh.IsVertex(VertexID));
checkSlow(VertexID < ClothFacade.GetNumRenderVertices());
OutAttribute->GetValue(VertexID, ClothFacade.GetRenderBoneIndices()[VertexID],
ClothFacade.GetRenderBoneWeights()[VertexID]);
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
/** Copy the skin weights from DynamicMesh to Sim Collection, handling split vertices. */
static void CopySkinWeightsFromDynamicMeshToSimCloth(const FDynamicMesh3& WeldedSimMesh,
const bool bUseParallel,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const int32 MaxNumInfluences)
{
using namespace UE::Geometry;
UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(ClothCollection);
//
// Copy the new bone weight data from the welded sim mesh back to the cloth patterns.
//
const FNonManifoldMappingSupport NonManifoldMapping(WeldedSimMesh);
TArray<TArray<int32>> SimMeshToDynamicMesh;
if (NonManifoldMapping.IsNonManifoldVertexInSource())
{
// WeldedSimMesh indices don't match cloth collection.
SimMeshToDynamicMesh.SetNum(ClothFacade.GetNumSimVertices3D());
for (int32 DynamicMeshVert = 0; DynamicMeshVert < WeldedSimMesh.VertexCount(); ++DynamicMeshVert)
{
SimMeshToDynamicMesh[NonManifoldMapping.GetOriginalNonManifoldVertexID(DynamicMeshVert)].Add(DynamicMeshVert);
}
ParallelFor(ClothFacade.GetNumSimVertices3D(), [&ClothFacade, &WeldedSimMesh, &SimMeshToDynamicMesh, MaxNumInfluences](int32 SimVertexID)
{
const FDynamicMeshVertexSkinWeightsAttribute* const OutAttribute = WeldedSimMesh.Attributes()->GetSkinWeightsAttribute(FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
checkSlow(OutAttribute);
if (!ensure(SimMeshToDynamicMesh[SimVertexID].Num() > 0))
{
ClothFacade.GetSimBoneIndices()[SimVertexID].Reset();
ClothFacade.GetSimBoneWeights()[SimVertexID].Reset();
return;
}
if (SimMeshToDynamicMesh[SimVertexID].Num() == 1)
{
// Simple most common case, one-to-one correspondence. Just copy over.
const int32 WeldedID = SimMeshToDynamicMesh[SimVertexID][0];
checkSlow(WeldedSimMesh.IsVertex(WeldedID));
OutAttribute->GetValue(WeldedID, ClothFacade.GetSimBoneIndices()[SimVertexID],
ClothFacade.GetSimBoneWeights()[SimVertexID]);
}
else
{
// Need to merge data because dynamic mesh split the original vertex
TMap<int32, TPair<float, int32>> CombinedData;
for (const int32 WeldedID : SimMeshToDynamicMesh[SimVertexID])
{
TArray<int32> Indices;
TArray<float> Weights;
checkSlow(WeldedSimMesh.IsVertex(WeldedID));
OutAttribute->GetValue(WeldedID, Indices, Weights);
check(Indices.Num() == Weights.Num());
for (int32 Idx = 0; Idx < Indices.Num(); ++Idx)
{
TPair<float, int32>& WeightedFloat = CombinedData.FindOrAdd(Indices[Idx]);
WeightedFloat.Get<0>() += Weights[Idx];
WeightedFloat.Get<1>() += 1;
}
}
TArray<int32>& BoneIndices = ClothFacade.GetSimBoneIndices()[SimVertexID];
TArray<float>& BoneWeights = ClothFacade.GetSimBoneWeights()[SimVertexID];
BoneIndices.Reset(CombinedData.Num());
BoneWeights.Reset(CombinedData.Num());
float WeightsSum = 0.f;
for (TMap<int32, TPair<float, int32>>::TConstIterator CombinedDataIter = CombinedData.CreateConstIterator(); CombinedDataIter; ++CombinedDataIter)
{
check(CombinedDataIter.Value().Get<1>() > 0);
BoneIndices.Add(CombinedDataIter.Key());
const float FloatVal = CombinedDataIter.Value().Get<0>() / (float)CombinedDataIter.Value().Get<1>();
BoneWeights.Add(FloatVal);
WeightsSum += FloatVal;
}
if (BoneIndices.Num() > MaxNumInfluences)
{
// Choose MaxNumInfluences highest weighted bones.
TArray<TPair<float, int32>> SortableData;
SortableData.Reserve(BoneIndices.Num());
for (int32 Idx = 0; Idx < BoneIndices.Num(); ++Idx)
{
SortableData.Emplace(BoneWeights[Idx], BoneIndices[Idx]);
}
SortableData.Sort([](const TPair<float, int32>& A, const TPair<float, int32>& B) { return A > B; });
BoneIndices.SetNum(MaxNumInfluences);
BoneWeights.SetNum(MaxNumInfluences);
WeightsSum = 0.f;
for (int32 Idx = 0; Idx < MaxNumInfluences; ++Idx)
{
BoneIndices[Idx] = SortableData[Idx].Get<1>();
BoneWeights[Idx] = SortableData[Idx].Get<0>();
WeightsSum += SortableData[Idx].Get<0>();
}
}
// Normalize weights
const float WeightsSumRecip = WeightsSum > UE_SMALL_NUMBER ? 1.f / WeightsSum : 0.f;
for (float& Weight : BoneWeights)
{
Weight *= WeightsSumRecip;
}
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
else
{
ParallelFor(WeldedSimMesh.MaxVertexID(), [&ClothFacade, &WeldedSimMesh, &NonManifoldMapping](int32 WeldedID)
{
const FDynamicMeshVertexSkinWeightsAttribute* OutAttribute = WeldedSimMesh.Attributes()->GetSkinWeightsAttribute(FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
checkSlow(OutAttribute);
checkSlow(WeldedSimMesh.IsVertex(WeldedID));
checkSlow(WeldedID < ClothFacade.GetNumSimVertices3D());
OutAttribute->GetValue(WeldedID, ClothFacade.GetSimBoneIndices()[WeldedID],
ClothFacade.GetSimBoneWeights()[WeldedID]);
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
}
/** Copy the inpaint mask array to Collection, handling split vertices. */
static void CopyInpaintMapFromDynamicMeshToSimCloth(const FDynamicMesh3& WeldedSimMesh,
const FName WeightMapName,
const bool bUseParallel,
const TArray<bool>& MatchedVertices,
const TSharedRef<FManagedArrayCollection>& ClothCollection)
{
using namespace UE::Geometry;
UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(ClothCollection);
TArrayView<float> InpaintWeightMask = ClothFacade.GetWeightMap(WeightMapName);
const FNonManifoldMappingSupport NonManifoldMapping(WeldedSimMesh);
TArray<TArray<int32>> SimMeshToDynamicMesh;
if (NonManifoldMapping.IsNonManifoldVertexInSource())
{
// WeldedSimMesh indices don't match cloth collection.
SimMeshToDynamicMesh.SetNum(ClothFacade.GetNumSimVertices3D());
for (int32 DynamicMeshVert = 0; DynamicMeshVert < WeldedSimMesh.VertexCount(); ++DynamicMeshVert)
{
SimMeshToDynamicMesh[NonManifoldMapping.GetOriginalNonManifoldVertexID(DynamicMeshVert)].Add(DynamicMeshVert);
}
ParallelFor(ClothFacade.GetNumSimVertices3D(), [&ClothFacade, &WeldedSimMesh, &SimMeshToDynamicMesh, &InpaintWeightMask, &MatchedVertices](int32 SimVertexID)
{
if (!ensure(SimMeshToDynamicMesh[SimVertexID].Num() > 0))
{
return;
}
if (SimMeshToDynamicMesh[SimVertexID].Num() == 1)
{
const int32 WeldedID = SimMeshToDynamicMesh[SimVertexID][0];
checkSlow(WeldedSimMesh.IsVertex(WeldedID));
InpaintWeightMask[SimVertexID] = MatchedVertices[WeldedID] ? 1.0f : 0.0f;
}
else
{
const int32 WeldedID = SimMeshToDynamicMesh[SimVertexID][0]; // Any welded id can be used here
InpaintWeightMask[SimVertexID] = MatchedVertices[WeldedID] ? 1.0f : 0.0f;
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
else
{
ParallelFor(WeldedSimMesh.MaxVertexID(), [&ClothFacade, &WeldedSimMesh, &NonManifoldMapping, &InpaintWeightMask, &MatchedVertices](int32 WeldedID)
{
checkSlow(WeldedSimMesh.IsVertex(WeldedID));
checkSlow(WeldedID < ClothFacade.GetNumSimVertices3D());
InpaintWeightMask[WeldedID] = MatchedVertices[WeldedID] ? 1.0f : 0.0f;
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
}
/**
* Transfer skin weights from the source to the target dynamic mesh.
* The target mesh is split into connected components and the transfer is run on each component separately.
*/
static bool TransferInpaintWeightsToMeshComponents(const FReferenceSkeleton& TargetRefSkeleton,
const UE::Geometry::FDynamicMesh3& SourceDynamicMesh,
UE::Geometry::FDynamicMesh3& TargetDynamicMesh,
const TransferBoneWeightsSettings& TransferSettings)
{
using namespace UE::Geometry;
// Find connected-components
FMeshConnectedComponents ConnectedComponents(&TargetDynamicMesh);
ConnectedComponents.FindConnectedTriangles();
// Pointer to the weight layer containing force inpaint mask (if one exists)
const FDynamicMeshWeightAttribute* ForceInpaintWeightLayer = nullptr;
if (!TransferSettings.InpaintMaskWeightMapName.IsEmpty())
{
for (int32 Idx = 0; Idx < TargetDynamicMesh.Attributes()->NumWeightLayers(); ++Idx)
{
const FDynamicMeshWeightAttribute* WeightLayer = TargetDynamicMesh.Attributes()->GetWeightLayer(Idx);
if (WeightLayer && WeightLayer->GetName() == FName(TransferSettings.InpaintMaskWeightMapName))
{
ForceInpaintWeightLayer = WeightLayer;
break;
}
}
}
// Iterate over each component and perform per-component skin weight transfer
const int32 NumComponents = ConnectedComponents.Num();
const int32 NumTasks = FMath::Max(FMath::Min(FTaskGraphInterface::Get().GetNumWorkerThreads(), NumComponents), 1);
constexpr int32 MinComponentByTask = 1;
const int32 ComnponentsByTask = FMath::Max(FMath::Max(FMath::DivideAndRoundUp(NumComponents, NumTasks), MinComponentByTask), 1);
const int32 NumBatches = FMath::DivideAndRoundUp(NumComponents, ComnponentsByTask);
TArray<UE::Tasks::FTask> PendingTasks;
PendingTasks.Reserve(NumBatches);
TArray<FDynamicMesh3> Submeshes;
Submeshes.SetNum(NumComponents);
TArray<TArray<int32>> SubmeshToBaseVs;
SubmeshToBaseVs.SetNum(NumComponents);
std::atomic<bool> bTransferParametersInvalid(false);
// Creating the BVH here in single threaded which is expensive allow to avoid every thread to pay the expensive cost
FDynamicMeshAABBTree3 SourceBVH(&SourceDynamicMesh);
for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
{
const int32 StartIndex = BatchIndex * ComnponentsByTask;
int32 EndIndex = (BatchIndex + 1) * ComnponentsByTask;
EndIndex = BatchIndex == NumBatches - 1 ? FMath::Min(NumComponents, EndIndex) : EndIndex;
UE::Tasks::FTask PendingTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [StartIndex, EndIndex, &TargetRefSkeleton, &ConnectedComponents,
&TargetDynamicMesh, &ForceInpaintWeightLayer, &TransferSettings, &SourceDynamicMesh, &SourceBVH, &Submeshes, &SubmeshToBaseVs, &bTransferParametersInvalid]()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TransferInpaintWeightsToMeshComponentsTask);
if (bTransferParametersInvalid.load(std::memory_order_relaxed))
{
return;
}
// Setup transfer weights operator
FTransferBoneWeights TransferBoneWeights(&SourceDynamicMesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName, &SourceBVH);
TransferBoneWeights.bUseParallel = TransferSettings.bUseParallel;
TransferBoneWeights.MaxNumInfluences = TransferSettings.MaxNumInfluences;
TransferBoneWeights.TransferMethod = TransferSettings.TransferMethod;
TransferBoneWeights.NormalThreshold = FMathd::DegToRad * TransferSettings.NormalThreshold;
TransferBoneWeights.SearchRadius = TransferSettings.RadiusPercentage * TargetDynamicMesh.GetBounds().DiagonalLength();
TransferBoneWeights.NumSmoothingIterations = TransferSettings.NumSmoothingIterations;
TransferBoneWeights.SmoothingStrength = TransferSettings.SmoothingStrength;
TransferBoneWeights.LayeredMeshSupport = TransferSettings.LayeredMeshSupport; // multilayerd clothing
if (TransferBoneWeights.Validate() != EOperationValidationResult::Ok)
{
bTransferParametersInvalid.store(true, std::memory_order_relaxed);
return;
}
for (int32 ComponentIdx = StartIndex; ComponentIdx < EndIndex; ++ComponentIdx)
{
// Mesh containing the geometry of the current component
FDynamicMesh3& Submesh = Submeshes[ComponentIdx];
Submesh.EnableAttributes();
Submesh.Attributes()->EnableBones(TargetRefSkeleton.GetRawBoneNum());
for (int32 BoneIdx = 0; BoneIdx < TargetRefSkeleton.GetRawBoneNum(); ++BoneIdx)
{
Submesh.Attributes()->GetBoneNames()->SetValue(BoneIdx, TargetRefSkeleton.GetRawRefBoneInfo()[BoneIdx].Name);
}
// Keep track of the maps between Mesh and the component submesh vertex/triangle indices
TMap<int32, int32> BaseToSubmeshV;
TArray<int32>& SubmeshToBaseV = SubmeshToBaseVs[ComponentIdx];
TArray<int32> SubmeshToBaseT;
const TArray<int32>& ComponentTris = ConnectedComponents[ComponentIdx].Indices;
for (const int32 TID : ComponentTris)
{
const FIndex3i Triangle = TargetDynamicMesh.GetTriangle(TID);
FIndex3i NewTriangle;
for (int32 TriCornerIdx = 0; TriCornerIdx < 3; ++TriCornerIdx)
{
const int32* FoundIdx = BaseToSubmeshV.Find(Triangle[TriCornerIdx]);
if (FoundIdx)
{
NewTriangle[TriCornerIdx] = *FoundIdx;
}
else
{
const FVector3d Position = TargetDynamicMesh.GetVertex(Triangle[TriCornerIdx]);
const int32 NewVtxID = Submesh.AppendVertex(Position);
check(NewVtxID == SubmeshToBaseV.Num());
SubmeshToBaseV.Add(Triangle[TriCornerIdx]);
BaseToSubmeshV.Add(Triangle[TriCornerIdx], NewVtxID);
NewTriangle[TriCornerIdx] = NewVtxID;
}
}
const int32 NewTriID = Submesh.AppendTriangle(NewTriangle);
check(NewTriID == SubmeshToBaseT.Num());
SubmeshToBaseT.Add(TID);
}
// Copy over the force inpaint values
if (ForceInpaintWeightLayer != nullptr)
{
TArray<float> ForceInpaint;
ForceInpaint.SetNum(Submesh.MaxVertexID());
for (int32 VID = 0; VID < Submesh.MaxVertexID(); ++VID)
{
float Value = 0;
ForceInpaintWeightLayer->GetValue(SubmeshToBaseV[VID], &Value);
ForceInpaint[VID] = Value;
}
// Set the mask in the transfer operator
TransferBoneWeights.ForceInpaint = MoveTemp(ForceInpaint);
}
// Transfer weights to the current submesh only. If transfer using the inpaint method fails, fall back to
// the closest point method which should always succeed. Common reason for the failure is if we didn't find
// any matches on the source at all with the current transfer settings.
if (!TransferBoneWeights.TransferWeightsToMesh(Submesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to transfer skin weights to some of the vertices of the render mesh using inpaint method, falling back to closest point method."));
// We can reuse the same operator but change the method type
TransferBoneWeights.TransferMethod = FTransferBoneWeights::ETransferBoneWeightsMethod::ClosestPointOnSurface;
// Ignore radius and normal settings, so all points on the source are considered
const double OldSearchRadius = TransferBoneWeights.SearchRadius;
const double OldNormalThreshold = TransferBoneWeights.NormalThreshold;
TransferBoneWeights.SearchRadius = -1;
TransferBoneWeights.NormalThreshold = -1;
// This should always succeed
if (!ensure(TransferBoneWeights.TransferWeightsToMesh(Submesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName)))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to transfer skin weights to some of the vertices of the render mesh."));
}
// Revert back the settings
TransferBoneWeights.TransferMethod = TransferSettings.TransferMethod;
TransferBoneWeights.SearchRadius = OldSearchRadius;
TransferBoneWeights.NormalThreshold = OldNormalThreshold;
}
}
});
PendingTasks.Add(PendingTask);
}
UE::Tasks::Wait(PendingTasks);
if (bTransferParametersInvalid)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Transfer method parameters are invalid."));
return false;
}
for (int32 ComponentIdx = 0; ComponentIdx < NumComponents; ++ComponentIdx)
{
TArray<int32>& SubmeshToBaseV = SubmeshToBaseVs[ComponentIdx];
// Copy over the data from the submesh to the base mesh
FDynamicMeshVertexSkinWeightsAttribute* SubMeshSkinWeights = UE::Chaos::ClothAsset::Private::GetOrCreateSkinWeightsAttribute(Submeshes[ComponentIdx]);
FDynamicMeshVertexSkinWeightsAttribute* BaseMeshSkinWeights = UE::Chaos::ClothAsset::Private::GetOrCreateSkinWeightsAttribute(TargetDynamicMesh);
for (int32 SubMeshVID = 0; SubMeshVID < SubmeshToBaseV.Num(); ++SubMeshVID)
{
const int32 BaseMeshVID = SubmeshToBaseV[SubMeshVID];
UE::AnimationCore::FBoneWeights Weights;
SubMeshSkinWeights->GetValue(SubMeshVID, Weights);
BaseMeshSkinWeights->SetValue(BaseMeshVID, Weights);
}
}
return true;
}
/** Transfer skin weights to sim cloth. */
static bool TransferInpaintWeightsToSim(
const FReferenceSkeleton& TargetRefSkeleton,
const UE::Geometry::FDynamicMesh3& SourceDynamicMesh,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const TransferBoneWeightsSettings& TransferSettings,
UE::Geometry::FDynamicMesh3& WeldedSimMesh)
{
using namespace UE::Geometry;
UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(ClothCollection);
// Convert cloth sim mesh LOD to the welded dynamic sim mesh.
if (!ClothToDynamicMesh(ClothCollection, TargetRefSkeleton, true, WeldedSimMesh))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to weld the simulation mesh for LOD."));
return false;
}
// Transfer the weights from the body to the welded sim mesh.
// TODO: run the transfer on components instead
FTransferBoneWeights TransferBoneWeights(&SourceDynamicMesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
TransferBoneWeights.bUseParallel = TransferSettings.bUseParallel;
TransferBoneWeights.MaxNumInfluences = TransferSettings.MaxNumInfluences;
TransferBoneWeights.TransferMethod = TransferSettings.TransferMethod;
TransferBoneWeights.NormalThreshold = FMathd::DegToRad * TransferSettings.NormalThreshold;
TransferBoneWeights.SearchRadius = TransferSettings.RadiusPercentage * WeldedSimMesh.GetBounds().DiagonalLength();
TransferBoneWeights.NumSmoothingIterations = TransferSettings.NumSmoothingIterations;
TransferBoneWeights.SmoothingStrength = TransferSettings.SmoothingStrength;
TransferBoneWeights.LayeredMeshSupport = TransferSettings.LayeredMeshSupport; // multilayerd clothing
TransferBoneWeights.ForceInpaintWeightMapName = FName(TransferSettings.InpaintMaskWeightMapName);
if (TransferBoneWeights.Validate() != EOperationValidationResult::Ok)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Transfer method parameters are invalid."));
return false;
}
if (!TransferBoneWeights.TransferWeightsToMesh(WeldedSimMesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Transferring skin weights failed."));
return false;
}
ClothFacade.AddWeightMap(InpaintWeightMaskName);
// Copy the new bone weight data and inpaint mask from the welded sim mesh back to the sim cloth patterns.
CopySkinWeightsFromDynamicMeshToSimCloth(WeldedSimMesh, true, ClothCollection, TransferSettings.MaxNumInfluences);
CopyInpaintMapFromDynamicMeshToSimCloth(WeldedSimMesh, InpaintWeightMaskName, true, TransferBoneWeights.MatchedVertices, ClothCollection);
return true;
}
static bool TransferClosestPointOnSurface(
const FReferenceSkeleton& TargetRefSkeleton,
const FDynamicMesh3& SkeletalDynamicMesh,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const TransferBoneWeightsSettings& TransferSettings,
const TSharedPtr<const FManagedArrayCollection>& SimClothCollection);
/** Transfer skin weights to render cloth. */
static bool TransferInpaintWeightsToRender(
const FReferenceSkeleton& TargetRefSkeleton,
const UE::Geometry::FDynamicMesh3& SourceDynamicMesh,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const TransferBoneWeightsSettings& TransferSettings)
{
using namespace UE::Geometry;
// Convert cloth render mesh LOD to the dynamic render mesh.
FDynamicMesh3 RenderDynamicMesh;
if (!ClothToDynamicMesh(ClothCollection, TargetRefSkeleton, false, RenderDynamicMesh))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to create the render dynamic mesh for LOD."));
return false;
}
const FNonManifoldMappingSupport NonManifoldMapping(RenderDynamicMesh);
if (NonManifoldMapping.IsNonManifoldVertexInSource())
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Render mesh inpaint method failed due to non manifold geometry, falling back to closest point method instead."));
checkf(!TransferSettings.bTransferToRenderFromSim, TEXT("Logic error, TransferClosestPointOnSurface should have been called instead transferring from Sim mesh."));
TransferBoneWeightsSettings NewTransferSettings = TransferSettings;
NewTransferSettings.bTransferToSim = false;
NewTransferSettings.TransferMethod = FTransferBoneWeights::ETransferBoneWeightsMethod::ClosestPointOnSurface;
TSharedPtr<const FManagedArrayCollection> EmptySimClothCollection;
return TransferClosestPointOnSurface(TargetRefSkeleton, SourceDynamicMesh, ClothCollection, NewTransferSettings, EmptySimClothCollection);
}
else
{
// Transfer weights to render mesh
if (!TransferInpaintWeightsToMeshComponents(TargetRefSkeleton, SourceDynamicMesh, RenderDynamicMesh, TransferSettings))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Failed to transfer skin weights to render mesh."));
return false;
}
}
CopySkinWeightsFromDynamicMeshToRenderCloth(RenderDynamicMesh, TransferSettings.bUseParallel, ClothCollection);
return true;
}
/** Transfer skin weights to sim and render cloth. */
static bool TransferInpaintWeights(
const FReferenceSkeleton& TargetRefSkeleton,
const UE::Geometry::FDynamicMesh3& SourceDynamicMesh,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const TransferBoneWeightsSettings& TransferSettings,
const TSharedPtr<const FManagedArrayCollection>& SimClothCollection)
{
using namespace UE::Geometry;
FDynamicMesh3 WeldedSimMesh;
if (TransferSettings.bTransferToSim)
{
//
// Convert cloth sim mesh LOD to the welded dynamic sim mesh and transfer weights.
//
if (!TransferInpaintWeightsToSim(TargetRefSkeleton, SourceDynamicMesh, ClothCollection, TransferSettings, WeldedSimMesh))
{
return false;
}
}
if (TransferSettings.bTransferToRender)
{
//
// Compute the bone weights for the render mesh by transferring weights from the sim mesh
//
if (TransferSettings.bTransferToRenderFromSim)
{
ensureMsgf(TransferSettings.bTransferToSim, TEXT("The UI shouldn't allow a sim mesh InpaintWeights transfer without the sim mesh set to transfer, check the EditConditions."));
// Transfers from the sim mesh most likely mean that a SkeletalMesh InpaintWeights transfer is not working for this render mesh,
// therefore it is best to assume that a closest point transfer will always provide a better result in this particular case.
TransferBoneWeightsSettings NewTransferSettings = TransferSettings;
NewTransferSettings.bTransferToSim = false;
NewTransferSettings.TransferMethod = FTransferBoneWeights::ETransferBoneWeightsMethod::ClosestPointOnSurface;
return TransferClosestPointOnSurface(TargetRefSkeleton, SourceDynamicMesh, ClothCollection, NewTransferSettings, SimClothCollection);
}
else
{
// Transfer skin weights to render cloth from the skeletal asset mesh
if (!TransferInpaintWeightsToRender(TargetRefSkeleton, SourceDynamicMesh, ClothCollection, TransferSettings))
{
return false;
}
}
}
return true;
}
static bool TransferClosestPointOnSurface(
const FReferenceSkeleton& TargetRefSkeleton,
const FDynamicMesh3& SkeletalDynamicMesh,
const TSharedRef<FManagedArrayCollection>& ClothCollection,
const TransferBoneWeightsSettings& TransferSettings,
const TSharedPtr<const FManagedArrayCollection>& SimClothCollection)
{
using namespace UE::Geometry;
FCollectionClothFacade ClothFacade(ClothCollection);
//
// Compute the bone index mappings. This allows the transfer operator to retarget weights to the correct skeleton.
//
TMap<FName, FBoneIndexType> TargetBoneToIndex;
TargetBoneToIndex.Reserve(TargetRefSkeleton.GetRawBoneNum());
for (int32 BoneIdx = 0; BoneIdx < TargetRefSkeleton.GetRawBoneNum(); ++BoneIdx)
{
TargetBoneToIndex.Add(TargetRefSkeleton.GetRawRefBoneInfo()[BoneIdx].Name, BoneIdx);
}
if (TransferSettings.bTransferToSim)
{
//
// Transfer weights to the sim mesh.
//
FTransferBoneWeights TransferBoneWeights(&SkeletalDynamicMesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
TransferBoneWeights.bUseParallel = TransferSettings.bUseParallel;
TransferBoneWeights.MaxNumInfluences = TransferSettings.MaxNumInfluences;
TransferBoneWeights.TransferMethod = TransferSettings.TransferMethod;
if (TransferBoneWeights.Validate() != EOperationValidationResult::Ok)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Transfer method parameters are invalid."));
return false;
}
ParallelFor(ClothFacade.GetNumSimVertices3D(), [&TransferBoneWeights, &TargetBoneToIndex, &ClothFacade](int32 VertexID)
{
TransferBoneWeights.TransferWeightsToPoint(ClothFacade.GetSimBoneIndices()[VertexID],
ClothFacade.GetSimBoneWeights()[VertexID],
ClothFacade.GetSimPosition3D()[VertexID],
&TargetBoneToIndex);
}, TransferSettings.bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
if (TransferSettings.bTransferToRender)
{
//
// Transfer weights to the render mesh.
//
const FDynamicMesh3* SourceMeshToTransferFrom = &SkeletalDynamicMesh; // transfer from body
FDynamicMesh3 WeldedSimMesh;
if (TransferSettings.bTransferToRenderFromSim)
{
// Convert sim cloth to dynamic mesh, use a different sim cloth collection if needed
constexpr bool bIsSimMesh = true;
if (!ClothToDynamicMesh(SimClothCollection.IsValid() ? SimClothCollection.ToSharedRef() : ClothCollection, TargetRefSkeleton, bIsSimMesh, WeldedSimMesh))
{
return false;
}
SourceMeshToTransferFrom = &WeldedSimMesh; // transfer from sim mesh instead
}
FTransferBoneWeights TransferBoneWeights(SourceMeshToTransferFrom, FSkeletalMeshAttributes::DefaultSkinWeightProfileName);
TransferBoneWeights.bUseParallel = TransferSettings.bUseParallel;
TransferBoneWeights.TransferMethod = TransferSettings.TransferMethod;
TransferBoneWeights.MaxNumInfluences = TransferSettings.MaxNumInfluences;
if (TransferBoneWeights.Validate() != EOperationValidationResult::Ok)
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Transfer method parameters are invalid."));
return false;
}
ParallelFor(ClothFacade.GetNumRenderVertices(), [&TransferBoneWeights, &TargetBoneToIndex, &ClothFacade](int32 VertexID)
{
TransferBoneWeights.TransferWeightsToPoint(ClothFacade.GetRenderBoneIndices()[VertexID],
ClothFacade.GetRenderBoneWeights()[VertexID],
ClothFacade.GetRenderPosition()[VertexID],
&TargetBoneToIndex);
}, TransferSettings.bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
return true;
}
}
FChaosClothAssetTransferSkinWeightsNode::FChaosClothAssetTransferSkinWeightsNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&SkeletalMesh);
RegisterInputConnection(&LodIndex);
RegisterInputConnection(&SimCollection)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&InpaintMask.WeightMap, GET_MEMBER_NAME_CHECKED(FChaosClothAssetWeightedValueNonAnimatableNoLowHighRange, WeightMap))
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterOutputConnection(&Collection, &Collection);
}
void FChaosClothAssetTransferSkinWeightsNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FChaosClothAssetTransferSkinWeightsNode_Evaluate);
using namespace UE::Chaos::ClothAsset;
using namespace UE::Chaos::ClothAsset::Private;
using namespace UE::Geometry;
if (Out->IsA<FManagedArrayCollection>(&Collection))
{
// Evaluate inputs
FManagedArrayCollection InputCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> ClothCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InputCollection));
const TSharedPtr<FManagedArrayCollection> SimClothCollection = IsConnected(&SimCollection) ?
MakeShared<FManagedArrayCollection>(GetValue(Context, &SimCollection)) :
TSharedPtr<FManagedArrayCollection>();
FCollectionClothFacade ClothFacade(ClothCollection);
if (ClothFacade.IsValid()) // Can only act on the collection if it is a valid cloth collection
{
TStrongObjectPtr<const USkeletalMesh> InSkeletalMesh;
const FReferenceSkeleton* TargetRefSkeleton = nullptr;
FDynamicMesh3 SourceDynamicMesh;
const bool bNeedsSkeletalMesh =
TargetMeshType != EChaosClothAssetTransferTargetMeshType::Render ||
RenderMeshSourceType == EChaosClothAssetTransferRenderMeshSource::SkeletalMesh;
if (bNeedsSkeletalMesh)
{
InSkeletalMesh = TStrongObjectPtr<const USkeletalMesh>(GetValue(Context, &SkeletalMesh));
if (!InSkeletalMesh)
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidSkeletalMeshHeadline", "Invalid Skeletal Mesh."),
FText::Format(
LOCTEXT("InvalidSkeletalMeshDetails", "No skeletal mesh has been specified and one is required for this type of transfer."),
LodIndex));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
int32 InLodIndex = GetValue(Context, &LodIndex);
if (!InSkeletalMesh->IsValidLODIndex(InLodIndex))
{
const int32 LastLODIndex = InSkeletalMesh->GetLODNum() - 1;
if (InSkeletalMesh->IsValidLODIndex(LastLODIndex))
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidLodIndexHeadline", "Invalid LOD Index."),
FText::Format(
LOCTEXT("InvalidLodIndexSubstituteDetails", "LOD index {0} is not a valid LOD for skeletal mesh {1}. Using LOD index {2} instead."),
InLodIndex,
FText::FromString(InSkeletalMesh->GetName()),
LastLODIndex));
InLodIndex = LastLODIndex;
}
else
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidLodIndexHeadline", "Invalid LOD Index."),
FText::Format(
LOCTEXT("InvalidLodIndexDetails", "LOD index {0} is not a valid LOD for skeletal mesh {1}."),
InLodIndex,
FText::FromString(InSkeletalMesh->GetName())));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
}
if (!SkeletalMeshToDynamicMesh(InSkeletalMesh.Get(), InLodIndex, SourceDynamicMesh))
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidLodHeadline", "Could not convert LOD to Dynamic Mesh."),
FText::Format(
LOCTEXT("InvalidLodDetails", "Could not convert LOD index {0} of the skeletal mesh {1} to dyanamic mesh."),
InLodIndex,
FText::FromString(InSkeletalMesh->GetName())));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
MeshTransforms::ApplyTransform(SourceDynamicMesh, Transform, true);
TargetRefSkeleton = &InSkeletalMesh->GetRefSkeleton();
ClothFacade.SetSkeletalMeshSoftObjectPathName(InSkeletalMesh->GetPathName());
}
else
{
// Reuse the input sim mesh skeleton
auto GetCollectionRefSkeleton = [&InSkeletalMesh](const FCollectionClothConstFacade& ClothFacade) -> const FReferenceSkeleton*
{
const FSoftObjectPath& SkeletalMeshPathName = ClothFacade.GetSkeletalMeshSoftObjectPathName();
InSkeletalMesh = TStrongObjectPtr<const USkeletalMesh>(Cast<USkeletalMesh>(SkeletalMeshPathName.TryLoad()));
return InSkeletalMesh ? &InSkeletalMesh->GetRefSkeleton() : nullptr;
};
if (SimClothCollection.IsValid())
{
FCollectionClothConstFacade SimClothFacade(SimClothCollection.ToSharedRef());
if (SimClothFacade.IsValid())
{
TargetRefSkeleton = GetCollectionRefSkeleton(SimClothFacade);
}
if (!TargetRefSkeleton)
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidSimRefSkeletonHeadline", "Invalid Reference Skeleton."),
LOCTEXT("InvalidSimRefSkeletonDetails", "Couldn't find a valid reference skeleton from the input sim collection."));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
}
else
{
TargetRefSkeleton = GetCollectionRefSkeleton(ClothFacade);
if (!TargetRefSkeleton)
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("InvalidRefSkeletonHeadline", "Invalid Reference Skeleton."),
LOCTEXT("InvalidRefSkeletonDetails", "Couldn't find a valid reference skeleton from the input collection."));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
}
}
// Clean up orphaned vertices
UE::Chaos::ClothAsset::FClothGeometryTools::CleanupAndCompactMesh(ClothCollection);
if (SimClothCollection.IsValid())
{
UE::Chaos::ClothAsset::FClothGeometryTools::CleanupAndCompactMesh(SimClothCollection.ToSharedRef());
}
//
// Setup the bone weight transfer settings.
//
TransferBoneWeightsSettings TransferSettings;
TransferSettings.bTransferToSim = TargetMeshType != EChaosClothAssetTransferTargetMeshType::Render;
TransferSettings.bTransferToRender = TargetMeshType != EChaosClothAssetTransferTargetMeshType::Simulation;
TransferSettings.bTransferToRenderFromSim = RenderMeshSourceType == EChaosClothAssetTransferRenderMeshSource::SimulationMesh;
TransferSettings.bUseParallel = true;
TransferSettings.MaxNumInfluences = static_cast<int32>(FChaosClothAssetTransferSkinWeightsNode::MaxNumInfluences);
TransferSettings.TransferMethod = static_cast<FTransferBoneWeights::ETransferBoneWeightsMethod>(TransferMethod);
TransferSettings.NormalThreshold = NormalThreshold;
TransferSettings.RadiusPercentage = RadiusPercentage;
TransferSettings.LayeredMeshSupport = LayeredMeshSupport;
TransferSettings.NumSmoothingIterations = NumSmoothingIterations;
TransferSettings.SmoothingStrength = SmoothingStrength;
TransferSettings.InpaintMaskWeightMapName = GetValue<FString>(Context, &InpaintMask.WeightMap);
bool bTransferResult = false;
switch (TransferMethod)
{
case EChaosClothAssetTransferSkinWeightsMethod::InpaintWeights:
bTransferResult = TransferInpaintWeights(*TargetRefSkeleton, SourceDynamicMesh, ClothCollection, TransferSettings, SimClothCollection);
break;
case EChaosClothAssetTransferSkinWeightsMethod::ClosestPointOnSurface:
bTransferResult = TransferClosestPointOnSurface(*TargetRefSkeleton, SourceDynamicMesh, ClothCollection, TransferSettings, SimClothCollection);
break;
default:
checkNoEntry();
break;
}
if (!bTransferResult)
{
FClothDataflowTools::LogAndToastWarning(*this,
LOCTEXT("TransferWeightsFailedHeadline", "Transfer Weights Failed."),
LOCTEXT("TransferWeightsDetails", "Failed to transfer skinning weights from the source."));
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
return;
}
// Optional check to make sure all vertices adhere to the bone influence limit
constexpr bool bCheckMaxInfluenceComplience = false;
if (bCheckMaxInfluenceComplience)
{
for (int32 VID = 0; VID < ClothFacade.GetNumSimVertices3D(); ++VID)
{
const int32 NumInfluences = ClothFacade.GetSimBoneIndices()[VID].Num();
if (!ensure(NumInfluences <= static_cast<int32>(FChaosClothAssetTransferSkinWeightsNode::MaxNumInfluences)))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Maximum number of influences is exceeded for sim vertex %i."), VID);
}
}
for (int32 VID = 0; VID < ClothFacade.GetNumRenderVertices(); ++VID)
{
const int32 NumInfluences = ClothFacade.GetRenderBoneIndices()[VID].Num();
if (!ensure(NumInfluences <= static_cast<int32>(FChaosClothAssetTransferSkinWeightsNode::MaxNumInfluences)))
{
UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("TransferSkinWeightsNode: Maximum number of influences is exceeded for render vertex %i."), VID);
}
}
}
}
SetValue(Context, MoveTemp(*ClothCollection), &Collection);
}
}
#undef LOCTEXT_NAMESPACE