// Copyright Epic Games, Inc. All Rights Reserved. #include "BuildGroomSkinningNodes.h" #include "GroomCollectionFacades.h" #include "MeshDescriptionToDynamicMesh.h" #include "SkeletalMeshAttributes.h" #include "SkeletalMeshLODRenderDataToDynamicMesh.h" #include "DynamicMesh/DynamicMesh3.h" #include "Dataflow/DataflowObjectInterface.h" #include "DynamicMesh/MeshTransforms.h" #include "Operations/TransferBoneWeights.h" #include "Rendering/SkeletalMeshRenderData.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(BuildGroomSkinningNodes) namespace UE::Groom::Private { FORCEINLINE 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; } template static void BuildSkinningData(FManagedArrayCollection& GroomCollection, const int32 GroupIndex, const TObjectPtr SkeletalMesh, const int32 LODIndex, const FTransform& RelativeTransform) { FacadeType GroomFacade(GroomCollection); if(GroomFacade.IsValid()) { TManagedArray>& ObjectSkeletalMeshes = GroomCollection.AddAttribute>(UE::Groom::FGroomGuidesFacade::ObjectSkeletalMeshesAttribute, FacadeType::ObjectsGroup); TManagedArray& ObjectMeshLODs = GroomCollection.AddAttribute(UE::Groom::FGroomGuidesFacade::ObjectMeshLODsAttribute, FacadeType::ObjectsGroup); TManagedArray& VertexBoneIndices = GroomCollection.AddAttribute(UE::Groom::FGroomGuidesFacade::PointBoneIndicesAttribute, FacadeType::VerticesGroup); TManagedArray& VertexBoneWeights = GroomCollection.AddAttribute(UE::Groom::FGroomGuidesFacade::PointBoneWeightsAttribute, FacadeType::VerticesGroup); if(GroupIndex == INDEX_NONE) { for(int32 LocalIndex = 0; LocalIndex < ObjectSkeletalMeshes.Num(); ++LocalIndex) { ObjectSkeletalMeshes[LocalIndex] = SkeletalMesh; ObjectMeshLODs[LocalIndex] = LODIndex; } } else if(ObjectSkeletalMeshes.IsValidIndex(GroupIndex) && ObjectMeshLODs.IsValidIndex(GroupIndex)) { ObjectSkeletalMeshes[GroupIndex] = SkeletalMesh; ObjectMeshLODs[GroupIndex] = LODIndex; } if(SkeletalMesh && SkeletalMesh->IsValidLODIndex(LODIndex)) { 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) { const int32 LocalIndex = GroupIndex; ParallelFor(GroomFacade.GetNumPoints(), [&TransferBoneWeights, &TargetBoneToIndex, &GroomFacade, LocalIndex, &VertexBoneIndices, &VertexBoneWeights]( int32 PointIndex) { const int32 CurveIndex = GroomFacade.GetPointCurveIndices()[PointIndex]; const int32 ObjectIndex = GroomFacade.GetCurveObjectIndices()[CurveIndex]; if(LocalIndex == INDEX_NONE || ObjectIndex == LocalIndex) { TArray BoneIndices; TArray BoneWeights; TransferBoneWeights.TransferWeightsToPoint(BoneIndices, BoneWeights, GroomFacade.GetPointRestPositions()[PointIndex],&TargetBoneToIndex); FIntVector4 PointBoneIndices(INDEX_NONE); FVector4f PointBoneWeights(0.0f); for(int32 BoneIdx = 0; BoneIdx < BoneIndices.Num(); ++BoneIdx) { PointBoneIndices[BoneIdx] = BoneIndices[BoneIdx]; PointBoneWeights[BoneIdx] = BoneWeights[BoneIdx]; } VertexBoneIndices[2*PointIndex] = PointBoneIndices; VertexBoneIndices[2*PointIndex+1] = PointBoneIndices; VertexBoneWeights[2*PointIndex] = PointBoneWeights; VertexBoneWeights[2*PointIndex+1] = PointBoneWeights; } }, TransferBoneWeights.bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); } } } } } template FORCEINLINE void ExtractSkinningData(FManagedArrayCollection& GroomCollection, const FCollectionAttributeKey& BoneIndicesKey, const FCollectionAttributeKey& BoneWeightsKey) { if (!BoneIndicesKey.Attribute.IsEmpty() && !BoneWeightsKey.Attribute.IsEmpty()) { FacadeType GroomFacade(GroomCollection); if(GroomFacade.IsValid()) { const TManagedArray* BoneIndices = GroomCollection.FindAttributeTyped(FName(UE::Groom::FGroomGuidesFacade::PointBoneIndicesAttribute), FacadeType::VerticesGroup); const TManagedArray* BoneWeights = GroomCollection.FindAttributeTyped(FName(UE::Groom::FGroomGuidesFacade::PointBoneWeightsAttribute), FacadeType::VerticesGroup); if(BoneIndices && BoneWeights) { TManagedArray>& IndicesArray = GroomCollection.AddAttribute>(FName(BoneIndicesKey.Attribute), FName(BoneIndicesKey.Group)); TManagedArray>& WeightsArray = GroomCollection.AddAttribute>(FName(BoneWeightsKey.Attribute), FName(BoneWeightsKey.Group)); const int32 NumVertices = GroomFacade.GetNumVertices(); for(int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { const FIntVector4& PointIndices = (*BoneIndices)[VertexIndex]; const FVector4f& PointWeights = (*BoneWeights)[VertexIndex]; IndicesArray[VertexIndex].Reset(4); WeightsArray[VertexIndex].Reset(4); for(int32 WeightIndex = 0; WeightIndex < 4; ++WeightIndex) { if(PointIndices[WeightIndex] != INDEX_NONE) { IndicesArray[VertexIndex].Add(PointIndices[WeightIndex]); WeightsArray[VertexIndex].Add(PointWeights[WeightIndex]); } } } } } } } template FORCEINLINE void ReportSkinningData(FManagedArrayCollection& GroomCollection, const FCollectionAttributeKey& BoneIndicesKey, const FCollectionAttributeKey& BoneWeightsKey) { if (!BoneIndicesKey.Attribute.IsEmpty() && !BoneWeightsKey.Attribute.IsEmpty()) { FacadeType GroomFacade(GroomCollection); if(GroomFacade.IsValid()) { const TManagedArray>* IndicesArray = GroomCollection.FindAttributeTyped>(FName(BoneIndicesKey.Attribute), FName(BoneIndicesKey.Group)); const TManagedArray>* WeightsArray = GroomCollection.FindAttributeTyped>(FName(BoneWeightsKey.Attribute), FName(BoneWeightsKey.Group)); if(IndicesArray && WeightsArray) { TManagedArray& BoneIndices = GroomCollection.AddAttribute(FName(UE::Groom::FGroomGuidesFacade::PointBoneIndicesAttribute), FacadeType::VerticesGroup); TManagedArray& BoneWeights = GroomCollection.AddAttribute(FName(UE::Groom::FGroomGuidesFacade::PointBoneWeightsAttribute), FacadeType::VerticesGroup); const int32 NumVertices = GroomFacade.GetNumVertices(); for(int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { FIntVector4 PointIndices(INDEX_NONE); FVector4f PointWeights(0.0f); const int32 NumWeights = FMath::Min(4, (*IndicesArray)[VertexIndex].Num()); float TotalWeight = 0.0; for(int32 BoneIdx = 0; BoneIdx < NumWeights; ++BoneIdx) { PointIndices[BoneIdx] = (*IndicesArray)[VertexIndex][BoneIdx]; PointWeights[BoneIdx] = (*WeightsArray)[VertexIndex][BoneIdx]; TotalWeight += PointWeights[BoneIdx]; } if(TotalWeight != 0.0) { for(int32 BoneIdx = 0; BoneIdx < NumWeights; ++BoneIdx) { PointWeights[BoneIdx] /= TotalWeight; } } BoneIndices[VertexIndex] = PointIndices; BoneWeights[VertexIndex] = PointWeights; } } } } } } void FBuildGuidesSkinningDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA(&Collection)) { FManagedArrayCollection GroomCollection = GetValue(Context, &Collection); if(CurvesType == EGroomCollectionType::Guides) { UE::Groom::Private::BuildSkinningData(GroomCollection, GroupIndex, SkeletalMesh, LODIndex, RelativeTransform); } else { UE::Groom::Private::BuildSkinningData(GroomCollection, GroupIndex, SkeletalMesh, LODIndex, RelativeTransform); } SetValue(Context, MoveTemp(GroomCollection), &Collection); } } TArray FBuildGuidesSkinningDataflowNode::GetRenderParametersImpl() const { if(CurvesType == EGroomCollectionType::Guides) { return { {TEXT("GuidesRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } else { return { {TEXT("StrandsRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } } void FUnpackSkinWeightsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace UE::Dataflow; // Get the pin value if plugged FCollectionAttributeKey BoneIndicesKeyValue = GetBoneIndicesKey(); FCollectionAttributeKey BoneWeightsKeyValue = GetBoneWeightsKey(); if (Out->IsA(&Collection)) { // Evaluate in collection FManagedArrayCollection GroomCollection = GetValue(Context, &Collection); if(CurvesType == EGroomCollectionType::Guides) { UE::Groom::Private::ExtractSkinningData(GroomCollection, BoneIndicesKeyValue, BoneWeightsKeyValue); } else { UE::Groom::Private::ExtractSkinningData(GroomCollection, BoneIndicesKeyValue, BoneWeightsKeyValue); } SetValue(Context, MoveTemp(GroomCollection), &Collection); } else if (Out->IsA(&BoneIndicesKey)) { SetValue(Context, MoveTemp(BoneIndicesKeyValue), &BoneIndicesKey); } else if (Out->IsA(&BoneWeightsKey)) { SetValue(Context, MoveTemp(BoneWeightsKeyValue), &BoneWeightsKey); } } TArray FUnpackSkinWeightsDataflowNode::GetRenderParametersImpl() const { if(CurvesType == EGroomCollectionType::Guides) { return { {TEXT("GuidesRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } else { return { {TEXT("StrandsRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } } FCollectionAttributeKey FUnpackSkinWeightsDataflowNode::GetBoneIndicesKey() const { // Get the pin value if plugged FCollectionAttributeKey Key; Key.Group = (CurvesType == EGroomCollectionType::Guides) ? UE::Groom::FGroomGuidesFacade::VerticesGroup.ToString() : UE::Groom::FGroomStrandsFacade::VerticesGroup.ToString(); Key.Attribute = BoneIndicesName; return Key; } FCollectionAttributeKey FUnpackSkinWeightsDataflowNode::GetBoneWeightsKey() const { // Get the pin value if plugged FCollectionAttributeKey Key; Key.Group = (CurvesType == EGroomCollectionType::Guides) ? UE::Groom::FGroomGuidesFacade::VerticesGroup.ToString() : UE::Groom::FGroomStrandsFacade::VerticesGroup.ToString(); Key.Attribute = BoneWeightsName; return Key; } void FPackSkinWeightsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace UE::Dataflow; // Get the pin value if plugged FCollectionAttributeKey BoneIndicesKeyValue = GetBoneIndicesKey(Context); FCollectionAttributeKey BoneWeightsKeyValue = GetBoneWeightsKey(Context); if (Out->IsA(&Collection)) { // Evaluate in collection FManagedArrayCollection GroomCollection = GetValue(Context, &Collection); if(CurvesType == EGroomCollectionType::Guides) { UE::Groom::Private::ReportSkinningData(GroomCollection, BoneIndicesKeyValue, BoneWeightsKeyValue); } else { UE::Groom::Private::ReportSkinningData(GroomCollection, BoneIndicesKeyValue, BoneWeightsKeyValue); } SetValue(Context, MoveTemp(GroomCollection), &Collection); } } TArray FPackSkinWeightsDataflowNode::GetRenderParametersImpl() const { if(CurvesType == EGroomCollectionType::Guides) { return { {TEXT("GuidesRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } else { return { {TEXT("StrandsRender"), FName("FGroomCollection"), {TEXT("Collection")}}}; } } FCollectionAttributeKey FPackSkinWeightsDataflowNode::GetBoneIndicesKey(UE::Dataflow::FContext& Context) const { // Get the pin value if plugged FCollectionAttributeKey Key = GetValue(Context, &BoneIndicesKey, BoneIndicesKey); // If nothing set used the local value if(Key.Attribute.IsEmpty() && Key.Group.IsEmpty()) { Key.Group = (CurvesType == EGroomCollectionType::Guides) ? UE::Groom::FGroomGuidesFacade::VerticesGroup.ToString() : UE::Groom::FGroomStrandsFacade::VerticesGroup.ToString(); Key.Attribute = BoneIndicesName; } return Key; } FCollectionAttributeKey FPackSkinWeightsDataflowNode::GetBoneWeightsKey(UE::Dataflow::FContext& Context) const { // Get the pin value if plugged FCollectionAttributeKey Key = GetValue(Context, &BoneWeightsKey, BoneWeightsKey); // If nothing set used the local value if(Key.Attribute.IsEmpty() && Key.Group.IsEmpty()) { Key.Group = (CurvesType == EGroomCollectionType::Guides) ? UE::Groom::FGroomGuidesFacade::VerticesGroup.ToString() : UE::Groom::FGroomStrandsFacade::VerticesGroup.ToString(); Key.Attribute = BoneWeightsName; } return Key; }