// Copyright Epic Games, Inc. All Rights Reserved. #include "BuildGroomSkinningNodes.h" #include "MeshDescriptionToDynamicMesh.h" #include "SkeletalMeshAttributes.h" #include "SkeletalMeshLODRenderDataToDynamicMesh.h" #include "DynamicMesh/DynamicMesh3.h" #include "Dataflow/DataflowObjectInterface.h" #include "DynamicMesh/MeshTransforms.h" #include "GeometryCollection/Facades/CollectionVertexBoneWeightsFacade.h" #include "GeometryCollection/Facades/CollectionMeshFacade.h" #include "Operations/TransferBoneWeights.h" #include "Rendering/SkeletalMeshRenderData.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(BuildGroomSkinningNodes) namespace UE::Groom::Private { static bool SkeletalMeshToDynamicMesh(const USkeletalMesh* SkeletalMesh, const int32 LodIndex, UE::Geometry::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; } static void BuildGeometrySkinWeights(GeometryCollection::Facades::FCollectionMeshFacade& MeshFacade, GeometryCollection::Facades::FVertexBoneWeightsFacade& SkinningFacade, const FDataflowVertexSelection& VertexSelection, const TObjectPtr SkeletalMesh, const int32 LODIndex, const FTransform& RelativeTransform) { if(MeshFacade.IsValid() && SkeletalMesh && SkeletalMesh->IsValidLODIndex(LODIndex)) { const bool bValidSelection = VertexSelection.IsValidForCollection(SkinningFacade.GetManagedArrayCollection()); TMap TargetBoneToIndex; TargetBoneToIndex.Reserve(SkeletalMesh->GetRefSkeleton().GetRawBoneNum()); for (int32 BoneIdx = 0; BoneIdx < SkeletalMesh->GetRefSkeleton().GetRawBoneNum(); ++BoneIdx) { TargetBoneToIndex.Add(SkeletalMesh->GetRefSkeleton().GetRawRefBoneInfo()[BoneIdx].Name, BoneIdx); } UE::Geometry::FDynamicMesh3 DynamicMesh; if (UE::Groom::Private::SkeletalMeshToDynamicMesh(SkeletalMesh, LODIndex, DynamicMesh)) { MeshTransforms::ApplyTransform(DynamicMesh, RelativeTransform, true); UE::Geometry::FTransferBoneWeights TransferBoneWeights(&DynamicMesh, FSkeletalMeshAttributes::DefaultSkinWeightProfileName); TransferBoneWeights.bUseParallel = true; TransferBoneWeights.MaxNumInfluences = 4; TransferBoneWeights.TransferMethod = UE::Geometry::FTransferBoneWeights::ETransferBoneWeightsMethod::ClosestPointOnSurface; if (TransferBoneWeights.Validate() == UE::Geometry::EOperationValidationResult::Ok) { for(int32 GeometryIndex = 0; GeometryIndex < SkinningFacade.NumGeometry(); ++GeometryIndex) { SkinningFacade.ModifyGeometryBinding(GeometryIndex, SkeletalMesh, LODIndex); const TArrayView VertexPositions = MeshFacade.GetVertexPositions(GeometryIndex); const TArray VertexIndices = MeshFacade.GetVertexIndices(GeometryIndex); const int32 NumVertices = VertexPositions.Num(); ParallelFor(NumVertices, [&TransferBoneWeights, &TargetBoneToIndex, &VertexPositions, &VertexIndices, &SkinningFacade, &VertexSelection, bValidSelection](const int32 LocalIndex) { const int32 VertexIndex = VertexIndices[LocalIndex]; if(!bValidSelection || (bValidSelection && (VertexSelection.IsSelected(VertexIndex)))) { TArray BoneIndices; TArray BoneWeights; TransferBoneWeights.TransferWeightsToPoint(BoneIndices, BoneWeights,VertexPositions[LocalIndex], &TargetBoneToIndex); SkinningFacade.ModifyBoneWeight(VertexIndex, BoneIndices, BoneWeights); } }, TransferBoneWeights.bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); } } } } } FCollectionAttributeKey GetBoneIndicesKey() { FCollectionAttributeKey Key; Key.Group = FGeometryCollection::VerticesGroup.ToString(); Key.Attribute = GeometryCollection::Facades::FVertexBoneWeightsFacade::BoneIndicesAttributeName.ToString(); return Key; } FCollectionAttributeKey GetBoneWeightsKey() { FCollectionAttributeKey Key; Key.Group = FGeometryCollection::VerticesGroup.ToString(); Key.Attribute = GeometryCollection::Facades::FVertexBoneWeightsFacade::BoneWeightsAttributeName.ToString(); return Key; } } FTransferSkinWeightsGroomNode::FTransferSkinWeightsGroomNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid) : FDataflowNode(InParam, InGuid) { RegisterInputConnection(&Collection); RegisterInputConnection(&SkeletalMesh); RegisterOutputConnection(&Collection, &Collection); RegisterOutputConnection(&BoneIndicesKey); RegisterOutputConnection(&BoneWeightsKey); RegisterOutputConnection(&SkeletalMesh, &SkeletalMesh); } void FTransferSkinWeightsGroomNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA(&Collection)) { SafeForwardInput(Context, &Collection, &Collection); } else if (Out->IsA(&BoneIndicesKey)) { SetValue(Context, UE::Groom::Private::GetBoneIndicesKey(), &BoneIndicesKey); } else if (Out->IsA(&BoneWeightsKey)) { SetValue(Context, UE::Groom::Private::GetBoneWeightsKey(), &BoneWeightsKey); } else if (Out->IsA>(&SkeletalMesh)) { SafeForwardInput(Context, &SkeletalMesh, &SkeletalMesh); } } FTransferGeometrySkinWeightsDataflowNode::FTransferGeometrySkinWeightsDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid) : FDataflowNode(InParam, InGuid) { RegisterInputConnection(&Collection); RegisterInputConnection(&VertexSelection); RegisterInputConnection(&SkeletalMesh); RegisterInputConnection(&LODIndex).SetCanHidePin(true).SetPinIsHidden(true); RegisterInputConnection(&RelativeTransform).SetCanHidePin(true).SetPinIsHidden(true); RegisterOutputConnection(&Collection, &Collection); RegisterOutputConnection(&BoneIndicesKey); RegisterOutputConnection(&BoneWeightsKey); } void FTransferGeometrySkinWeightsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA(&Collection)) { FManagedArrayCollection GeometryCollection = GetValue(Context, &Collection); GeometryCollection::Facades::FCollectionMeshFacade MeshFacade(GeometryCollection); if(MeshFacade.IsValid()) { GeometryCollection::Facades::FVertexBoneWeightsFacade SkinningFacade(GeometryCollection, false); SkinningFacade.DefineSchema(); UE::Groom::Private::BuildGeometrySkinWeights(MeshFacade, SkinningFacade, GetValue(Context, &VertexSelection), GetValue>(Context, &SkeletalMesh), GetValue(Context, &LODIndex), GetValue(Context, &RelativeTransform)); } SetValue(Context, MoveTemp(GeometryCollection), &Collection); } else if (Out->IsA(&BoneIndicesKey)) { SetValue(Context, UE::Groom::Private::GetBoneIndicesKey(), &BoneIndicesKey); } else if (Out->IsA(&BoneWeightsKey)) { SetValue(Context, UE::Groom::Private::GetBoneWeightsKey(), &BoneWeightsKey); } }