Files
UnrealEngine/Engine/Plugins/ChaosClothAssetEditor/Source/ChaosClothAssetDataflowNodes/Private/ChaosClothAsset/SelectionNode.cpp
2025-05-18 13:04:45 +08:00

735 lines
31 KiB
C++

// 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<float>& WeightMap, const float TransferSelectionThreshold, TSet<int32>& OutSelection)
{
OutSelection.Reset();
for (int32 Index = 0; Index < WeightMap.Num(); ++Index)
{
if (WeightMap[Index] >= TransferSelectionThreshold)
{
OutSelection.Add(Index);
}
}
}
void ConvertWeightMapToFaceSelection(const TArray<float>& WeightMap, const float TransferSelectionThreshold, const TConstArrayView<FIntVector3>& Indices, TSet<int32>& 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 bIsSecondarySelection>
bool TransferSelectionSet(const TSharedRef<const FManagedArrayCollection>& TransferClothCollection, const TSharedRef<FManagedArrayCollection>& ClothCollection,
const FName& InInputName, const FName& SelectionGroupName, const EChaosClothAssetWeightMapTransferType SimTransferType, const float TransferSelectionThreshold, TSet<int32>& 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<int32> 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<float> TransferWeights;
TransferWeights.SetNumZeroed(TransferClothCollection->NumElements(DesiredTransferGroup));
for (const int32 SetIndex : TransferSet)
{
if (TransferWeights.IsValidIndex(SetIndex))
{
TransferWeights[SetIndex] = 1.f;
}
}
// Transfer weights
TArray<float> RemappedWeights;
RemappedWeights.SetNumZeroed(ClothCollection->NumElements(DesiredTransferGroup));
if (bIsValidRenderSelection)
{
FClothGeometryTools::TransferWeightMap(
TransferClothFacade.GetRenderPosition(),
TransferClothFacade.GetRenderIndices(),
TransferWeights,
ClothFacade.GetRenderPosition(),
ClothFacade.GetRenderNormal(),
ClothFacade.GetRenderIndices(),
TArrayView<float>(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<FVector3f> TransferSimPositions2DAs3D;
TConstArrayView<FVector2f> TransferPositions2D = TransferClothFacade.GetSimPosition2D();
TransferSimPositions2DAs3D.SetNumUninitialized(TransferPositions2D.Num());
for (int32 Index = 0; Index < TransferPositions2D.Num(); ++Index)
{
TransferSimPositions2DAs3D[Index] = FVector3f(TransferPositions2D[Index], 0.f);
}
TConstArrayView<FVector2f> Positions2D = ClothFacade.GetSimPosition2D();
TArray<FVector3f> Positions2DAs3D;
TArray<FVector3f> 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<FVector3f>(TransferSimPositions2DAs3D),
TransferClothFacade.GetSimIndices2D(),
TransferWeights,
TConstArrayView<FVector3f>(Positions2DAs3D),
TConstArrayView<FVector3f>(NormalsZAxis),
ClothFacade.GetSimIndices2D(),
TArrayView<float>(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<int32> Selection2D;
ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, Selection2D);
const TConstArrayView<int32> 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<float>(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<int32> Selection3D;
ConvertWeightMapToVertexSelection(RemappedWeights, TransferSelectionThreshold, Selection3D);
const TConstArrayView<TArray<int32>> SimVertex2DLookup = ClothFacade.GetSimVertex2DLookup();
for (const int32 Vertex3D : Selection3D)
{
for (const int32 Vertex2D : SimVertex2DLookup[Vertex3D])
{
OutSelection.Add(Vertex2D);
}
}
}
}
return true;
}
void SetIndices(const TSet<int32>& InputSet, const TSet<int32>& FinalSet, EChaosClothAssetSelectionOverrideType OverrideType, TSet<int32>& Indices, TSet<int32>& RemoveIndices)
{
if (InputSet.IsEmpty() || OverrideType == EChaosClothAssetSelectionOverrideType::ReplaceAll)
{
Indices = FinalSet;
RemoveIndices.Reset();
return;
}
Indices = FinalSet.Difference(InputSet);
RemoveIndices = InputSet.Difference(FinalSet);
}
void CalculateFinalSet(const TSet<int32>& InputSet, TSet<int32>& FinalSet, EChaosClothAssetSelectionOverrideType OverrideType, const TSet<int32>& Indices, const TSet<int32>& 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<int32> SavedIndices;
TSet<int32> 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<UDataflow>(Object))
{
if (const TSharedPtr<FDataflowNode> BaseNode = Dataflow->GetDataflow()->FindBaseNode(NodeGuid))
{
if (FChaosClothAssetSelectionNode_v2* const Node = BaseNode->AsType<FChaosClothAssetSelectionNode_v2>())
{
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<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> ClothCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InCollection));
const FCollectionClothSelectionConstFacade SelectionFacade(ClothCollection);
const FName InInputName = GetInputName(Context);
if (const TSet<int32>* 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<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> ClothCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InCollection));
const FCollectionClothSelectionConstFacade SelectionFacade(ClothCollection);
const FName InInputName = GetInputName(Context);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (const TSet<int32>* 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<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> ClothCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InCollection));
FCollectionClothConstFacade ClothFacade(ClothCollection);
if (ClothFacade.HasValidSimulationData()) // Can only act on the collection if it is a valid cloth collection
{
FManagedArrayCollection InTransferCollection = GetValue<FManagedArrayCollection>(Context, &TransferCollection);
const TSharedRef<const FManagedArrayCollection> TransferClothCollection = MakeShared<const FManagedArrayCollection>(MoveTemp(InTransferCollection));
FCollectionClothConstFacade TransferClothFacade(TransferClothCollection);
FCollectionClothSelectionConstFacade TransferSelectionFacade(TransferClothCollection);
const FName InInputName = GetInputName(Context);
const FName SelectionGroupName(*Group.Name);
TSet<int32> PrimaryFinalSelection;
if (Private::TransferSelectionSet<false>(TransferClothCollection, ClothCollection, InInputName, SelectionGroupName, SimTransferType, TransferSelectionThreshold, PrimaryFinalSelection))
{
TSet<int32> 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<FManagedArrayCollection> SelectionCollection,
const FName& SelectionGroupName,
const TSet<int32>& SourceIndices,
TSet<int32>& 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<FManagedArrayCollection>(&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<FManagedArrayCollection>(Context, &Collection);
SetValue(Context, SelectionCollection, &Collection);
return;
}
const FName SelectionGroupName(*Group.Name);
FManagedArrayCollection InSelectionCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> SelectionCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InSelectionCollection));
FCollectionClothSelectionFacade SelectionFacade(SelectionCollection);
SelectionFacade.DefineSchema();
check(SelectionFacade.IsValid());
TSet<int32> InputSelectionSet;
FClothGeometryTools::ConvertSelectionToNewGroupType(SelectionCollection, InInputName, SelectionGroupName, InputSelectionSet);
TSet<int32> FinalSet;
CalculateFinalSet(InputSelectionSet, FinalSet);
TSet<int32>& SelectionSet = SelectionFacade.FindOrAddSelectionSet(SelectionName, SelectionGroupName);
CopyIntoSelection(SelectionCollection, SelectionGroupName, FinalSet, SelectionSet);
SetValue(Context, MoveTemp(*SelectionCollection), &Collection);
}
else if (Out->IsA<FString>(&OutputName.StringValue))
{
FString InputNameString = GetValue<FString>(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<FString>(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<int32>& InputSet, const TSet<int32>& FinalSet)
{
UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices);
}
void FChaosClothAssetSelectionNode_v2::CalculateFinalSet(const TSet<int32>& InputSet, TSet<int32>& FinalSet) const
{
UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices);
}
TUniquePtr<FToolCommandChange> FChaosClothAssetSelectionNode_v2::MakeSelectedNodeChange(const FChaosClothAssetSelectionNode_v2& Node)
{
return MakeUnique<FChaosClothAssetSelectionNode_v2::FSelectionNodeChange>(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<UObject> Asset, UE::Dataflow::FContext& Context) const
{
using namespace UE::Chaos::ClothAsset;
if (UChaosClothAsset* const ClothAsset = Cast<UChaosClothAsset>(Asset.Get()))
{
if (UDataflow* const DataflowAsset = ClothAsset->GetDataflow())
{
const TSharedPtr<UE::Dataflow::FGraph, ESPMode::ThreadSafe> Dataflow = DataflowAsset->GetDataflow();
if (const TSharedPtr<FDataflowNode> BaseNode = Dataflow->FindBaseNode(this->GetGuid())) // This is basically a safe const_cast
{
FChaosClothAssetSelectionNode* const MutableThis = static_cast<FChaosClothAssetSelectionNode*>(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<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> ClothCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InCollection));
FCollectionClothConstFacade ClothFacade(ClothCollection);
if (ClothFacade.HasValidSimulationData()) // Can only act on the collection if it is a valid cloth collection
{
FManagedArrayCollection InTransferCollection = GetValue<FManagedArrayCollection>(Context, &TransferCollection);
const TSharedRef<const FManagedArrayCollection> TransferClothCollection = MakeShared<const FManagedArrayCollection>(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<int32> 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<int32> 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<int32> PrimaryFinalSelection;
if (Private::TransferSelectionSet<false>(TransferClothCollection, ClothCollection, InInputName, SelectionGroupName, SimTransferType, TransferSelectionThreshold, PrimaryFinalSelection))
{
TSet<int32> InputSelection;
FClothGeometryTools::ConvertSelectionToNewGroupType(ClothCollection, InInputName, SelectionGroupName, InputSelection);
MutableThis->SetIndices(InputSelection, PrimaryFinalSelection);
}
TSet<int32> SecondaryFinalSelection;
if (Private::TransferSelectionSet<true>(TransferClothCollection, ClothCollection, InInputName, SelectionSecondaryGroupName, SimTransferType, TransferSelectionThreshold, SecondaryFinalSelection))
{
TSet<int32> 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<FManagedArrayCollection> SelectionCollection,
const FName& SelectionGroupName,
const TSet<int32>& SourceIndices,
TSet<int32>& 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<FManagedArrayCollection>(&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<FManagedArrayCollection>(Context, &Collection);
SetValue(Context, SelectionCollection, &Collection);
return;
}
const FName SelectionGroupName(*Group.Name);
FManagedArrayCollection InSelectionCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
const TSharedRef<FManagedArrayCollection> SelectionCollection = MakeShared<FManagedArrayCollection>(MoveTemp(InSelectionCollection));
FCollectionClothSelectionFacade SelectionFacade(SelectionCollection);
SelectionFacade.DefineSchema();
check(SelectionFacade.IsValid());
TSet<int32> InputSelectionSet;
FClothGeometryTools::ConvertSelectionToNewGroupType(SelectionCollection, InInputName, SelectionGroupName, InputSelectionSet);
TSet<int32> FinalSet;
CalculateFinalSet(InputSelectionSet, FinalSet);
TSet<int32>& 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<int32>& SecondarySelectionSet = SelectionFacade.FindOrAddSelectionSecondarySet(SecondarySelectionName, SecondarySelectionGroupName);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
CopyIntoSelection(SelectionCollection, SecondarySelectionGroupName, FinalSet, SecondarySelectionSet);
}
SetValue(Context, MoveTemp(*SelectionCollection), &Collection);
}
else if (Out->IsA<FString>(&Name))
{
FString InputNameString = GetValue<FString>(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<FString>(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<int32>& InputSet, const TSet<int32>& FinalSet)
{
UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices);
}
void FChaosClothAssetSelectionNode::SetSecondaryIndices(const TSet<int32>& InputSet, const TSet<int32>& FinalSet)
{
UE::Chaos::ClothAsset::Private::SetIndices(InputSet, FinalSet, SelectionOverrideType, SecondaryIndices, RemoveSecondaryIndices);
}
void FChaosClothAssetSelectionNode::CalculateFinalSet(const TSet<int32>& InputSet, TSet<int32>& FinalSet) const
{
UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, Indices, RemoveIndices);
}
void FChaosClothAssetSelectionNode::CalculateFinalSecondarySet(const TSet<int32>& InputSet, TSet<int32>& FinalSet) const
{
UE::Chaos::ClothAsset::Private::CalculateFinalSet(InputSet, FinalSet, SelectionOverrideType, SecondaryIndices, RemoveSecondaryIndices);
}
#undef LOCTEXT_NAMESPACE