Files
UnrealEngine/Engine/Plugins/Experimental/ChaosFlesh/Source/ChaosFleshNodes/Private/Dataflow/ChaosFleshSetFleshBonePositionTargetBindingNode.cpp
2025-05-18 13:04:45 +08:00

396 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Dataflow/ChaosFleshSetFleshBonePositionTargetBindingNode.h"
#include "Engine/SkeletalMesh.h"
#include "Chaos/BoundingVolumeHierarchy.h"
#include "Chaos/Utilities.h"
#include "GeometryCollection/Facades/CollectionKinematicBindingFacade.h"
#include "GeometryCollection/Facades/CollectionPositionTargetFacade.h"
#include "GeometryCollection/Facades/CollectionVertexBoneWeightsFacade.h"
#include "GeometryCollection/Facades/CollectionTransformFacade.h"
#include "Rendering/SkeletalMeshModel.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "SkeletalMeshAttributes.h"
#include "ChaosFlesh/ChaosFlesh.h"
#include "Chaos/HierarchicalSpatialHash.h"
#include "Chaos/TriangleCollisionPoint.h"
#include "BoneWeights.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ChaosFleshSetFleshBonePositionTargetBindingNode)
DEFINE_LOG_CATEGORY_STATIC(ChaosFleshSetFleshBonePositionTargetBindingNodeLog, Log, All);
void FSetFleshBonePositionTargetBindingDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection))
{
FManagedArrayCollection InCollection = GetValue(Context, &Collection);
#if WITH_EDITORONLY_DATA
if (TObjectPtr<USkeletalMesh> BoneSkeletalMesh = GetValue<TObjectPtr<USkeletalMesh>>(Context, &SkeletalMeshIn))
{
const TManagedArray<FIntVector>* Indices = InCollection.FindAttribute<FIntVector>("Indices", FGeometryCollection::FacesGroup);
const TManagedArray<FVector3f>* Vertices = InCollection.FindAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
const TManagedArray<FTransform3f>* Transform = InCollection.FindAttribute<FTransform3f>("Transform", FTransformCollection::TransformGroup);
const TManagedArray<FString>* TransformBoneName = InCollection.FindAttribute<FString>("BoneName", FTransformCollection::TransformGroup);
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(InCollection);
const FReferenceSkeleton& ReferenceSkeleton = BoneSkeletalMesh->GetRefSkeleton();
const int32 LODIndex = 0;
if (Indices && Vertices && Transform && TransformBoneName)
{
FSkeletalMeshRenderData* RenderData = BoneSkeletalMesh->GetResourceForRendering();
FMeshDescription MeshDescription;
if (BoneSkeletalMesh->CloneMeshDescription(LODIndex, MeshDescription) && RenderData->LODRenderData.Num())
{
FSkeletalMeshLODRenderData* LODRenderData = &RenderData->LODRenderData[LODIndex];
FSkeletalMeshAttributes MeshAttribs(MeshDescription);
FSkinWeightsVertexAttributesRef VertexSkinWeights = MeshAttribs.GetVertexSkinWeights();
int32 NumSkeletonVertices = MeshDescription.Vertices().Num();
Chaos::FReal SphereRadius = (Chaos::FReal)0.;
Chaos::FVec3f CoordMaxs(-FLT_MAX);
Chaos::FVec3f CoordMins(FLT_MAX);
for (int32 i = 0; i < NumSkeletonVertices; i++)
{
CoordMaxs = CoordMaxs.ComponentwiseMax(MeshDescription.GetVertexPosition(i));
CoordMins = CoordMins.ComponentwiseMin(MeshDescription.GetVertexPosition(i));
}
Chaos::FVec3f CoordDiff = (CoordMaxs - CoordMins) * VertexRadiusRatio;
SphereRadius = Chaos::FReal(FGenericPlatformMath::Max(CoordDiff[0], FGenericPlatformMath::Max(CoordDiff[1], CoordDiff[2])));
TArray<Chaos::FSphere*> VertexSpherePtrs;
TArray<Chaos::FSphere> VertexSpheres;
VertexSpheres.Init(Chaos::FSphere(Chaos::TVec3<Chaos::FReal>(0), SphereRadius), NumSkeletonVertices);
VertexSpherePtrs.SetNum(NumSkeletonVertices);
for (int32 i = 0; i < int32(NumSkeletonVertices); i++)
{
Chaos::TVec3<Chaos::FReal> SphereCenter(MeshDescription.GetVertexPosition(i));
Chaos::FSphere VertexSphere(SphereCenter, SphereRadius);
VertexSpheres[i] = Chaos::FSphere(SphereCenter, SphereRadius);
VertexSpherePtrs[i] = &VertexSpheres[i];
}
Chaos::TBoundingVolumeHierarchy<
TArray<Chaos::FSphere*>,
TArray<int32>,
Chaos::FReal,
3> VertexBVH(VertexSpherePtrs);
GeometryCollection::Facades::FVertexBoneWeightsFacade VertexBoneWeightsFacade(InCollection);
//Mapping from SKM bone index to Collection bone index
const TMap<FString, int32> BoneNameIndexMap = TransformFacade.BoneNameIndexMap();
TMap<int32, int32> SKMBoneIndexToCollectionBoneIndex;
for (int32 BoneIndex = 0; BoneIndex < ReferenceSkeleton.GetNum(); ++BoneIndex)
{
FString SKMBoneName = ReferenceSkeleton.GetRefBoneInfo()[BoneIndex].Name.ToString();
if (BoneNameIndexMap.Contains(SKMBoneName))
{
SKMBoneIndexToCollectionBoneIndex.Add(BoneIndex, BoneNameIndexMap[SKMBoneName]);
}
}
if (SkeletalBindingMode == ESkeletalBindingMode::Dataflow_SkeletalBinding_Kinematic)
{
for (int32 i = 0; i < Vertices->Num(); ++i)
{
//only work on particles that are not kinematic:
if (!VertexBoneWeightsFacade.IsKinematicVertex(i))
{
TArray<int32> ParticleIntersection = VertexBVH.FindAllIntersections((*Vertices)[i]);
int32 MinIndex = -1;
float MinDis = SphereRadius;
for (int32 j = 0; j < ParticleIntersection.Num(); j++)
{
Chaos::FRealSingle CurrentDistance = ((*Vertices)[i] - MeshDescription.GetVertexPosition(ParticleIntersection[j])).Size();
if (CurrentDistance < MinDis)
{
MinDis = CurrentDistance;
MinIndex = ParticleIntersection[j];
}
}
if (MinIndex != -1)
{
FVertexBoneWeights BoneWeights = VertexSkinWeights.Get(FVertexID(MinIndex));
const int32 InfluenceCount = BoneWeights.Num();
TArray<int32> VertexBoneIndex;
TArray<float> VertexBoneWeight;
for (int32 InfluenceIndex = 0; InfluenceIndex < InfluenceCount; ++InfluenceIndex)
{
if (SKMBoneIndexToCollectionBoneIndex.Contains(BoneWeights[InfluenceIndex].GetBoneIndex()))
{
VertexBoneIndex.Add(SKMBoneIndexToCollectionBoneIndex[BoneWeights[InfluenceIndex].GetBoneIndex()]);
VertexBoneWeight.Add(BoneWeights[InfluenceIndex].GetWeight());
}
}
VertexBoneWeightsFacade.ModifyBoneWeight(i, VertexBoneIndex, VertexBoneWeight);
VertexBoneWeightsFacade.SetVertexKinematic(i);
}
}
}
}
else
{
GeometryCollection::Facades::FPositionTargetFacade PositionTargets(InCollection);
PositionTargets.DefineSchema();
for (int32 i = 0; i < Vertices->Num(); ++i)
{
//only work on particles that are not kinematic:
if (!VertexBoneWeightsFacade.IsKinematicVertex(i))
{
TArray<int32> ParticleIntersection = VertexBVH.FindAllIntersections((*Vertices)[i]);
int32 MinIndex = -1;
float MinDis = SphereRadius;
for (int32 j = 0; j < ParticleIntersection.Num(); j++)
{
Chaos::FRealSingle CurrentDistance = ((*Vertices)[i] - MeshDescription.GetVertexPosition(ParticleIntersection[j])).Size();
if (CurrentDistance < MinDis)
{
MinDis = CurrentDistance;
MinIndex = ParticleIntersection[j];
}
}
if (MinIndex != -1)
{
// add kinematic particles
int32 ParticleIndex = InCollection.AddElements(1, FGeometryCollection::VerticesGroup);
TManagedArray<FVector3f>& CurrentVertices = InCollection.ModifyAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
CurrentVertices[ParticleIndex] = MeshDescription.GetVertexPosition(MinIndex);
FVertexBoneWeights BoneWeights = VertexSkinWeights.Get(FVertexID(MinIndex));
const int32 InfluenceCount = BoneWeights.Num();
TArray<int32> VertexBoneIndex;
TArray<float> VertexBoneWeight;
for (int32 InfluenceIndex = 0; InfluenceIndex < InfluenceCount; ++InfluenceIndex)
{
if (SKMBoneIndexToCollectionBoneIndex.Contains(BoneWeights[InfluenceIndex].GetBoneIndex()))
{
VertexBoneIndex.Add(SKMBoneIndexToCollectionBoneIndex[BoneWeights[InfluenceIndex].GetBoneIndex()]);
VertexBoneWeight.Add(BoneWeights[InfluenceIndex].GetWeight());
}
}
VertexBoneWeightsFacade.ModifyBoneWeight(ParticleIndex, VertexBoneIndex, VertexBoneWeight);
VertexBoneWeightsFacade.SetVertexKinematic(ParticleIndex);
GeometryCollection::Facades::FPositionTargetsData DataPackage;
DataPackage.TargetIndex.Init(ParticleIndex, 1);
DataPackage.TargetWeights.Init(1.f, 1);
DataPackage.SourceWeights.Init(1.f, 1);
DataPackage.SourceIndex.Init(i, 1);
if (const TManagedArray<float>* Mass = InCollection.FindAttribute<float>("Mass", FGeometryCollection::VerticesGroup))
{
//Target is kinematic, only compute stiffness from source
DataPackage.Stiffness = PositionTargetStiffness * (*Mass)[i];
}
else
{
DataPackage.Stiffness = PositionTargetStiffness;
}
PositionTargets.AddPositionTarget(DataPackage);
}
}
}
}
}
}
}
#else
ensureMsgf(false, TEXT("SetFleshBonePositionTargetBinding is an editor only node."));
#endif
SetValue(Context, MoveTemp(InCollection), &Collection);
}
}
void FSetFleshBonePositionTargetBindingDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection))
{
FManagedArrayCollection InCollection = GetValue(Context, &Collection);
#if WITH_EDITORONLY_DATA
if (TObjectPtr<USkeletalMesh> BoneSkeletalMesh = GetValue<TObjectPtr<USkeletalMesh>>(Context, &SkeletalMeshIn))
{
const TManagedArray<FVector3f>* Vertices = InCollection.FindAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
const TManagedArray<FTransform3f>* Transform = InCollection.FindAttribute<FTransform3f>("Transform", FTransformCollection::TransformGroup);
const TManagedArray<FString>* TransformBoneName = InCollection.FindAttribute<FString>("BoneName", FTransformCollection::TransformGroup);
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(InCollection);
const FReferenceSkeleton& ReferenceSkeleton = BoneSkeletalMesh->GetRefSkeleton();
static int32 LODIndex = 0;
if (Vertices && Transform && TransformBoneName)
{
FMeshDescription MeshDescription;
if (BoneSkeletalMesh->CloneMeshDescription(LODIndex, MeshDescription))
{
FSkeletalMeshAttributes MeshAttribs(MeshDescription);
const FSkinWeightsVertexAttributesRef VertexSkinWeights = MeshAttribs.GetVertexSkinWeights();
const int32 NumSkeletonVertices = MeshDescription.Vertices().Num();
const int32 NumSkeletonTriangles = MeshDescription.Triangles().Num();
TArray<Chaos::TVec3<Chaos::FReal>> SkeletonVerticesTVec3;
SkeletonVerticesTVec3.SetNum(NumSkeletonVertices);
// Get Vertex Position
for (const FVertexID& VertexID : MeshDescription.Vertices().GetElementIDs())
{
const int32 VertexIndex = VertexID.GetValue();
SkeletonVerticesTVec3[VertexIndex] = Chaos::TVec3<Chaos::FReal>(MeshDescription.GetVertexPosition(VertexID));
}
TConstArrayView<Chaos::TVec3<Chaos::FReal>> ConstSkeletonVerticesTVec3(SkeletonVerticesTVec3);
// Get Triangles
Chaos::FTriangleMesh TriangleMesh;
TArray<Chaos::TVec3<int32>> TriangleElements;
TriangleElements.SetNum(NumSkeletonTriangles);
for (const FTriangleID& TriangleID : MeshDescription.Triangles().GetElementIDs())
{
const int32 TriangleIndex = TriangleID.GetValue();
const TArrayView<const FVertexID>& Triangle = MeshDescription.GetTriangleVertices(TriangleID);
TriangleElements[TriangleIndex] = Chaos::TVec3<int32>(Triangle[0], Triangle[1], Triangle[2]);
}
TriangleMesh.Init(TriangleElements);
Chaos::FTriangleMesh::TSpatialHashType<Chaos::FReal> SpatialHash;
Chaos::FReal SphereRadius = (Chaos::FReal)SearchRadius;
TriangleMesh.BuildSpatialHash(ConstSkeletonVerticesTVec3, SpatialHash, SphereRadius);
GeometryCollection::Facades::FVertexBoneWeightsFacade VertexBoneWeightsFacade(InCollection);
GeometryCollection::Facades::FPositionTargetFacade PositionTargets(InCollection);
//Mapping from SKM bone index to Collection bone index
const TMap<FString, int32> BoneNameIndexMap = TransformFacade.BoneNameIndexMap();
TMap<int32, int32> SKMBoneIndexToCollectionBoneIndex;
for (int32 BoneIndex = 0; BoneIndex < ReferenceSkeleton.GetNum(); ++BoneIndex)
{
FString SKMBoneName = ReferenceSkeleton.GetRefBoneInfo()[BoneIndex].Name.ToString();
if (BoneNameIndexMap.Contains(SKMBoneName))
{
SKMBoneIndexToCollectionBoneIndex.Add(BoneIndex, BoneNameIndexMap[SKMBoneName]);
}
}
// Only keep boundary vertices within VertexSelection
TArray<int32> SelectedVertices;
if (IsConnected(&VertexSelection))
{
FDataflowVertexSelection InDataflowVertexSelection = GetValue<FDataflowVertexSelection>(Context, &VertexSelection);
if (InDataflowVertexSelection.Num() != Vertices->Num())
{
Context.Error(FString::Printf(
TEXT("VertexSelection size [%d] is not equal to the collection's vertex count [%d]"),
InDataflowVertexSelection.Num(), Vertices->Num()),
this, Out);
return;
}
SelectedVertices = InDataflowVertexSelection.AsArray();
}
else
{
SelectedVertices.SetNum(Vertices->Num());
for (int32 VertIdx = 0; VertIdx < Vertices->Num(); ++VertIdx)
{
SelectedVertices[VertIdx] = VertIdx;
}
}
//Transfer skin weights
TSet<int32> MissingSourceBones;
for (int32 VertIdx : SelectedVertices)
{
//only work on particles that are not kinematic:
if (!VertexBoneWeightsFacade.IsKinematicVertex(VertIdx))
{
TArray<Chaos::TTriangleCollisionPoint<Chaos::FReal>> Result;
if (TriangleMesh.PointClosestTriangleQuery(SpatialHash, ConstSkeletonVerticesTVec3,
VertIdx, Chaos::TVec3<Chaos::FReal>((*Vertices)[VertIdx]), SphereRadius / 2.f, SphereRadius / 2.f,
[](const int32 PointIndex, const int32 TriangleIndex)->bool { return true; }, Result))
{
for (const Chaos::TTriangleCollisionPoint<Chaos::FReal>& CollisionPoint : Result)
{
TMap<int32, float> BoneWeightBucket;
FVector3f InterpSkeletonVertexPosition = FVector3f(0);
for (int32 LocalTriIdx = 0; LocalTriIdx < 3; ++LocalTriIdx)
{
const int32 TriVertexIndex = TriangleElements[CollisionPoint.Indices[1]][LocalTriIdx];
const float TriInterpWeight = CollisionPoint.Bary[LocalTriIdx + 1];
InterpSkeletonVertexPosition += TriInterpWeight * MeshDescription.GetVertexPosition(TriVertexIndex);
FVertexBoneWeights BoneWeights = VertexSkinWeights.Get(FVertexID(TriVertexIndex));
const int32 InfluenceCount = BoneWeights.Num();
for (int32 InfluenceIndex = 0; InfluenceIndex < InfluenceCount; ++InfluenceIndex)
{
const int32 SKMBoneIndex = BoneWeights[InfluenceIndex].GetBoneIndex();
if (SKMBoneIndexToCollectionBoneIndex.Contains(SKMBoneIndex))
{
const int32 CollectionBoneIndex = SKMBoneIndexToCollectionBoneIndex[SKMBoneIndex];
const float InterpBoneWeight = TriInterpWeight * BoneWeights[InfluenceIndex].GetWeight();
if (BoneWeightBucket.Contains(CollectionBoneIndex))
{
BoneWeightBucket[CollectionBoneIndex] += InterpBoneWeight;
}
else
{
BoneWeightBucket.Add(CollectionBoneIndex, InterpBoneWeight);
}
}
else if (!MissingSourceBones.Contains(SKMBoneIndex))
{
MissingSourceBones.Add(SKMBoneIndex);
UE_LOG(ChaosFleshSetFleshBonePositionTargetBindingNodeLog, Error, TEXT("Collection does not contain bone[%s]."), *BoneSkeletalMesh->GetRefSkeleton().GetBoneName(SKMBoneIndex).ToString());
}
}
}
TArray<int32> VertexBoneIndex;
TArray<float> VertexBoneWeight;
for (const TPair<int32, float>& Pair : BoneWeightBucket)
{
VertexBoneIndex.Add(Pair.Key);
VertexBoneWeight.Add(Pair.Value);
}
if (SkeletalBindingMode == ESkeletalBindingMode::Dataflow_SkeletalBinding_Kinematic)
{
VertexBoneWeightsFacade.ModifyBoneWeight(VertIdx, VertexBoneIndex, VertexBoneWeight);
VertexBoneWeightsFacade.SetVertexKinematic(VertIdx);
}
else // Dataflow_SkeletalBinding_PositionTarget
{
const int32 ParticleIndex = InCollection.AddElements(1, FGeometryCollection::VerticesGroup);
TManagedArray<FVector3f>& CurrentVertices = InCollection.ModifyAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
CurrentVertices[ParticleIndex] = InterpSkeletonVertexPosition;
VertexBoneWeightsFacade.ModifyBoneWeight(ParticleIndex, VertexBoneIndex, VertexBoneWeight);
VertexBoneWeightsFacade.SetVertexKinematic(ParticleIndex);
GeometryCollection::Facades::FPositionTargetsData DataPackage;
DataPackage.TargetIndex.Init(ParticleIndex, 1);
DataPackage.TargetWeights.Init(1.f, 1);
DataPackage.SourceWeights.Init(1.f, 1);
DataPackage.SourceIndex.Init(VertIdx, 1);
if (const TManagedArray<float>* Mass = InCollection.FindAttribute<float>("Mass", FGeometryCollection::VerticesGroup))
{
//Target is kinematic, only compute stiffness from source
DataPackage.Stiffness = PositionTargetStiffness * (*Mass)[VertIdx];
}
else
{
DataPackage.Stiffness = PositionTargetStiffness;
}
PositionTargets.AddPositionTarget(DataPackage);
}
break;
}
}
}
}
}
}
}
#else
ensureMsgf(false, TEXT("SetFleshBonePositionTargetBinding is an editor only node."));
#endif
SetValue(Context, MoveTemp(InCollection), &Collection);
}
}