// 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 BoneSkeletalMesh = GetValue>(Context, &SkeletalMeshIn)) { const TManagedArray* Indices = InCollection.FindAttribute("Indices", FGeometryCollection::FacesGroup); const TManagedArray* Vertices = InCollection.FindAttribute("Vertex", FGeometryCollection::VerticesGroup); const TManagedArray* Transform = InCollection.FindAttribute("Transform", FTransformCollection::TransformGroup); const TManagedArray* TransformBoneName = InCollection.FindAttribute("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 VertexSpherePtrs; TArray VertexSpheres; VertexSpheres.Init(Chaos::FSphere(Chaos::TVec3(0), SphereRadius), NumSkeletonVertices); VertexSpherePtrs.SetNum(NumSkeletonVertices); for (int32 i = 0; i < int32(NumSkeletonVertices); i++) { Chaos::TVec3 SphereCenter(MeshDescription.GetVertexPosition(i)); Chaos::FSphere VertexSphere(SphereCenter, SphereRadius); VertexSpheres[i] = Chaos::FSphere(SphereCenter, SphereRadius); VertexSpherePtrs[i] = &VertexSpheres[i]; } Chaos::TBoundingVolumeHierarchy< TArray, TArray, Chaos::FReal, 3> VertexBVH(VertexSpherePtrs); GeometryCollection::Facades::FVertexBoneWeightsFacade VertexBoneWeightsFacade(InCollection); //Mapping from SKM bone index to Collection bone index const TMap BoneNameIndexMap = TransformFacade.BoneNameIndexMap(); TMap 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 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 VertexBoneIndex; TArray 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 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& CurrentVertices = InCollection.ModifyAttribute("Vertex", FGeometryCollection::VerticesGroup); CurrentVertices[ParticleIndex] = MeshDescription.GetVertexPosition(MinIndex); FVertexBoneWeights BoneWeights = VertexSkinWeights.Get(FVertexID(MinIndex)); const int32 InfluenceCount = BoneWeights.Num(); TArray VertexBoneIndex; TArray 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* Mass = InCollection.FindAttribute("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 BoneSkeletalMesh = GetValue>(Context, &SkeletalMeshIn)) { const TManagedArray* Vertices = InCollection.FindAttribute("Vertex", FGeometryCollection::VerticesGroup); const TManagedArray* Transform = InCollection.FindAttribute("Transform", FTransformCollection::TransformGroup); const TManagedArray* TransformBoneName = InCollection.FindAttribute("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> SkeletonVerticesTVec3; SkeletonVerticesTVec3.SetNum(NumSkeletonVertices); // Get Vertex Position for (const FVertexID& VertexID : MeshDescription.Vertices().GetElementIDs()) { const int32 VertexIndex = VertexID.GetValue(); SkeletonVerticesTVec3[VertexIndex] = Chaos::TVec3(MeshDescription.GetVertexPosition(VertexID)); } TConstArrayView> ConstSkeletonVerticesTVec3(SkeletonVerticesTVec3); // Get Triangles Chaos::FTriangleMesh TriangleMesh; TArray> TriangleElements; TriangleElements.SetNum(NumSkeletonTriangles); for (const FTriangleID& TriangleID : MeshDescription.Triangles().GetElementIDs()) { const int32 TriangleIndex = TriangleID.GetValue(); const TArrayView& Triangle = MeshDescription.GetTriangleVertices(TriangleID); TriangleElements[TriangleIndex] = Chaos::TVec3(Triangle[0], Triangle[1], Triangle[2]); } TriangleMesh.Init(TriangleElements); Chaos::FTriangleMesh::TSpatialHashType 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 BoneNameIndexMap = TransformFacade.BoneNameIndexMap(); TMap 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 SelectedVertices; if (IsConnected(&VertexSelection)) { FDataflowVertexSelection InDataflowVertexSelection = GetValue(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 MissingSourceBones; for (int32 VertIdx : SelectedVertices) { //only work on particles that are not kinematic: if (!VertexBoneWeightsFacade.IsKinematicVertex(VertIdx)) { TArray> Result; if (TriangleMesh.PointClosestTriangleQuery(SpatialHash, ConstSkeletonVerticesTVec3, VertIdx, Chaos::TVec3((*Vertices)[VertIdx]), SphereRadius / 2.f, SphereRadius / 2.f, [](const int32 PointIndex, const int32 TriangleIndex)->bool { return true; }, Result)) { for (const Chaos::TTriangleCollisionPoint& CollisionPoint : Result) { TMap 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 VertexBoneIndex; TArray VertexBoneWeight; for (const TPair& 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& CurrentVertices = InCollection.ModifyAttribute("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* Mass = InCollection.FindAttribute("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); } }