// Copyright Epic Games, Inc. All Rights Reserved. #include "ChaosClothAsset/AddWeightMapNode.h" #include "ChaosClothAsset/ClothAsset.h" #include "ChaosClothAsset/ClothCollectionGroup.h" #include "ChaosClothAsset/ClothDataflowTools.h" #include "ChaosClothAsset/ClothGeometryTools.h" #include "ChaosClothAsset/CollectionClothFacade.h" #include "ChaosClothAsset/WeightedValue.h" #include "Dataflow/DataflowInputOutput.h" #include "Dataflow/DataflowObject.h" #include "InteractiveToolChange.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(AddWeightMapNode) #define LOCTEXT_NAMESPACE "ChaosClothAssetAddWeightMapNode" namespace UE::Chaos::ClothAsset::Private { void TransferWeightMap( const TConstArrayView& InSourcePositions, const TConstArrayView& SourceIndices, const TConstArrayView SourceWeightsLookup, const TConstArrayView& InSourceWeights, const TConstArrayView& InTargetPositions, const TConstArrayView& TargetIndices, const TConstArrayView TargetWeightsLookup, TArray& OutTargetWeights) { TArray SourcePositions; SourcePositions.SetNumUninitialized(InSourcePositions.Num()); for (int32 Index = 0; Index < SourcePositions.Num(); ++Index) { SourcePositions[Index] = FVector3f(InSourcePositions[Index], 0.f); } TArray SourceWeights; SourceWeights.SetNumUninitialized(InSourcePositions.Num()); for (int32 Index = 0; Index < SourceWeights.Num(); ++Index) { SourceWeights[Index] = InSourceWeights[SourceWeightsLookup[Index]]; } TArray TargetPositions; TArray TargetNormals; TargetPositions.SetNumUninitialized(InTargetPositions.Num()); TargetNormals.SetNumUninitialized(InTargetPositions.Num()); for (int32 Index = 0; Index < TargetPositions.Num(); ++Index) { TargetPositions[Index] = FVector3f(InTargetPositions[Index], 0.f); TargetNormals[Index] = FVector3f::ZAxisVector; } TArray TargetWeights; TargetWeights.SetNumUninitialized(TargetPositions.Num()); FClothGeometryTools::TransferWeightMap(SourcePositions, SourceIndices, SourceWeights, TargetPositions, TargetNormals, TargetIndices, TArrayView(TargetWeights)); for (int32 Index = 0; Index < TargetWeights.Num(); ++Index) { OutTargetWeights[TargetWeightsLookup[Index]] = TargetWeights[Index]; } } void SetVertexWeights(const TConstArrayView InputMap, const TArray& FinalValues, EChaosClothAssetWeightMapOverrideType OverrideType, TArray& SourceVertexWeights) { if (InputMap.IsEmpty() || OverrideType == EChaosClothAssetWeightMapOverrideType::ReplaceAll) { // Default input is 0, so OverrideType doesn't matter. SourceVertexWeights = FinalValues; return; } check(InputMap.Num() == FinalValues.Num()); SourceVertexWeights.SetNumUninitialized(FinalValues.Num()); for (int32 Index = 0; Index < FinalValues.Num(); ++Index) { switch (OverrideType) { case EChaosClothAssetWeightMapOverrideType::ReplaceChanged: if (InputMap[Index] == FinalValues[Index]) { PRAGMA_DISABLE_DEPRECATION_WARNINGS SourceVertexWeights[Index] = FChaosClothAssetAddWeightMapNode::ReplaceChangedPassthroughValue; PRAGMA_ENABLE_DEPRECATION_WARNINGS } else { SourceVertexWeights[Index] = FinalValues[Index]; } break; case EChaosClothAssetWeightMapOverrideType::Add: SourceVertexWeights[Index] = FinalValues[Index] - InputMap[Index]; break; default: unimplemented(); } } } void CalculateFinalVertexWeightValues(const TConstArrayView InputMap, TArrayView FinalOutputMap, EChaosClothAssetWeightMapOverrideType OverrideType, const TArray& SourceVertexWeights) { const int32 EndWeightIndex = FMath::Min(FinalOutputMap.Num(), SourceVertexWeights.Num()); if (InputMap.IsEmpty()) { for (int32 Index = 0; Index < EndWeightIndex; ++Index) { PRAGMA_DISABLE_DEPRECATION_WARNINGS FinalOutputMap[Index] = FMath::Clamp(SourceVertexWeights[Index] == FChaosClothAssetAddWeightMapNode::ReplaceChangedPassthroughValue ? 0.f : SourceVertexWeights[Index], 0.f, 1.f); PRAGMA_ENABLE_DEPRECATION_WARNINGS } return; } check(InputMap.Num() == FinalOutputMap.Num()); for (int32 Index = 0; Index < EndWeightIndex; ++Index) { PRAGMA_DISABLE_DEPRECATION_WARNINGS if(SourceVertexWeights[Index] == FChaosClothAssetAddWeightMapNode::ReplaceChangedPassthroughValue) PRAGMA_ENABLE_DEPRECATION_WARNINGS { // This value is only set when OverrideType == ReplaceChanged, but it's possible the override type changed. FinalOutputMap[Index] = FMath::Clamp(InputMap[Index], 0.f, 1.f); } else { switch (OverrideType) { case EChaosClothAssetWeightMapOverrideType::ReplaceAll: case EChaosClothAssetWeightMapOverrideType::ReplaceChanged: FinalOutputMap[Index] = FMath::Clamp(SourceVertexWeights[Index], 0.f, 1.f); break; case EChaosClothAssetWeightMapOverrideType::Add: FinalOutputMap[Index] = FMath::Clamp(InputMap[Index] + SourceVertexWeights[Index], 0.f, 1.f); break; default: unimplemented(); } } } if (InputMap.GetData() != FinalOutputMap.GetData()) { // Fill in remaining values with InputMap for (int32 Index = EndWeightIndex; Index < FinalOutputMap.Num(); ++Index) { FinalOutputMap[Index] = FMath::Clamp(InputMap[Index], 0.f, 1.f); } } } } FChaosClothAssetAddWeightMapNode::FChaosClothAssetAddWeightMapNode(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 FChaosClothAssetAddWeightMapNode::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 { FChaosClothAssetAddWeightMapNode* const MutableThis = static_cast(BaseNode.Get()); check(MutableThis == this); // Make the name a valid attribute name, and replace the value in the UI FWeightMapTools::MakeWeightMapName(MutableThis->Name); // Transfer weight map 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.IsValid()) // 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); const FName InInputName = GetInputName(Context); const uint32 NameTypeHash = HashCombineFast(GetTypeHash(InInputName), (uint32)TransferType); const uint32 InTransferCollectionHash = (TransferClothFacade.HasValidSimulationData() && InInputName != NAME_None) ? HashCombineFast(TransferClothFacade.CalculateWeightMapTypeHash(), NameTypeHash) : 0; // TODO: Remove after adding the function (currently shelved!) if (TransferCollectionHash != InTransferCollectionHash) { MutableThis->TransferCollectionHash = InTransferCollectionHash; if (TransferCollectionHash) { if (TransferClothFacade.HasWeightMap(InInputName)) { // Remap the weights TArray RemappedWeights; RemappedWeights.SetNumZeroed(ClothFacade.GetNumSimVertices3D()); switch (TransferType) { case EChaosClothAssetWeightMapTransferType::Use2DSimMesh: Private::TransferWeightMap( TransferClothFacade.GetSimPosition2D(), TransferClothFacade.GetSimIndices2D(), TransferClothFacade.GetSimVertex3DLookup(), TransferClothFacade.GetWeightMap(InInputName), ClothFacade.GetSimPosition2D(), ClothFacade.GetSimIndices2D(), ClothFacade.GetSimVertex3DLookup(), RemappedWeights); break; case EChaosClothAssetWeightMapTransferType::Use3DSimMesh: FClothGeometryTools::TransferWeightMap( TransferClothFacade.GetSimPosition3D(), TransferClothFacade.GetSimIndices3D(), TransferClothFacade.GetWeightMap(InInputName), ClothFacade.GetSimPosition3D(), ClothFacade.GetSimNormal(), ClothFacade.GetSimIndices3D(), TArrayView(RemappedWeights)); break; default: unimplemented(); } MutableThis->SetVertexWeights(ClothFacade.GetWeightMap(InInputName), RemappedWeights); } } } } } } } } void FChaosClothAssetAddWeightMapNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace UE::Chaos::ClothAsset; auto CheckSourceVertexWeights = [this](TArrayView& ClothWeights, const TArray& SourceVertexWeights, bool bIsSim) { if (SourceVertexWeights.Num() > 0 && SourceVertexWeights.Num() != ClothWeights.Num()) { FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("VertexCountMismatchHeadline", "Vertex count mismatch."), FText::Format(LOCTEXT("VertexCountMismatchDetails", "{0} vertex weights in the node: {1}\n{0} vertices in the cloth: {2}"), bIsSim ? FText::FromString("Sim") : FText::FromString("Render"), SourceVertexWeights.Num(), ClothWeights.Num())); } }; if (Out->IsA(&Collection)) { // Evaluate InputName const FName InInputName = GetInputName(Context); // Evaluate in collection FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); FCollectionClothFacade ClothFacade(ClothCollection); if (ClothFacade.IsValid()) // Can only act on the collection if it is a valid cloth collection { const FName InName(Name.IsEmpty() ? InInputName : FName(Name)); // Copy simulation weights into cloth collection if (MeshTarget == EChaosClothAssetWeightMapMeshType::Simulation || MeshTarget == EChaosClothAssetWeightMapMeshType::Both) { ClothFacade.AddWeightMap(InName); // Does nothing if weight map already exists TArrayView ClothSimWeights = ClothFacade.GetWeightMap(InName); if (ClothSimWeights.Num() != ClothFacade.GetNumSimVertices3D()) { check(ClothSimWeights.Num() == 0); FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("InvalidSimWeightMapNameHeadline", "Invalid weight map name."), FText::Format(LOCTEXT("InvalidSimWeightMapNameDetails", "Could not create a sim weight map with name \"{0}\" (reserved name? wrong type?)."), FText::FromName(InName))); } else { constexpr bool bIsSim = true; CheckSourceVertexWeights(ClothSimWeights, GetVertexWeights(), bIsSim); CalculateFinalVertexWeightValues(ClothFacade.GetWeightMap(InInputName), ClothSimWeights); } } // Copy render weights into cloth collection if (MeshTarget == EChaosClothAssetWeightMapMeshType::Render || MeshTarget == EChaosClothAssetWeightMapMeshType::Both) { ClothFacade.AddUserDefinedAttribute(InName, ClothCollectionGroup::RenderVertices); TArrayView ClothRenderWeights = ClothFacade.GetUserDefinedAttribute(InName, ClothCollectionGroup::RenderVertices); if (ClothRenderWeights.Num() != ClothFacade.GetNumRenderVertices()) { check(ClothRenderWeights.Num() == 0); FClothDataflowTools::LogAndToastWarning(*this, LOCTEXT("InvalidRenderWeightMapNameHeadline", "Invalid weight map name."), FText::Format(LOCTEXT("InvalidRenderWeightMapNameDetails", "Could not create a render weight map with name \"{0}\" (reserved name? wrong type?)."), FText::FromName(InName))); } else { constexpr bool bIsSim = false; CheckSourceVertexWeights(ClothRenderWeights, GetRenderVertexWeights(), bIsSim); CalculateFinalRenderVertexWeightValues(ClothFacade.GetUserDefinedAttribute(InInputName, ClothCollectionGroup::RenderVertices), ClothRenderWeights); } } } SetValue(Context, MoveTemp(*ClothCollection), &Collection); } else if (Out->IsA(&Name)) { FString InputNameString = GetValue(Context, &InputName.StringValue); UE::Chaos::ClothAsset::FWeightMapTools::MakeWeightMapName(InputNameString); SetValue(Context, Name.IsEmpty() ? InputNameString : Name, &Name); } } FName FChaosClothAssetAddWeightMapNode::GetInputName(UE::Dataflow::FContext& Context) const { FString InputNameString = GetValue(Context, &InputName.StringValue); UE::Chaos::ClothAsset::FWeightMapTools::MakeWeightMapName(InputNameString); const FName InInputName(*InputNameString); return InInputName != NAME_None ? InInputName : FName(Name); } void FChaosClothAssetAddWeightMapNode::SetVertexWeights(const TConstArrayView InputMap, const TArray& FinalValues) { UE::Chaos::ClothAsset::Private::SetVertexWeights(InputMap, FinalValues, MapOverrideType, GetVertexWeights()); } void FChaosClothAssetAddWeightMapNode::SetRenderVertexWeights(const TConstArrayView InputMap, const TArray& FinalValues) { UE::Chaos::ClothAsset::Private::SetVertexWeights(InputMap, FinalValues, MapOverrideType, GetRenderVertexWeights()); } void FChaosClothAssetAddWeightMapNode::CalculateFinalVertexWeightValues(const TConstArrayView InputMap, TArrayView FinalOutputMap) const { UE::Chaos::ClothAsset::Private::CalculateFinalVertexWeightValues(InputMap, FinalOutputMap, MapOverrideType, GetVertexWeights()); } void FChaosClothAssetAddWeightMapNode::CalculateFinalRenderVertexWeightValues(const TConstArrayView InputMap, TArrayView FinalOutputMap) const { UE::Chaos::ClothAsset::Private::CalculateFinalVertexWeightValues(InputMap, FinalOutputMap, MapOverrideType, GetRenderVertexWeights()); } // Object encapsulating a change to the AddWeightMap node's values. Used for Undo/Redo. class FChaosClothAssetAddWeightMapNode::FWeightMapNodeChange final : public FToolCommandChange { public: PRAGMA_DISABLE_DEPRECATION_WARNINGS FWeightMapNodeChange(const FChaosClothAssetAddWeightMapNode& Node) : PRAGMA_ENABLE_DEPRECATION_WARNINGS NodeGuid(Node.GetGuid()), SavedWeights(Node.GetVertexWeights()), SavedRenderWeights(Node.GetRenderVertexWeights()), SavedMapOverrideType(Node.MapOverrideType), SavedWeightMapName(Node.Name) {} private: FGuid NodeGuid; TArray SavedWeights; // Note we could store only one set of weights and use a bool to determine whether we are updating sim or render vertices, however in the future // we may enable writing both weight maps to the node at once. TArray SavedRenderWeights; EChaosClothAssetWeightMapOverrideType SavedMapOverrideType; FString SavedWeightMapName; virtual FString ToString() const final { return TEXT("ChaosClothAssetAddWeightMapNodeChange"); } 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)) { PRAGMA_DISABLE_DEPRECATION_WARNINGS if (FChaosClothAssetAddWeightMapNode* const Node = BaseNode->AsType()) PRAGMA_ENABLE_DEPRECATION_WARNINGS { Swap(Node->GetVertexWeights(), SavedWeights); Swap(Node->GetRenderVertexWeights(), SavedRenderWeights); Swap(Node->MapOverrideType, SavedMapOverrideType); Swap(Node->Name, SavedWeightMapName); Node->Invalidate(); } } } } }; PRAGMA_DISABLE_DEPRECATION_WARNINGS TUniquePtr FChaosClothAssetAddWeightMapNode::MakeWeightMapNodeChange(const FChaosClothAssetAddWeightMapNode& Node) { return MakeUnique(Node); } PRAGMA_ENABLE_DEPRECATION_WARNINGS #undef LOCTEXT_NAMESPACE