// Copyright Epic Games, Inc. All Rights Reserved. #include "ChaosClothAsset/SimulationBaseConfigNode.h" #include "ChaosClothAsset/CollectionClothFacade.h" #include "ChaosClothAsset/ClothDataflowTools.h" #include "Chaos/CollectionPropertyFacade.h" #include "ChaosClothAsset/ClothCollectionGroup.h" #include "Dataflow/DataflowInputOutput.h" #define LOCTEXT_NAMESPACE "ChaosClothAssetSimulationBaseConfigNode" namespace UE::Chaos::ClothAsset::Private { static void LogAndToastDuplicateProperty(const FDataflowNode& DataflowNode, const FName& PropertyName) { using namespace UE::Chaos::ClothAsset; static const FText Headline = LOCTEXT("DuplicatePropertyHeadline", "Duplicate property."); const FText Details = FText::Format( LOCTEXT( "DuplicatePropertyDetails", "Cloth collection property '{0}' was already set in an upstream node.\n" "Its values have now been overridden."), FText::FromName(PropertyName)); FClothDataflowTools::LogAndToastWarning(DataflowNode, Headline, Details); } static void LogAndToastSimilarProperty(const FDataflowNode& DataflowNode, const FName& PropertyName, const FName& SimilarPropertyName) { using namespace UE::Chaos::ClothAsset; static const FText Headline = LOCTEXT("SimilarPropertyHeadline", "Similar property."); const FText Details = FText::Format( LOCTEXT( "SimilarPropertyDetails", "Cloth collection property '{0}' is similar to the property '{1}' already set in an upstream node.\n" "This might result in an undefined simulation behavior."), FText::FromName(PropertyName), FText::FromName(SimilarPropertyName)); FClothDataflowTools::LogAndToastWarning(DataflowNode, Headline, Details); } static FWeightedValueBounds ComputeFabricWeightedValueBounds(FCollectionClothFacade& ClothFacade, TArray& PatternValues, const TFunction& FabricValueFunction) { const int32 NumPatterns = ClothFacade.GetNumSimPatterns(); PatternValues.Init(0.0f, NumPatterns); float MinValue = FLT_MAX, MaxValue = 0.0f; for(int32 PatternIndex = 0; PatternIndex < NumPatterns; ++PatternIndex) { FCollectionClothSimPatternFacade PatternFacade = ClothFacade.GetSimPattern(PatternIndex); const int32 FabricIndex = PatternFacade.GetFabricIndex(); if(FabricIndex >= 0 && FabricIndex < ClothFacade.GetNumFabrics()) { FCollectionClothFabricFacade FabricFacade = ClothFacade.GetFabric(FabricIndex); const float FabricValue = FabricValueFunction(FabricFacade); MinValue = FMath::Min(MinValue, FabricValue); MaxValue = FMath::Max(MaxValue, FabricValue); PatternValues[PatternIndex] = FabricValue; } } return {MinValue, MaxValue}; } static FWeightedValueBounds ComputePatternWeightedValueBounds(FCollectionClothFacade& ClothFacade, TArray& PatternValues, const TFunction& PatternValueFunction) { const int32 NumPatterns = ClothFacade.GetNumSimPatterns(); PatternValues.Init(0.0f, NumPatterns); float MinValue = FLT_MAX, MaxValue = 0.0f; for(int32 PatternIndex = 0; PatternIndex < NumPatterns; ++PatternIndex) { FCollectionClothSimPatternFacade PatternFacade = ClothFacade.GetSimPattern(PatternIndex); const float PatternValue = PatternValueFunction(PatternFacade); MinValue = FMath::Min(MinValue, PatternValue); MaxValue = FMath::Max(MaxValue, PatternValue); PatternValues[PatternIndex] = PatternValue; } return {MinValue, MaxValue}; } static FWeightedValueBounds ComputeWeightedValueMap(FCollectionClothFacade& ClothFacade, const FString& WeightMapName, const FWeightedValueBounds& WeightValueBounds, const TArray& PatternValues) { const int32 NumPatterns = ClothFacade.GetNumSimPatterns(); if(WeightValueBounds.Low != WeightValueBounds.High) { const int32 NumVertices = ClothFacade.GetNumSimVertices3D(); TArray NumValues; NumValues.Init(0, NumVertices); TArray VertexValues; VertexValues.Init(0.0f, NumVertices); ClothFacade.AddWeightMap(*WeightMapName); const TArrayView ValueWeightMap = ClothFacade.GetWeightMap(*WeightMapName); for(int32 PatternIndex = 0; PatternIndex < NumPatterns; ++PatternIndex) { FCollectionClothSimPatternFacade PatternFacade = ClothFacade.GetSimPattern(PatternIndex); const TConstArrayView SimVertex3DLookup = static_cast(PatternFacade).GetSimVertex3DLookup(); // Average of the pattern values at the seam for(const int32& SimVertex3DIndex : SimVertex3DLookup) { VertexValues[SimVertex3DIndex] += PatternValues[PatternIndex]; NumValues[SimVertex3DIndex]++; } } for(int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { if(NumValues[VertexIndex] > 0) { ValueWeightMap[VertexIndex] = (VertexValues[VertexIndex] / NumValues[VertexIndex] - WeightValueBounds.Low) / (WeightValueBounds.High - WeightValueBounds.Low); } } } return WeightValueBounds; } static FWeightedValueBounds BuildFabricWeightedValue(FCollectionClothFacade& ClothFacade, const FString& WeightMapName, const TFunction& FabricValueFunction) { TArray PatternValues; const FWeightedValueBounds WeightValueBounds = ComputeFabricWeightedValueBounds(ClothFacade, PatternValues, FabricValueFunction); return ComputeWeightedValueMap(ClothFacade, WeightMapName, WeightValueBounds, PatternValues); } static FWeightedValueBounds BuildPatternWeightedValue(FCollectionClothFacade& ClothFacade, const FString& WeightMapName, const TFunction& PatternValueFunction) { TArray PatternValues; const FWeightedValueBounds WeightValueBounds = ComputePatternWeightedValueBounds(ClothFacade, PatternValues, PatternValueFunction); return ComputeWeightedValueMap(ClothFacade, WeightMapName, WeightValueBounds, PatternValues); } } FChaosClothAssetSimulationBaseConfigNode::FChaosClothAssetSimulationBaseConfigNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid) : FDataflowNode(InParam, InGuid) {} void FChaosClothAssetSimulationBaseConfigNode::RegisterCollectionConnections() { RegisterInputConnection(&Collection); RegisterOutputConnection(&Collection, &Collection); } void FChaosClothAssetSimulationBaseConfigNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { using namespace Chaos::Softs; using namespace UE::Chaos::ClothAsset; if (Out->IsA(&Collection)) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); const TSharedRef ClothCollection = MakeShared(MoveTemp(InCollection)); FCollectionPropertyMutableFacade Properties(ClothCollection); Properties.DefineSchema(); PRAGMA_DISABLE_DEPRECATION_WARNINGS AddProperties(Context, Properties); // Deprecated 5.4 PRAGMA_ENABLE_DEPRECATION_WARNINGS FPropertyHelper PropertyHelper(*this, Context, Properties, ClothCollection); AddProperties(PropertyHelper); if (FCollectionClothFacade(ClothCollection).IsValid()) // Can only act on the collection if it is a valid cloth collection { EvaluateClothCollection(Context, ClothCollection); } SetValue(Context, MoveTemp(*ClothCollection), &Collection); } } int32 FChaosClothAssetSimulationBaseConfigNode::AddPropertyHelper( ::Chaos::Softs::FCollectionPropertyMutableFacade& Properties, const FName& PropertyName, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) const { using namespace UE::Chaos::ClothAsset; int32 KeyIndex = Properties.GetKeyIndex(PropertyName.ToString()); if (KeyIndex == INDEX_NONE) { KeyIndex = Properties.AddProperty(PropertyName.ToString()); } else if (bWarnDuplicateProperty && !Properties.IsLegacy(KeyIndex)) // Only warns of duplicates when the property hasn't been set using a legacy Chaos config { Private::LogAndToastDuplicateProperty(*this, PropertyName); } // else don't warn and hope people know what they are doing // Set/clear flags Properties.SetFlags(KeyIndex, ECollectionPropertyFlags::Enabled | PropertyFlags); // Always enabled when then node is active, no longer a legacy property after being set by this node // Check for similar properties for (const FName& SimilarPropertyName : SimilarPropertyNames) { const int32 SimilarPropertyKeyIndex = Properties.GetKeyIndex(SimilarPropertyName.ToString()); if (SimilarPropertyKeyIndex != INDEX_NONE && !Properties.IsLegacy(SimilarPropertyKeyIndex)) // Only warns of overrides when the property hasn't been set using a legacy Chaos config { Private::LogAndToastSimilarProperty(*this, PropertyName, SimilarPropertyName); } } return KeyIndex; } FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::FPropertyHelper(const FChaosClothAssetSimulationBaseConfigNode& InConfigNode, UE::Dataflow::FContext& InContext, ::Chaos::Softs::FCollectionPropertyMutableFacade& InProperties, const TSharedRef& InClothCollection) : ConfigNode(InConfigNode) , Context(InContext) , Properties(InProperties) , ClothCollection(InClothCollection) {} int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyBool( const FName& PropertyName, bool PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { checkf(!PropertyName.ToString().StartsWith(TEXT("b")), TEXT("Unlike its C++ counterpart the Boolean property name should not start with a 'b'.")); const int32 PropertyKeyIndex = ConfigNode.AddPropertyHelper(Properties, PropertyName, SimilarPropertyNames, PropertyFlags); Properties.SetValue(PropertyKeyIndex, PropertyValue); return PropertyKeyIndex; } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyString( const FName& PropertyName, const FString& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { const int32 PropertyKeyIndex = ConfigNode.AddPropertyHelper(Properties, PropertyName, SimilarPropertyNames, PropertyFlags); Properties.SetStringValue(PropertyKeyIndex, PropertyValue); return PropertyKeyIndex; } FString FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::GetPropertyString(const FString* PropertyReference) const { return ConfigNode.GetValue(Context, PropertyReference); } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyWeighted( const FName& PropertyName, const bool bIsAnimatable, const float& PropertyLow, const float& PropertyHigh, const FString& WeightMap, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) const { ensureMsgf(!EnumHasAnyFlags(PropertyFlags, ECollectionPropertyFlags::Animatable), TEXT("Animatable flag ignored. Weighted properties are set animatable through FChaosClothAssetWeightedValue::bIsAnimatable.")); if (bIsAnimatable) { EnumAddFlags(PropertyFlags, ECollectionPropertyFlags::Animatable); // Animatable } else { EnumRemoveFlags(PropertyFlags, ECollectionPropertyFlags::Animatable); // Non-animatable } EnumAddFlags(PropertyFlags, ECollectionPropertyFlags::Interpolable); // Interpolable const int32 PropertyKeyIndex = ConfigNode.AddPropertyHelper(Properties, PropertyName, SimilarPropertyNames, PropertyFlags); Properties.SetWeightedValue(PropertyKeyIndex, PropertyLow, PropertyHigh); Properties.SetStringValue(PropertyKeyIndex, ConfigNode.GetValue(Context, &WeightMap)); return PropertyKeyIndex; } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyWeighted( const FName& PropertyName, const FVector2f& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { EnumAddFlags(PropertyFlags, ECollectionPropertyFlags::Interpolable); // Interpolable const int32 PropertyKeyIndex = ConfigNode.AddPropertyHelper(Properties, PropertyName, SimilarPropertyNames, PropertyFlags); Properties.SetWeightedValue(PropertyKeyIndex, PropertyValue[0], PropertyValue[1]); return PropertyKeyIndex; } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyWeighted( const FName& PropertyName, const FChaosClothAssetWeightedValue& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { return SetPropertyWeighted(PropertyName, PropertyValue.bIsAnimatable, PropertyValue.Low, PropertyValue.High, PropertyValue.WeightMap, SimilarPropertyNames, PropertyFlags); } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyWeighted( const FName& PropertyName, const FChaosClothAssetWeightedValueNonAnimatable& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { return SetPropertyWeighted(PropertyName, false, PropertyValue.Low, PropertyValue.High, PropertyValue.WeightMap, SimilarPropertyNames, PropertyFlags); } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyWeighted( const FName& PropertyName, const FChaosClothAssetWeightedValueNonAnimatableNoLowHighRange& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { return SetPropertyWeighted(PropertyName, false, 0.0f, 1.0f, PropertyValue.WeightMap, SimilarPropertyNames, PropertyFlags); } void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::OverridePropertiesBool(const TArray& PropertyNames, bool bPropertyValue) { for (const FName& PropertyName : PropertyNames) { const int32 PropertyKeyIndex = Properties.GetKeyIndex(PropertyName.ToString()); if (PropertyKeyIndex != INDEX_NONE) { Properties.SetValue(PropertyKeyIndex, bPropertyValue); } } } void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::OverridePropertiesFloat(const TArray& PropertyNames, const EChaosClothAssetConstraintOverrideType OverrideType, const float OverrideValue) { if (OverrideType == EChaosClothAssetConstraintOverrideType::None) { return; } for (const FName& PropertyName : PropertyNames) { const int32 PropertyKeyIndex = Properties.GetKeyIndex(PropertyName.ToString()); if (PropertyKeyIndex != INDEX_NONE) { if (OverrideType == EChaosClothAssetConstraintOverrideType::Override) { Properties.SetValue(PropertyKeyIndex, OverrideValue); } else if (OverrideType == EChaosClothAssetConstraintOverrideType::Multiply) { Properties.SetValue(PropertyKeyIndex, Properties.GetValue(PropertyKeyIndex) * OverrideValue); } } } } void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::OverridePropertiesWeighted(const TArray& PropertyNames, const EChaosClothAssetConstraintOverrideType OverrideType, const FChaosClothAssetWeightedValueOverride& OverrideValue) { if (OverrideType == EChaosClothAssetConstraintOverrideType::None) { return; } for (const FName& PropertyName : PropertyNames) { const int32 PropertyKeyIndex = Properties.GetKeyIndex(PropertyName.ToString()); if (PropertyKeyIndex != INDEX_NONE) { if (OverrideType == EChaosClothAssetConstraintOverrideType::Override) { Properties.SetWeightedValue(PropertyKeyIndex, OverrideValue.Low, OverrideValue.High); } else if (OverrideType == EChaosClothAssetConstraintOverrideType::Multiply) { Properties.SetWeightedValue(PropertyKeyIndex, Properties.GetLowValue(PropertyKeyIndex) * OverrideValue.Low, Properties.GetHighValue(PropertyKeyIndex) * OverrideValue.High); } } } } int32 FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetPropertyString( const FName& PropertyName, const FChaosClothAssetConnectableIStringValue& PropertyValue, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { const int32 PropertyKeyIndex = ConfigNode.AddPropertyHelper(Properties, PropertyName, SimilarPropertyNames, PropertyFlags); Properties.SetStringValue(PropertyKeyIndex, ConfigNode.GetValue(Context, &PropertyValue.StringValue)); return PropertyKeyIndex; } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverProperty(const FName& PropertyName, const PropertyType& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(GetClothCollection()); if(PropertyValue.bUseImportedValue && ClothFacade.IsValid(UE::Chaos::ClothAsset::EClothCollectionExtendedSchemas::Solvers) && ClothFacade.HasSolverElement()) { PropertyValue.ImportedValue = SolverValueFunction(ClothFacade); } SetProperty(PropertyName, PropertyValue.ImportedValue, SimilarPropertyNames, PropertyFlags); } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricProperty(const FName& PropertyName, const PropertyType& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(GetClothCollection()); if(PropertyValue.bUseImportedValue && ClothFacade.IsValid(UE::Chaos::ClothAsset::EClothCollectionExtendedSchemas::Fabrics) && ClothFacade.GetNumFabrics() > 0) { const int32 NumFabrics = ClothFacade.GetNumFabrics(); typename PropertyType::ImportedType AveragedFabricValue(0.0f); for(int32 FabricIndex = 0; FabricIndex < NumFabrics; ++FabricIndex) { UE::Chaos::ClothAsset::FCollectionClothFabricFacade FabricFacade = ClothFacade.GetFabric(FabricIndex); AveragedFabricValue += FabricValueFunction(FabricFacade); } AveragedFabricValue /= NumFabrics; PropertyValue.ImportedValue = AveragedFabricValue; } SetProperty(PropertyName, PropertyValue.ImportedValue, SimilarPropertyNames, PropertyFlags); } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricPropertyWeighted( const FName& PropertyName, const PropertyType& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(GetClothCollection()); if(PropertyValue.bCouldUseFabrics && ClothFacade.IsValid(UE::Chaos::ClothAsset::EClothCollectionExtendedSchemas::Fabrics) && (ClothFacade.GetNumFabrics() > 0) && (PropertyValue.bImportFabricBounds || PropertyValue.bBuildFabricMaps)) { UE::Chaos::ClothAsset::FWeightedValueBounds WeightedValueBounds; if(PropertyValue.bBuildFabricMaps) { WeightedValueBounds = UE::Chaos::ClothAsset::Private::BuildFabricWeightedValue(ClothFacade, GetPropertyString(&PropertyValue.WeightMap), FabricValueFunction); } else { TArray PatternValues; WeightedValueBounds = UE::Chaos::ClothAsset::Private::ComputeFabricWeightedValueBounds( ClothFacade, PatternValues, FabricValueFunction); } if(PropertyValue.bImportFabricBounds) { PropertyValue.Low = WeightedValueBounds.Low; PropertyValue.High = WeightedValueBounds.High; } } SetPropertyWeighted(PropertyName, PropertyValue, SimilarPropertyNames, PropertyFlags); } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricPropertyString( const FName& PropertyName, const PropertyType& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags, const FName& GroupName) { UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(GetClothCollection()); if(PropertyValue.bCouldUseFabrics && ClothFacade.IsValid(UE::Chaos::ClothAsset::EClothCollectionExtendedSchemas::Fabrics) && (ClothFacade.GetNumFabrics() > 0) && PropertyValue.bBuildFabricMaps) { const int32 NumPatterns = ClothFacade.GetNumSimPatterns(); const FName StringValue(*GetPropertyString(&PropertyValue.StringValue)); if (!ClothFacade.HasUserDefinedAttribute(StringValue, GroupName)) { ClothFacade.AddUserDefinedAttribute(StringValue, GroupName); } TArrayView UserMap = ClothFacade.GetUserDefinedAttribute(StringValue, GroupName); for(int32 PatternIndex = 0; PatternIndex < NumPatterns; ++PatternIndex) { UE::Chaos::ClothAsset::FCollectionClothSimPatternFacade PatternFacade = ClothFacade.GetSimPattern(PatternIndex); const int32 FabricIndex = PatternFacade.GetFabricIndex(); if(FabricIndex >= 0 && FabricIndex < ClothFacade.GetNumFabrics()) { UE::Chaos::ClothAsset::FCollectionClothFabricFacade FabricFacade = ClothFacade.GetFabric(FabricIndex); const float FabricValue = FabricValueFunction(FabricFacade); if(GroupName == UE::Chaos::ClothAsset::ClothCollectionGroup::SimFaces) { const int32 PatternFacesStart = PatternFacade.GetSimFacesOffset(); const int32 PatternFacesEnd = PatternFacade.GetNumSimFaces() + PatternFacesStart; for(int32 SimFaceIndex = PatternFacesStart; SimFaceIndex < PatternFacesEnd; ++SimFaceIndex) { UserMap[SimFaceIndex] = FabricValue; } } else if(GroupName == UE::Chaos::ClothAsset::ClothCollectionGroup::SimVertices3D) { const TConstArrayView SimVertex3DLookup = static_cast(PatternFacade).GetSimVertex3DLookup(); for(const int32& SimVertex3DIndex : SimVertex3DLookup) { UserMap[SimVertex3DIndex] = FabricValue; } } } } } SetPropertyString(PropertyName, PropertyValue, SimilarPropertyNames, PropertyFlags); } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverPropertyWeighted( const FName& PropertyName, const PropertyType& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags) { UE::Chaos::ClothAsset::FCollectionClothFacade ClothFacade(GetClothCollection()); if(PropertyValue.bCouldUseFabrics && (ClothFacade.IsValid(UE::Chaos::ClothAsset::EClothCollectionExtendedSchemas::Solvers) && ClothFacade.HasSolverElement()) && PropertyValue.bImportFabricBounds) { const float SolverValue = SolverValueFunction(ClothFacade); PropertyValue.Low = SolverValue; PropertyValue.High = SolverValue; } SetPropertyWeighted(PropertyName, PropertyValue, SimilarPropertyNames, PropertyFlags); } template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverProperty(const FName& PropertyName, const FChaosClothAssetImportedVectorValue& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverProperty(const FName& PropertyName, const FChaosClothAssetImportedFloatValue& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverProperty(const FName& PropertyName, const FChaosClothAssetImportedIntValue& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricProperty(const FName& PropertyName, const FChaosClothAssetImportedVectorValue& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricProperty(const FName& PropertyName, const FChaosClothAssetImportedFloatValue& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricProperty(const FName& PropertyName, const FChaosClothAssetImportedIntValue& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverPropertyWeighted( const FName& PropertyName, const FChaosClothAssetWeightedValueNonAnimatable& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetSolverPropertyWeighted( const FName& PropertyName, const FChaosClothAssetWeightedValue& PropertyValue, const TFunction& SolverValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricPropertyWeighted(const FName& PropertyName, const FChaosClothAssetWeightedValue& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricPropertyWeighted(const FName& PropertyName, const FChaosClothAssetWeightedValueNonAnimatable& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags); template void FChaosClothAssetSimulationBaseConfigNode::FPropertyHelper::SetFabricPropertyString( const FName& PropertyName, const FChaosClothAssetConnectableIStringValue& PropertyValue, const TFunction& FabricValueFunction, const TArray& SimilarPropertyNames, ECollectionPropertyFlags PropertyFlags, const FName& GroupName); #undef LOCTEXT_NAMESPACE