// Copyright Epic Games, Inc. All Rights Reserved. #include "ChaosClothAsset/SelectionNode.h" #include "ChaosClothAsset/ClothAsset.h" #include "ChaosClothAsset/ClothCollectionGroup.h" #include "ChaosClothAsset/CollectionClothFacade.h" #include "ChaosClothAsset/ClothDataflowTools.h" #include "ChaosClothAsset/ClothGeometryTools.h" #include "ChaosClothAsset/CollectionClothSelectionFacade.h" #include "ChaosClothAsset/WeightedValue.h" #include "Dataflow/DataflowInputOutput.h" #include "Dataflow/DataflowObject.h" #include "InteractiveToolChange.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(SelectionNode) #define LOCTEXT_NAMESPACE "FChaosClothAssetSelectionNode" namespace UE::Chaos::ClothAsset::Private { void ConvertWeightMapToVertexSelection(const TArray& WeightMap, const float TransferSelectionThreshold, TSet& OutSelection) { OutSelection.Reset(); for (int32 Index = 0; Index < WeightMap.Num(); ++Index) { if (WeightMap[Index] >= TransferSelectionThreshold) { OutSelection.Add(Index); } } } void ConvertWeightMapToFaceSelection(const TArray& WeightMap, const float TransferSelectionThreshold, const TConstArrayView& Indices, TSet& OutSelection) { OutSelection.Reset(); for (int32 FaceIndex = 0; FaceIndex < Indices.Num(); ++FaceIndex) { const FIntVector& Element = Indices[FaceIndex]; if (WeightMap[Element[0]] >= TransferSelectionThreshold && WeightMap[Element[1]] >= TransferSelectionThreshold && WeightMap[Element[2]] >= TransferSelectionThreshold) { OutSelection.Add(FaceIndex); } } } template bool TransferSelectionSet(const TSharedRef& TransferClothCollection, const TSharedRef& ClothCollection, const FName& InInputName, const FName& SelectionGroupName, const EChaosClothAssetWeightMapTransferType SimTransferType, const float TransferSelectionThreshold, TSet& OutSelection) { FCollectionClothConstFacade ClothFacade(ClothCollection); FCollectionClothConstFacade TransferClothFacade(TransferClothCollection); FCollectionClothSelectionConstFacade TransferSelectionFacade(TransferClothCollection); const bool bIsValidRenderSelection = SelectionGroupName == ClothCollectionGroup::RenderFaces || SelectionGroupName == ClothCollectionGroup::RenderVertices; const bool bIsValidSimSelection = SelectionGroupName == ClothCollectionGroup::SimFaces || SelectionGroupName == ClothCollectionGroup::SimVertices2D || SelectionGroupName == ClothCollectionGroup::SimVertices3D; if (!bIsValidRenderSelection && !bIsValidSimSelection) { return false; } // Get the selection as a vertex set. TSet TransferSet; const FName DesiredTransferGroup = bIsValidRenderSelection ? ClothCollectionGroup::RenderVertices : (SimTransferType == EChaosClothAssetWeightMapTransferType::Use2DSimMesh ? ClothCollectionGroup::SimVertices2D : ClothCollectionGroup::SimVertices3D); PRAGMA_DISABLE_DEPRECATION_WARNINGS if (!FClothGeometryTools::ConvertSelectionToNewGroupType(TransferClothCollection, InInputName, DesiredTransferGroup, bIsSecondarySelection, TransferSet)) { return false; } PRAGMA_ENABLE_DEPRECATION_WARNINGS // Convert to weights that are 0 on unselected vertices and 1 on selected vertices TArray TransferWeights; TransferWeights.SetNumZeroed(TransferClothCollection->NumElements(DesiredTransferGroup)); for (const int32 SetIndex : TransferSet) { if (TransferWeights.IsValidIndex(SetIndex)) { TransferWeights[SetIndex] = 1.f; } } // Transfer weights TArray RemappedWeights; RemappedWeights.SetNumZeroed(ClothCollection->NumElements(DesiredTransferGroup)); if (bIsValidRenderSelection) { FClothGeometryTools::TransferWeightMap( TransferClothFacade.GetRenderPosition(), TransferClothFacade.GetRenderIndices(), TransferWeights, ClothFacade.GetRenderPosition(), ClothFacade.GetRenderNormal(), ClothFacade.GetRenderIndices(), TArrayView(RemappedWeights)); OutSelection.Reset(); if (SelectionGroupName == ClothCollectionGroup::RenderFaces) { ConvertWeightMapToFaceSelection(RemappedWeights, TransferSelectionThreshold, ClothFacade.GetRenderIndices(), OutSelection); } else { check(SelectionGroupName == ClothCollectionGroup::RenderVertices); ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, OutSelection); } } else if (SimTransferType == EChaosClothAssetWeightMapTransferType::Use2DSimMesh) { TArray TransferSimPositions2DAs3D; TConstArrayView TransferPositions2D = TransferClothFacade.GetSimPosition2D(); TransferSimPositions2DAs3D.SetNumUninitialized(TransferPositions2D.Num()); for (int32 Index = 0; Index < TransferPositions2D.Num(); ++Index) { TransferSimPositions2DAs3D[Index] = FVector3f(TransferPositions2D[Index], 0.f); } TConstArrayView Positions2D = ClothFacade.GetSimPosition2D(); TArray Positions2DAs3D; TArray NormalsZAxis; Positions2DAs3D.SetNumUninitialized(Positions2D.Num()); NormalsZAxis.Init(FVector3f::ZAxisVector, Positions2D.Num()); for (int32 Index = 0; Index < Positions2D.Num(); ++Index) { Positions2DAs3D[Index] = FVector3f(Positions2D[Index], 0.f); } FClothGeometryTools::TransferWeightMap( TConstArrayView(TransferSimPositions2DAs3D), TransferClothFacade.GetSimIndices2D(), TransferWeights, TConstArrayView(Positions2DAs3D), TConstArrayView(NormalsZAxis), ClothFacade.GetSimIndices2D(), TArrayView(RemappedWeights)); if (SelectionGroupName == ClothCollectionGroup::SimFaces) { ConvertWeightMapToFaceSelection(RemappedWeights, TransferSelectionThreshold, ClothFacade.GetSimIndices2D(), OutSelection); } else if (SelectionGroupName == ClothCollectionGroup::SimVertices2D) { ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, OutSelection); } else { check(SelectionGroupName == ClothCollectionGroup::SimVertices3D); TSet Selection2D; ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, Selection2D); const TConstArrayView SimVertex3DLookup = ClothFacade.GetSimVertex3DLookup(); for (const int32 Vertex2D : Selection2D) { OutSelection.Add(SimVertex3DLookup[Vertex2D]); } } } else { check(SimTransferType == EChaosClothAssetWeightMapTransferType::Use3DSimMesh); FClothGeometryTools::TransferWeightMap( TransferClothFacade.GetSimPosition3D(), TransferClothFacade.GetSimIndices3D(), TransferWeights, ClothFacade.GetSimPosition3D(), ClothFacade.GetSimNormal(), ClothFacade.GetSimIndices3D(), TArrayView(RemappedWeights)); if (SelectionGroupName == ClothCollectionGroup::SimFaces) { ConvertWeightMapToFaceSelection(RemappedWeights, TransferSelectionThreshold, ClothFacade.GetSimIndices3D(), OutSelection); } else if (SelectionGroupName == ClothCollectionGroup::SimVertices3D) { ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, OutSelection); } else { check(SelectionGroupName == ClothCollectionGroup::SimVertices2D); TSet Selection3D; ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, Selection3D); const TConstArrayView> SimVertex2DLookup = ClothFacade.GetSimVertex2DLookup(); for (const int32 Vertex3D : Selection3D) { for (const int32 Vertex2D : SimVertex2DLookup[Vertex3D]) { OutSelection.Add(Vertex2D); } } } } return true; } void SetIndices(const TSet& InputSet, const TSet& FinalSet, EChaosClothAssetSelectionOverrideType OverrideType, TSet& Indices, TSet& RemoveIndices) { if (InputSet.IsEmpty() || OverrideType == EChaosClothAssetSelectionOverrideType::ReplaceAll) { Indices = FinalSet; RemoveIndices.Reset(); return; } Indices = FinalSet.Difference(InputSet); RemoveIndices = InputSet.Difference(FinalSet); } void CalculateFinalSet(const TSet& InputSet, TSet& FinalSet, EChaosClothAssetSelectionOverrideType OverrideType, const TSet& Indices, const TSet& RemoveIndices) { if (InputSet.IsEmpty() || OverrideType == EChaosClothAssetSelectionOverrideType::ReplaceAll) { FinalSet = Indices; return; } FinalSet = InputSet; FinalSet.Append(Indices); if (!RemoveIndices.IsEmpty()) { FinalSet = FinalSet.Difference(RemoveIndices); } } } // namespace UE::Chaos::ClothAsset::Private // Object encapsulating a change to the Selection Node's values. Used for Undo/Redo. class FChaosClothAssetSelectionNode_v2::FSelectionNodeChange final : public FToolCommandChange { public: FSelectionNodeChange(const FChaosClothAssetSelectionNode_v2& Node) : NodeGuid(Node.GetGuid()) , SavedName(Node.OutputName.StringValue) , SavedSelectionOverrideType(Node.SelectionOverrideType) , SavedGroup(Node.Group) , SavedIndices(Node.Indices) , SavedRemoveIndices(Node.RemoveIndices) {} private: FGuid NodeGuid; FString SavedName; EChaosClothAssetSelectionOverrideType SavedSelectionOverrideType; FChaosClothAssetNodeSelectionGroup SavedGroup; TSet SavedIndices; TSet SavedRemoveIndices; virtual FString ToString() const final { return TEXT("ChaosClothAssetSelectionNodeChange"); } virtual void Apply(UObject* Object) final { SwapApplyRevert(Object); } virtual void Revert(UObject* Object) final { SwapApplyRevert(Object); } void SwapApplyRevert(UObject* Object) { if (UDataflow* const Dataflow = Cast(Object)) { if (const TSharedPtr BaseNode = Dataflow->GetDataflow()->FindBaseNode(NodeGuid)) { if (FChaosClothAssetSelectionNode_v2* const Node = BaseNode->AsType()) { Swap(Node->OutputName.StringValue, SavedName); Swap(Node->SelectionOverrideType, SavedSelectionOverrideType); Swap(Node->Group, SavedGroup); Swap(Node->Indices, SavedIndices); Swap(Node->RemoveIndices, SavedRemoveIndices); Node->Invalidate(); } } } } }; FChaosClothAssetSelectionNode_v2::FChaosClothAssetSelectionNode_v2(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid) : FDataflowNode(InParam, InGuid) , Import(FDataflowFunctionProperty::FDelegate::CreateRaw(this, &FChaosClothAssetSelectionNode_v2::OnImport)) , ImportSecondary(FDataflowFunctionProperty::FDelegate::CreateRaw(this, &FChaosClothAssetSelectionNode_v2::OnImportSecondary)) , Transfer(FDataflowFunctionProperty::FDelegate::CreateRaw(this, &FChaosClothAssetSelectionNode_v2::OnTransfer)) { RegisterInputConnection(&Collection); RegisterInputConnection(&InputName.StringValue, GET_MEMBER_NAME_CHECKED(FChaosClothAssetConnectableIStringValue, StringValue)) .SetCanHidePin(true) .SetPinIsHidden(true); RegisterInputConnection(&TransferCollection) .SetCanHidePin(true) .SetPinIsHidden(true); RegisterOutputConnection(&Collection, &Collection); RegisterOutputConnection(&OutputName.StringValue, (FString*)nullptr, GET_MEMBER_NAME_CHECKED(FChaosClothAssetConnectableOStringValue, StringValue)); } void FChaosClothAssetSelectionNode_v2::OnImport(UE::Dataflow::FContext& Context) { using namespace UE::Chaos::ClothAsset; FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); const FCollectionClothSelectionConstFacade SelectionFacade(ClothCollection); const FName InInputName = GetInputName(Context); if (const TSet* const SelectionSet = SelectionFacade.IsValid() ? SelectionFacade.FindSelectionSet(InInputName) : nullptr) { Indices = *SelectionSet; RemoveIndices.Empty(); Group.Name = SelectionFacade.GetSelectionGroup(InInputName).ToString(); SelectionOverrideType = EChaosClothAssetSelectionOverrideType::ReplaceAll; } else { FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("NoMatchingSelectionFoundHeadline", "No matching selection found"), FText::Format(LOCTEXT("NoMatchingSelectionFoundDetails", "No matching selection with the name \"{0}\" has been found to import."), FText::FromName(InInputName))); } } void FChaosClothAssetSelectionNode_v2::OnImportSecondary(UE::Dataflow::FContext& Context) { using namespace UE::Chaos::ClothAsset; FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); const FCollectionClothSelectionConstFacade SelectionFacade(ClothCollection); const FName InInputName = GetInputName(Context); PRAGMA_DISABLE_DEPRECATION_WARNINGS if (const TSet* const SelectionSet = SelectionFacade.IsValid() ? SelectionFacade.FindSelectionSecondarySet(InInputName) : nullptr) { Indices = *SelectionSet; RemoveIndices.Empty(); Group.Name = SelectionFacade.GetSelectionSecondaryGroup(InInputName).ToString(); SelectionOverrideType = EChaosClothAssetSelectionOverrideType::ReplaceAll; } PRAGMA_ENABLE_DEPRECATION_WARNINGS else { FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("NoMatchingSecondarySelectionFoundHeadline", "No matching secondary selection found"), FText::Format(LOCTEXT("NoMatchingSecondarySelectionFoundDetails", "No matching secondary selection with the name \"{0}\" has been found to import."), FText::FromName(InInputName))); } } void FChaosClothAssetSelectionNode_v2::OnTransfer(UE::Dataflow::FContext& Context) { using namespace UE::Chaos::ClothAsset; // Transfer selection if the transfer collection input has changed and is valid FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); FCollectionClothConstFacade ClothFacade(ClothCollection); if (ClothFacade.HasValidSimulationData()) // Can only act on the collection if it is a valid cloth collection { FManagedArrayCollection InTransferCollection = GetValue(Context, &TransferCollection); const TSharedRef TransferClothCollection = MakeShared(MoveTemp(InTransferCollection)); FCollectionClothConstFacade TransferClothFacade(TransferClothCollection); FCollectionClothSelectionConstFacade TransferSelectionFacade(TransferClothCollection); const FName InInputName = GetInputName(Context); const FName SelectionGroupName(*Group.Name); TSet PrimaryFinalSelection; if (Private::TransferSelectionSet(TransferClothCollection, ClothCollection, InInputName, SelectionGroupName, SimTransferType, TransferSelectionThreshold, PrimaryFinalSelection)) { TSet InputSelection; FClothGeometryTools::ConvertSelectionToNewGroupType(ClothCollection, InInputName, SelectionGroupName, InputSelection); SetIndices(InputSelection, PrimaryFinalSelection); } } } void FChaosClothAssetSelectionNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace UE::Chaos::ClothAsset; auto CopyIntoSelection = [this](const TSharedRef SelectionCollection, const FName& SelectionGroupName, const TSet& SourceIndices, TSet& DestSelectionSet) { const int32 NumElementsInGroup = SelectionCollection->NumElements(SelectionGroupName); bool bFoundAnyInvalidIndex = false; DestSelectionSet.Reset(); for (const int32 Index : SourceIndices) { if (Index < 0 || Index >= NumElementsInGroup) { const FText LogErrorMessage = FText::Format(LOCTEXT("SelectionIndexOutOfBoundsDetails", "Selection index {0} not valid for group \"{1}\" with {2} elements"), Index, FText::FromName(SelectionGroupName), NumElementsInGroup); // Log all indices, but toast once UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("%s"), *LogErrorMessage.ToString()); bFoundAnyInvalidIndex = true; } else { DestSelectionSet.Add(Index); } } if (bFoundAnyInvalidIndex) { // Toast once const FText ToastErrorMessage = FText::Format(LOCTEXT("AnySelectionIndexOutOfBoundsDetails", "Found invalid selection indices for group \"{0}.\" See log for details"), FText::FromName(SelectionGroupName)); FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("AnySelectionIndexOutOfBoundsHeadline", "Invalid selection"), ToastErrorMessage); } }; if (Out->IsA(&Collection)) { // Evaluate InputName const FName InInputName = GetInputName(Context); const FName SelectionName(OutputName.StringValue.IsEmpty() ? InInputName : FName(OutputName.StringValue)); if (SelectionName == NAME_None || Group.Name.IsEmpty()) { const FManagedArrayCollection& SelectionCollection = GetValue(Context, &Collection); SetValue(Context, SelectionCollection, &Collection); return; } const FName SelectionGroupName(*Group.Name); FManagedArrayCollection InSelectionCollection = GetValue(Context, &Collection); const TSharedRef SelectionCollection = MakeShared(MoveTemp(InSelectionCollection)); FCollectionClothSelectionFacade SelectionFacade(SelectionCollection); SelectionFacade.DefineSchema(); check(SelectionFacade.IsValid()); TSet InputSelectionSet; FClothGeometryTools::ConvertSelectionToNewGroupType(SelectionCollection, InInputName, SelectionGroupName, InputSelectionSet); TSet FinalSet; CalculateFinalSet(InputSelectionSet, FinalSet); TSet& SelectionSet = SelectionFacade.FindOrAddSelectionSet(SelectionName, SelectionGroupName); CopyIntoSelection(SelectionCollection, SelectionGroupName, FinalSet, SelectionSet); SetValue(Context, MoveTemp(*SelectionCollection), &Collection); } else if (Out->IsA(&OutputName.StringValue)) { FString InputNameString = GetValue(Context, &InputName.StringValue); FClothDataflowTools::MakeCollectionName(InputNameString); SetValue(Context, OutputName.StringValue.IsEmpty() ? InputNameString : OutputName.StringValue, &OutputName.StringValue); } } FName FChaosClothAssetSelectionNode_v2::GetInputName(UE::Dataflow::FContext& Context) const { FString InputNameString = GetValue(Context, &InputName.StringValue); UE::Chaos::ClothAsset::FClothDataflowTools::MakeCollectionName(InputNameString); const FName InInputName(*InputNameString); return InInputName != NAME_None ? InInputName : FName(OutputName.StringValue); } void FChaosClothAssetSelectionNode_v2::SetIndices(const TSet& InputSet, const TSet& FinalSet) { UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices); } void FChaosClothAssetSelectionNode_v2::CalculateFinalSet(const TSet& InputSet, TSet& FinalSet) const { UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices); } TUniquePtr FChaosClothAssetSelectionNode_v2::MakeSelectedNodeChange(const FChaosClothAssetSelectionNode_v2& Node) { return MakeUnique(Node); } FChaosClothAssetSelectionNode::FChaosClothAssetSelectionNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid) : FDataflowTerminalNode(InParam, InGuid) { RegisterInputConnection(&Collection); RegisterInputConnection(&InputName.StringValue, GET_MEMBER_NAME_CHECKED(FChaosClothAssetConnectableIStringValue, StringValue)) .SetCanHidePin(true) .SetPinIsHidden(true); RegisterInputConnection(&TransferCollection) .SetCanHidePin(true) .SetPinIsHidden(true); RegisterOutputConnection(&Collection, &Collection); RegisterOutputConnection(&Name); } void FChaosClothAssetSelectionNode::SetAssetValue(TObjectPtr Asset, UE::Dataflow::FContext& Context) const { using namespace UE::Chaos::ClothAsset; if (UChaosClothAsset* const ClothAsset = Cast(Asset.Get())) { if (UDataflow* const DataflowAsset = ClothAsset->GetDataflow()) { const TSharedPtr Dataflow = DataflowAsset->GetDataflow(); if (const TSharedPtr BaseNode = Dataflow->FindBaseNode(this->GetGuid())) // This is basically a safe const_cast { FChaosClothAssetSelectionNode* const MutableThis = static_cast(BaseNode.Get()); check(MutableThis == this); // Make the name a valid attribute name, and replace the value in the UI FClothDataflowTools::MakeCollectionName(MutableThis->Name); const FName SelectionGroupName(*Group.Name); const FName SelectionSecondaryGroupName(*SecondaryGroup.Name); // Transfer selection if the transfer collection input has changed and is valid FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); FCollectionClothConstFacade ClothFacade(ClothCollection); if (ClothFacade.HasValidSimulationData()) // Can only act on the collection if it is a valid cloth collection { FManagedArrayCollection InTransferCollection = GetValue(Context, &TransferCollection); const TSharedRef TransferClothCollection = MakeShared(MoveTemp(InTransferCollection)); FCollectionClothConstFacade TransferClothFacade(TransferClothCollection); FCollectionClothSelectionConstFacade TransferSelectionFacade(TransferClothCollection); const FName InInputName = GetInputName(Context); uint32 InTransferCollectionHash = HashCombineFast(GetTypeHash(InInputName), GetTypeHash(SelectionGroupName)); InTransferCollectionHash = HashCombineFast(InTransferCollectionHash, GetTypeHash(SelectionSecondaryGroupName)); InTransferCollectionHash = HashCombineFast(InTransferCollectionHash, (uint32)SimTransferType); if (TransferClothFacade.HasValidSimulationData() && TransferSelectionFacade.IsValid() && InInputName != NAME_None && TransferSelectionFacade.HasSelection(InInputName)) { InTransferCollectionHash = HashCombineFast(InTransferCollectionHash, GetTypeHash(TransferSelectionFacade.GetSelectionGroup(InInputName))); const TArray SelectionAsArray = TransferSelectionFacade.GetSelectionSet(InInputName).Array(); InTransferCollectionHash = GetArrayHash(SelectionAsArray.GetData(), SelectionAsArray.Num(), InTransferCollectionHash); PRAGMA_DISABLE_DEPRECATION_WARNINGS if (TransferSelectionFacade.HasSelectionSecondarySet(InInputName)) { InTransferCollectionHash = HashCombineFast(InTransferCollectionHash, GetTypeHash(TransferSelectionFacade.GetSelectionSecondaryGroup(InInputName))); const TArray SecondarySelectionAsArray = TransferSelectionFacade.GetSelectionSecondarySet(InInputName).Array(); InTransferCollectionHash = GetArrayHash(SecondarySelectionAsArray.GetData(), SecondarySelectionAsArray.Num(), InTransferCollectionHash); } PRAGMA_ENABLE_DEPRECATION_WARNINGS } else { InTransferCollectionHash = 0; } if (TransferCollectionHash != InTransferCollectionHash) { MutableThis->TransferCollectionHash = InTransferCollectionHash; if(TransferCollectionHash) { TSet PrimaryFinalSelection; if (Private::TransferSelectionSet(TransferClothCollection, ClothCollection, InInputName, SelectionGroupName, SimTransferType, TransferSelectionThreshold, PrimaryFinalSelection)) { TSet InputSelection; FClothGeometryTools::ConvertSelectionToNewGroupType(ClothCollection, InInputName, SelectionGroupName, InputSelection); MutableThis->SetIndices(InputSelection, PrimaryFinalSelection); } TSet SecondaryFinalSelection; if (Private::TransferSelectionSet(TransferClothCollection, ClothCollection, InInputName, SelectionSecondaryGroupName, SimTransferType, TransferSelectionThreshold, SecondaryFinalSelection)) { TSet InputSelection; PRAGMA_DISABLE_DEPRECATION_WARNINGS FClothGeometryTools::ConvertSelectionToNewGroupType(ClothCollection, InInputName, SelectionGroupName, true, InputSelection); MutableThis->SetSecondaryIndices(InputSelection, SecondaryFinalSelection); PRAGMA_ENABLE_DEPRECATION_WARNINGS } } } } } } } } void FChaosClothAssetSelectionNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace UE::Chaos::ClothAsset; auto CopyIntoSelection = [this](const TSharedRef SelectionCollection, const FName& SelectionGroupName, const TSet& SourceIndices, TSet& DestSelectionSet) { const int32 NumElementsInGroup = SelectionCollection->NumElements(SelectionGroupName); bool bFoundAnyInvalidIndex = false; DestSelectionSet.Reset(); for (const int32 Index : SourceIndices) { if (Index < 0 || Index >= NumElementsInGroup) { const FText LogErrorMessage = FText::Format(LOCTEXT("SelectionIndexOutOfBoundsDetails", "Selection index {0} not valid for group \"{1}\" with {2} elements"), Index, FText::FromName(SelectionGroupName), NumElementsInGroup); // Log all indices, but toast once UE_LOG(LogChaosClothAssetDataflowNodes, Warning, TEXT("%s"), *LogErrorMessage.ToString()); bFoundAnyInvalidIndex = true; } else { DestSelectionSet.Add(Index); } } if (bFoundAnyInvalidIndex) { // Toast once const FText ToastErrorMessage = FText::Format(LOCTEXT("AnySelectionIndexOutOfBoundsDetails", "Found invalid selection indices for group \"{0}.\" See log for details"), FText::FromName(SelectionGroupName)); FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("AnySelectionIndexOutOfBoundsHeadline", "Invalid selection"), ToastErrorMessage); } }; if (Out->IsA(&Collection)) { // Evaluate InputName const FName InInputName = GetInputName(Context); const FName SelectionName(Name.IsEmpty() ? InInputName : FName(Name)); if (SelectionName == NAME_None || Group.Name.IsEmpty()) { const FManagedArrayCollection& SelectionCollection = GetValue(Context, &Collection); SetValue(Context, SelectionCollection, &Collection); return; } const FName SelectionGroupName(*Group.Name); FManagedArrayCollection InSelectionCollection = GetValue(Context, &Collection); const TSharedRef SelectionCollection = MakeShared(MoveTemp(InSelectionCollection)); FCollectionClothSelectionFacade SelectionFacade(SelectionCollection); SelectionFacade.DefineSchema(); check(SelectionFacade.IsValid()); TSet InputSelectionSet; FClothGeometryTools::ConvertSelectionToNewGroupType(SelectionCollection, InInputName, SelectionGroupName, InputSelectionSet); TSet FinalSet; CalculateFinalSet(InputSelectionSet, FinalSet); TSet& SelectionSet = SelectionFacade.FindOrAddSelectionSet(SelectionName, SelectionGroupName); CopyIntoSelection(SelectionCollection, SelectionGroupName, FinalSet, SelectionSet); if (!SecondaryGroup.Name.IsEmpty() && !SecondaryIndices.IsEmpty()) { const FName SecondarySelectionName(Name.IsEmpty() ? InInputName : FName(Name)); const FName SecondarySelectionGroupName = (*SecondaryGroup.Name); InputSelectionSet.Reset(); PRAGMA_DISABLE_DEPRECATION_WARNINGS FClothGeometryTools::ConvertSelectionToNewGroupType(SelectionCollection, InInputName, SelectionGroupName, true, InputSelectionSet); FinalSet.Reset(); CalculateFinalSecondarySet(InputSelectionSet, FinalSet); TSet& SecondarySelectionSet = SelectionFacade.FindOrAddSelectionSecondarySet(SecondarySelectionName, SecondarySelectionGroupName); PRAGMA_ENABLE_DEPRECATION_WARNINGS CopyIntoSelection(SelectionCollection, SecondarySelectionGroupName, FinalSet, SecondarySelectionSet); } SetValue(Context, MoveTemp(*SelectionCollection), &Collection); } else if (Out->IsA(&Name)) { FString InputNameString = GetValue(Context, &InputName.StringValue); FClothDataflowTools::MakeCollectionName(InputNameString); SetValue(Context, Name.IsEmpty() ? InputNameString : Name, &Name); } } void FChaosClothAssetSelectionNode::Serialize(FArchive& Ar) { using namespace UE::Chaos::ClothAsset; // This is just for convenience and can be removed post 5.4 once the plugin loses its experimental status PRAGMA_DISABLE_DEPRECATION_WARNINGS if (Ar.IsLoading() && Type_DEPRECATED != EChaosClothAssetSelectionType::Deprecated) { switch (Type_DEPRECATED) { case EChaosClothAssetSelectionType::SimVertex2D: Group.Name = ClothCollectionGroup::SimVertices2D.ToString(); break; case EChaosClothAssetSelectionType::SimVertex3D: Group.Name = ClothCollectionGroup::SimVertices3D.ToString(); break; case EChaosClothAssetSelectionType::RenderVertex: Group.Name = ClothCollectionGroup::RenderVertices.ToString(); break; case EChaosClothAssetSelectionType::SimFace: Group.Name = ClothCollectionGroup::SimFaces.ToString(); break; case EChaosClothAssetSelectionType::RenderFace: Group.Name = ClothCollectionGroup::RenderFaces.ToString(); break; default: checkNoEntry(); } Type_DEPRECATED = EChaosClothAssetSelectionType::Deprecated; // This is only for clarity since the Type property won't be saved from now on FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("DeprecatedSelectionType", "Outdated Dataflow asset."), LOCTEXT("DeprecatedSelectionDetails", "This node is out of date and contains deprecated data. The asset needs to be re-saved before it stops working at the next version update.")); } PRAGMA_ENABLE_DEPRECATION_WARNINGS } FName FChaosClothAssetSelectionNode::GetInputName(UE::Dataflow::FContext& Context) const { FString InputNameString = GetValue(Context, &InputName.StringValue); UE::Chaos::ClothAsset::FClothDataflowTools::MakeCollectionName(InputNameString); const FName InInputName(*InputNameString); return InInputName != NAME_None ? InInputName : FName(Name); } void FChaosClothAssetSelectionNode::SetIndices(const TSet& InputSet, const TSet& FinalSet) { UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices); } void FChaosClothAssetSelectionNode::SetSecondaryIndices(const TSet& InputSet, const TSet& FinalSet) { UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, SecondaryIndices, RemoveSecondaryIndices); } void FChaosClothAssetSelectionNode::CalculateFinalSet(const TSet& InputSet, TSet& FinalSet) const { UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices); } void FChaosClothAssetSelectionNode::CalculateFinalSecondarySet(const TSet& InputSet, TSet& FinalSet) const { UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, SecondaryIndices, RemoveSecondaryIndices); } #undef LOCTEXT_NAMESPACE