Files
UnrealEngineUWP/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/Operations/SmoothBoneWeights.cpp
rinat abdrashitov 347165bea8 -Added SmoothBoneWeights geometry operator that will contain a collection of algorithms for smoothing skin/bone weights.
-Refactored the SkinWeightsPaintTool to use the new operator.

#rb david.hill
#rb kiaran.ritchie
#jira none

[CL 25879825 by rinat abdrashitov in ue5-main branch]
2023-06-08 15:45:35 -04:00

267 lines
7.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Operations/SmoothBoneWeights.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/NonManifoldMappingSupport.h"
#include "Util/ProgressCancel.h"
#include "BoneWeights.h"
#include "Math/UnrealMathUtility.h"
using namespace UE::AnimationCore;
using namespace UE::Geometry;
namespace SmoothBoneWeightsLocals
{
template <typename BoneIndexType, typename BoneWeightType>
void NormalizeWeights(TMap<BoneIndexType, BoneWeightType>& InOutWeights)
{
BoneWeightType TotalWeight = (BoneWeightType) 0.f;
for (const TTuple<BoneIndexType, BoneWeightType>& Weight : InOutWeights)
{
TotalWeight += Weight.Value;
}
if (!FMath::IsNearlyZero(TotalWeight))
{
for (TTuple<BoneIndexType, BoneWeightType>& Weight : InOutWeights)
{
Weight.Value /= TotalWeight;
}
}
}
/** Data source implementation for bone weights data stored in the FDynamicMeshVertexSkinWeightsAttribute. */
class FSkinWeightsAttributeDataSource : public TBoneWeightsDataSource<FBoneIndexType, float>
{
public:
FSkinWeightsAttributeDataSource(const FDynamicMeshVertexSkinWeightsAttribute* InAttribute)
:
Attribute(InAttribute)
{
checkSlow(Attribute);
}
virtual ~FSkinWeightsAttributeDataSource() = default;
virtual int32 GetBoneNum(const int32 VertexID) override
{
FBoneWeights Weights;
Attribute->GetValue(VertexID, Weights);
return Weights.Num();
}
virtual FBoneIndexType GetBoneIndex(const int32 VertexID, const int32 Index) override
{
FBoneWeights Weights;
Attribute->GetValue(VertexID, Weights);
return Weights[Index].GetBoneIndex();
}
virtual float GetBoneWeight(const int32 VertexID, const int32 Index) override
{
FBoneWeights Weights;
Attribute->GetValue(VertexID, Weights);
return Weights[Index].GetWeight();
}
virtual float GetWeightOfBoneOnVertex(const int32 VertexID, const FBoneIndexType BoneIndex) override
{
FBoneWeights Weights;
Attribute->GetValue(VertexID, Weights);
for (const FBoneWeight& BoneWeight : Weights)
{
if (BoneWeight.GetBoneIndex() == BoneIndex)
{
return BoneWeight.GetWeight();;
}
}
return 0.f;
}
protected:
const FDynamicMeshVertexSkinWeightsAttribute* Attribute = nullptr;
};
}
//
// TSmoothBoneWeights
//
template <typename BoneIndexType, typename BoneWeightType>
TSmoothBoneWeights<BoneIndexType, BoneWeightType>::TSmoothBoneWeights(const FDynamicMesh3* InSourceMesh,
TBoneWeightsDataSource<BoneIndexType, BoneWeightType>* InDataSource)
:
SourceMesh(InSourceMesh),
DataSource(InDataSource)
{
checkSlow(InSourceMesh);
}
template <typename BoneIndexType, typename BoneWeightType>
bool TSmoothBoneWeights<BoneIndexType, BoneWeightType>::Cancelled()
{
return (Progress == nullptr) ? false : Progress->Cancelled();
}
template <typename BoneIndexType, typename BoneWeightType>
EOperationValidationResult TSmoothBoneWeights<BoneIndexType, BoneWeightType>::Validate()
{
if (SourceMesh == nullptr)
{
return EOperationValidationResult::Failed_UnknownReason;
}
if (DataSource == nullptr)
{
return EOperationValidationResult::Failed_UnknownReason;
}
return EOperationValidationResult::Ok;
}
template <typename BoneIndexType, typename BoneWeightType>
bool TSmoothBoneWeights<BoneIndexType, BoneWeightType>::SmoothWeightsAtVertex(const int32 VertexID,
const BoneWeightType VertexFalloff,
TMap<BoneIndexType, BoneWeightType>& OutFinalWeights)
{
using namespace SmoothBoneWeightsLocals;
const UE::Geometry::FNonManifoldMappingSupport NonManifoldMappingSupport(*SourceMesh);
// for each vertex in the stamp...
constexpr int32 AvgNumNeighbors = 8;
using VertexNeighborWeights = TArray<BoneWeightType, TInlineAllocator<AvgNumNeighbors>>;
TArray<int32> AllNeighborVertices;
TMap<BoneIndexType, VertexNeighborWeights> WeightsOnAllNeighbors;
const int32 SrcVertexID = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(VertexID);
// get list of all neighboring vertices, AND this vertex
AllNeighborVertices.Add(VertexID);
for (const int32 NeighborVertexID : SourceMesh->VtxVerticesItr(SrcVertexID))
{
AllNeighborVertices.Add(NeighborVertexID);
}
// get all weights above a given threshold across ALL neighbors (including self)
for (const int32 NeighborVertexID : AllNeighborVertices)
{
for (int32 Index = 0; Index < DataSource->GetBoneNum(NeighborVertexID); ++Index)
{
const BoneWeightType Weight = DataSource->GetBoneWeight(NeighborVertexID, Index);
if (Weight > MinimumWeightThreshold)
{
VertexNeighborWeights& BoneWeights = WeightsOnAllNeighbors.FindOrAdd(DataSource->GetBoneIndex(NeighborVertexID, Index));
BoneWeights.Add(Weight);
}
}
}
// calculate single average weight of each bone on all the neighbors
OutFinalWeights.Reset();
for (const TTuple<BoneIndexType, VertexNeighborWeights>& NeighborWeights : WeightsOnAllNeighbors)
{
BoneWeightType TotalWeightOnThisBone = (BoneWeightType) 0.f;
for (const BoneWeightType& Value : NeighborWeights.Value)
{
TotalWeightOnThisBone += Value;
}
OutFinalWeights.Add(NeighborWeights.Key, TotalWeightOnThisBone / (BoneWeightType)NeighborWeights.Value.Num());
}
// normalize the weights
NormalizeWeights(OutFinalWeights);
// lerp weights from previous values, to fully relaxed values by brush strength scaled by falloff
for (TTuple<BoneIndexType, BoneWeightType>& FinalWeight : OutFinalWeights)
{
const BoneIndexType BoneIndex = FinalWeight.Key;
BoneWeightType NewWeight = FinalWeight.Value;
BoneWeightType OldWeight = DataSource->GetWeightOfBoneOnVertex(VertexID, BoneIndex);
FinalWeight.Value = FMath::Lerp<BoneWeightType>(OldWeight, NewWeight, VertexFalloff);
}
// normalize again
NormalizeWeights(OutFinalWeights);
if (Cancelled())
{
return false;
}
return true;
}
//
// FSmoothDynamicMeshVertexSkinWeights
//
FSmoothDynamicMeshVertexSkinWeights::FSmoothDynamicMeshVertexSkinWeights(const FDynamicMesh3* InSourceMesh, const FName InProfile)
:
TSmoothBoneWeights(InSourceMesh, nullptr)
{
if (SourceMesh && SourceMesh->Attributes())
{
Attribute = SourceMesh->Attributes()->GetSkinWeightsAttribute(InProfile);
if (Attribute)
{
SkinWeightsAttributeDataSource = MakeUnique<SmoothBoneWeightsLocals::FSkinWeightsAttributeDataSource>(Attribute);
DataSource = SkinWeightsAttributeDataSource.Get();
}
}
}
FSmoothDynamicMeshVertexSkinWeights::FSmoothDynamicMeshVertexSkinWeights(const FDynamicMesh3* InSourceMesh, FDynamicMeshVertexSkinWeightsAttribute* InAttribute)
:
TSmoothBoneWeights(InSourceMesh, nullptr),
Attribute(InAttribute)
{
if (InAttribute)
{
SkinWeightsAttributeDataSource = MakeUnique<SmoothBoneWeightsLocals::FSkinWeightsAttributeDataSource>(InAttribute);
DataSource = SkinWeightsAttributeDataSource.Get();
}
}
EOperationValidationResult FSmoothDynamicMeshVertexSkinWeights::Validate()
{
if (Attribute == nullptr)
{
return EOperationValidationResult::Failed_UnknownReason;
}
return TSmoothBoneWeights::Validate();
}
bool FSmoothDynamicMeshVertexSkinWeights::SmoothWeightsAtVertex(const int32 VertexID, const float VertexFalloff)
{
TMap<FBoneIndexType, float> FinalWeights;
if (TSmoothBoneWeights<FBoneIndexType, float>::SmoothWeightsAtVertex(VertexID, VertexFalloff, FinalWeights))
{
FBoneWeightsSettings BoneSettings;
BoneSettings.SetNormalizeType(EBoneWeightNormalizeType::None);
FBoneWeights WeightArray;
for (const TTuple<FBoneIndexType, float>& FinalWeight : FinalWeights)
{
WeightArray.SetBoneWeight(FBoneWeight(FinalWeight.Key, FinalWeight.Value), BoneSettings);
}
Attribute->SetValue(VertexID, WeightArray);
return true;
}
return false;
}
// template instantiation
template class DYNAMICMESH_API UE::Geometry::TSmoothBoneWeights<FBoneIndexType, float>;
template class DYNAMICMESH_API UE::Geometry::TSmoothBoneWeights<int, float>;