// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Chaos/ParticleHandleFwd.h" #include "CoreMinimal.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Containers/Queue.h" #include "Field/FieldSystemTypes.h" #include "Field/FieldArrayView.h" #include "Math/Vector.h" /** * FFieldContextIndex * The Context is passed into the field evaluation pipeline during evaluation. The Nodes * will have access to the samples and indices for evaluation. The MetaData is a optional * data package that the nodes will use during evaluation, the context does not assume * ownership of the metadata but assumes it will remain in scope during evaluation. */ struct FFieldContextIndex { FFieldContextIndex(int32 InSample = INDEX_NONE, int32 InResult = INDEX_NONE) : Sample(InSample) , Result(InResult) {} static void ContiguousIndices( TArray& Array, const int NumParticles, const bool bForce = true) { if(bForce) { Array.SetNum(NumParticles); for(int32 i = 0; i < Array.Num(); ++i) { Array[i].Result = i; Array[i].Sample = i; } } } int32 Sample; int32 Result; }; /** Enum to specify on which array the final field output will be stored for future use in rban/cloth*/ enum class EFieldCommandOutputType : uint8 { LinearForce = 0, LinearVelocity = 1, AngularTorque = 2, AngularVelocity = 3, NumOutputs = 4 }; /** Enum to specify on which array the intermediate fields results are going to be stored */ enum class EFieldCommandResultType : uint8 { FinalResult = 0, TransientResult = 1, NumResults = 2 }; /** Enum to specify on whjich array the particle handles are going to be stored */ enum class EFieldCommandHandlesType : uint8 { FilteredHandles = 0, InsideHandles = 1, NumHandles = 2 }; /** List of datas that will be stored during field evaluation to avoid reallocation */ struct FFieldExecutionDatas { /** Sample positions to be used to build the context */ TArray SamplePositions; /** Sample indices to be used to build the context */ TArray SampleIndices; /** List of particles handles used during evaluation */ TArray ParticleHandles[(uint8)EFieldCommandHandlesType::NumHandles]; /** Results for the target results */ TArray FieldOutputs[(uint8)EFieldCommandOutputType::NumOutputs]; /** Field vector targets results */ TArray VectorResults[(uint8)EFieldCommandResultType::NumResults]; /** Field scalar targets results */ TArray ScalarResults[(uint8)EFieldCommandResultType::NumResults]; /** Field integer targets results */ TArray IntegerResults[(uint8)EFieldCommandResultType::NumResults]; /** Field index results */ TArray IndexResults[(uint8)EFieldCommandResultType::NumResults]; }; /** Reset the results array with a given size and a default value */ template FORCEINLINE void ResetResultsArray(const int32 FieldSize, TArray& FieldArray, const FieldType DefaultValue) { FieldArray.SetNum(FieldSize, EAllowShrinking::No); for (int32 i = 0; i < FieldSize; ++i) { FieldArray[i] = DefaultValue; } } /** Reset the results arrays given a list of targets */ template FORCEINLINE void ResetResultsArrays(const int32 FieldSize, const TArray& FieldTargets, TArray FieldArray[(uint8)EFieldCommandOutputType::NumOutputs], const FieldType DefaultValue) { for (const EFieldCommandOutputType& FieldTarget : FieldTargets) { if (FieldTarget < EFieldCommandOutputType::NumOutputs) { ResetResultsArray(FieldSize, FieldArray[FieldTarget], DefaultValue); } } } /** Empty the results array without deallocating when shrinking*/ template FORCEINLINE void EmptyResultsArray(TArray& FieldArray) { FieldArray.SetNum(0, EAllowShrinking::No); } /** Empty the results arrays given a list of targets */ template FORCEINLINE void EmptyResultsArrays(const TArray& FieldTargets, TArray FieldArray[(uint8)EFieldCommandOutputType::NumOutputs]) { for (const EFieldCommandOutputType& FieldTarget : FieldTargets) { if (FieldTarget < EFieldCommandOutputType::NumOutputs) { EmptyResultsArray(FieldArray[(uint8)FieldTarget]); } } } /** * MetaData * * Metadata is used to attach state based information to the field evaluation * pipeline. Contexts and Commands can store metadata that can be used by * the Evaluate() of the field node, or during the processing of the command. */ class FFieldSystemMetaData { public: enum EMetaType { ECommandData_None = 0, ECommandData_ProcessingResolution, ECommandData_Results, ECommandData_Iteration, ECommandData_Culling, ECommandData_Filter }; virtual ~FFieldSystemMetaData() {}; virtual EMetaType Type() const = 0; virtual FFieldSystemMetaData* NewCopy() const = 0; }; class FFieldSystemMetaDataProcessingResolution : public FFieldSystemMetaData { public: FFieldSystemMetaDataProcessingResolution(EFieldResolutionType ProcessingResolutionIn) : ProcessingResolution(ProcessingResolutionIn) {}; virtual ~FFieldSystemMetaDataProcessingResolution() {}; virtual EMetaType Type() const { return EMetaType::ECommandData_ProcessingResolution; } virtual FFieldSystemMetaData* NewCopy() const { return new FFieldSystemMetaDataProcessingResolution(ProcessingResolution); } EFieldResolutionType ProcessingResolution; }; class FFieldSystemMetaDataFilter : public FFieldSystemMetaData { public: FFieldSystemMetaDataFilter(EFieldFilterType FilterTypeIn, EFieldObjectType ObjectTypeIn, EFieldPositionType PositionTypeIn) : FilterType(FilterTypeIn), ObjectType(ObjectTypeIn), PositionType(PositionTypeIn) {}; virtual ~FFieldSystemMetaDataFilter() {}; virtual EMetaType Type() const { return EMetaType::ECommandData_Filter; } virtual FFieldSystemMetaData* NewCopy() const { return new FFieldSystemMetaDataFilter(FilterType, ObjectType, PositionType); } EFieldFilterType FilterType; EFieldObjectType ObjectType; EFieldPositionType PositionType; }; template class FFieldSystemMetaDataResults : public FFieldSystemMetaData { public: FFieldSystemMetaDataResults(const TFieldArrayView& ResultsIn) : Results(ResultsIn) {}; virtual ~FFieldSystemMetaDataResults() {}; virtual EMetaType Type() const { return EMetaType::ECommandData_Results; } virtual FFieldSystemMetaData* NewCopy() const { return new FFieldSystemMetaDataResults(Results); } const TFieldArrayView& Results; }; class FFieldSystemMetaDataIteration : public FFieldSystemMetaData { public: FFieldSystemMetaDataIteration(int32 IterationsIn) : Iterations(IterationsIn) {}; virtual ~FFieldSystemMetaDataIteration() {}; virtual EMetaType Type() const { return EMetaType::ECommandData_Iteration; } virtual FFieldSystemMetaData* NewCopy() const { return new FFieldSystemMetaDataIteration(Iterations); } int32 Iterations; }; class FFieldSystemMetaDataCulling : public FFieldSystemMetaData { public: explicit FFieldSystemMetaDataCulling(TArray& CullingIndicesIn) : bCullingActive(false) , CullingIndices(CullingIndicesIn) {}; virtual ~FFieldSystemMetaDataCulling() = default; virtual EMetaType Type() const { return EMetaType::ECommandData_Culling; } virtual FFieldSystemMetaData* NewCopy() const { return new FFieldSystemMetaDataCulling(CullingIndices); } bool bCullingActive; TArray& CullingIndices; }; struct FFieldContext { typedef TMap > UniquePointerMap; typedef TMap PointerMap; FFieldContext() = delete; FFieldContext(const FFieldContext&) = delete; FFieldContext(FFieldContext&&) = delete; FFieldContext& operator =(const FFieldContext&) = delete; FFieldContext & operator =(FFieldContext&&) = delete; FFieldContext(const TFieldArrayView< FFieldContextIndex >& SampleIndicesIn, const TFieldArrayView& SamplePositionsIn, const UniquePointerMap & MetaDataIn, const Chaos::FReal TimeSecondsIn, TArray& VectorResultsIn, TArray& ScalarResultsIn, TArray& IntegerResultsIn, TArray& IndexResultsIn, TArray& CullingResultsIn) : SampleIndices(SampleIndicesIn) , SamplePositions(SamplePositionsIn) , TimeSeconds(TimeSecondsIn) , VectorResults(VectorResultsIn) , ScalarResults(ScalarResultsIn) , IntegerResults(IntegerResultsIn) , IndexResults(IndexResultsIn) { for (const TPair>& Meta : MetaDataIn) { MetaData.Add(Meta.Key) = Meta.Value.Get(); } CullingData = MakeUnique(CullingResultsIn); MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_Culling, CullingData.Get()); } FFieldContext(const TFieldArrayView< FFieldContextIndex >& SampleIndicesIn, const TFieldArrayView& SamplePositionsIn, const PointerMap & MetaDataIn, const Chaos::FReal TimeSecondsIn, TArray& VectorResultsIn, TArray& ScalarResultsIn, TArray& IntegerResultsIn, TArray& IndexResultsIn, TArray& CullingResultsIn) : SampleIndices(SampleIndicesIn) , SamplePositions(SamplePositionsIn) , MetaData(MetaDataIn) , TimeSeconds(TimeSecondsIn) , VectorResults(VectorResultsIn) , ScalarResults(ScalarResultsIn) , IntegerResults(IntegerResultsIn) , IndexResults(IndexResultsIn) { CullingData = MakeUnique(CullingResultsIn); MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_Culling, CullingData.Get()); } FFieldContext(FFieldExecutionDatas& ExecutionDatas, const UniquePointerMap& MetaDataIn, const Chaos::FReal TimeSecondsIn) : SampleIndices(ExecutionDatas.SampleIndices, 0, ExecutionDatas.SampleIndices.Num()) , SamplePositions(ExecutionDatas.SamplePositions, 0, ExecutionDatas.SamplePositions.Num()) , TimeSeconds(TimeSecondsIn) , VectorResults(ExecutionDatas.VectorResults[(uint8)EFieldCommandResultType::TransientResult]) , ScalarResults(ExecutionDatas.ScalarResults[(uint8)EFieldCommandResultType::TransientResult]) , IntegerResults(ExecutionDatas.IntegerResults[(uint8)EFieldCommandResultType::TransientResult]) , IndexResults(ExecutionDatas.IndexResults[(uint8)EFieldCommandResultType::TransientResult]) { for (const TPair>& Meta : MetaDataIn) { MetaData.Add(Meta.Key) = Meta.Value.Get(); } CullingData = MakeUnique(ExecutionDatas.IndexResults[(uint8)EFieldCommandResultType::FinalResult]); MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_Culling, CullingData.Get()); } TFieldArrayView GetEvaluatedSamples() { if(!CullingData->bCullingActive) { // No culling took place return SampleIndices; } // Culling fields created an evaluation set return TFieldArrayView(CullingData->CullingIndices, 0, CullingData->CullingIndices.Num()); } // // Ryan - TODO: This concept of having discreet sample data needs to change. // I think we'd be better off supplying lambda accessors which can be specialized // for each respective use case. That means the method by which this data is // traversed also needs to change; possibly to some load balanced threaded iterator // or task based paradigm. TFieldArrayView SampleIndices; TFieldArrayView SamplePositions; PointerMap MetaData; TUniquePtr CullingData; Chaos::FReal TimeSeconds; TArray& VectorResults; TArray& ScalarResults; TArray& IntegerResults; TArray& IndexResults; }; /** Get the vector execution array given a result type */ template FORCEINLINE TArray& GetResultArray(FFieldContext& FieldContext); /** Get the vector execution array given a result type */ template<> FORCEINLINE TArray& GetResultArray(FFieldContext& FieldContext) { return FieldContext.VectorResults; } /** Get the scalar execution array given a result type */ template<> FORCEINLINE TArray& GetResultArray(FFieldContext& FieldContext) { return FieldContext.ScalarResults; } /** Get the integer execution array given a result type */ template<> FORCEINLINE TArray& GetResultArray(FFieldContext& FieldContext) { return FieldContext.IntegerResults; } /** * Limits the application of a meta data object to a single scope. * This has the effect of exposing metadata to downstream nodes but making sure * upstream nodes cannot see it. */ class FScopedFieldContextMetaData { FScopedFieldContextMetaData() = delete; FScopedFieldContextMetaData(const FScopedFieldContextMetaData&) = delete; FScopedFieldContextMetaData(FScopedFieldContextMetaData&&) = delete; FScopedFieldContextMetaData& operator=(const FScopedFieldContextMetaData&) = delete; FScopedFieldContextMetaData& operator=(FScopedFieldContextMetaData&&) = delete; public: explicit FScopedFieldContextMetaData(FFieldContext& InContext, FFieldSystemMetaData* InMetaData) : TargetContext(InContext) { check(InMetaData); MetaType = InMetaData->Type(); TargetContext.MetaData.Add(MetaType, InMetaData); } ~FScopedFieldContextMetaData() { TargetContext.MetaData.Remove(MetaType); } private: FFieldSystemMetaData::EMetaType MetaType; FFieldContext& TargetContext; }; /** * FFieldNodeBase * * Abstract base class for the field node evaluation. * */ class FFieldNodeBase { public: enum EFieldType { EField_None = 0, EField_Results, EField_Int32, EField_Float, EField_FVector, }; enum ESerializationType { FieldNode_Null = 0, FieldNode_FUniformInteger, FieldNode_FRadialIntMask, FieldNode_FUniformScalar, FieldNode_FRadialFalloff, FieldNode_FPlaneFalloff, FieldNode_FBoxFalloff, FieldNode_FNoiseField, FieldNode_FUniformVector, FieldNode_FRadialVector, FieldNode_FRandomVector, FieldNode_FSumScalar, FieldNode_FSumVector, FieldNode_FConversionField, FieldNode_FCullingField, FieldNode_FWaveScalar, FieldNode_FReturnResultsTerminal }; FFieldNodeBase() : Name("") {} virtual ~FFieldNodeBase() {} virtual EFieldType Type() const { check(false); return EFieldType::EField_None; } virtual ESerializationType SerializationType() const { check(false); return ESerializationType::FieldNode_Null; } virtual FFieldNodeBase * NewCopy() const = 0; virtual void Serialize(FArchive& Ar) { Ar << Name; } virtual bool operator==(const FFieldNodeBase& Node) { return Name.IsEqual(Node.GetName()); } /** Count the number of offsets/params that will be used by the world physics field */ virtual void FillSetupCount(int32& NumOffsets, int32& NumParams) const {} /** Fill the offsets/params arrays that will be used by the world physics field */ virtual void FillSetupDatas(TArray& NodesOffsets, TArray& NodesParams, const float CommandTime) const {} /** Evaluate the maximum magnitude of the field graph */ virtual float EvalMaxMagnitude() const { return 1.0; } /** Compute the min/max spatial bounds of the field */ virtual void ComputeFieldBounds(FVector& MinBounds, FVector& MaxBounds, FVector& CenterPosition) const { MinBounds = FVector(-FLT_MAX); MaxBounds = FVector(FLT_MAX); CenterPosition = FVector::Zero(); } FName GetName() const { return Name; } void SetName(const FName & NameIn) { Name = NameIn; } private: FName Name; }; /** * FieldNode * * Typed field nodes are used for the evaluation of specific types of data arrays. * For exampe, The FFieldNode::Evaluate(...) will expect resutls * of type TFieldArrayView, and an example implementation is the UniformVectorField. * */ template class FFieldNode : public FFieldNodeBase { public: virtual ~FFieldNode() {} virtual void Evaluate(FFieldContext&, TFieldArrayView& Results) const = 0; static EFieldType StaticType(); virtual EFieldType Type() const { return StaticType(); } /** Count the number of offsets/params that will be used by the world physics field */ virtual void FillSetupCount(int32& NumOffsets, int32& NumParams) const override { ++NumOffsets; NumParams += 2; } /** Fill the offsets/params arrays that will be used by the world physics field */ virtual void FillSetupDatas(TArray& NodesOffsets, TArray& NodesParams, const float CommandTime) const override { NodesOffsets.Add(NodesParams.Num()); NodesParams.Add(static_cast(Type())); NodesParams.Add(static_cast(SerializationType())); } }; template<> inline FFieldNodeBase::EFieldType FFieldNode::StaticType() { return EFieldType::EField_Int32; } template<> inline FFieldNodeBase::EFieldType FFieldNode::StaticType() { return EFieldType::EField_Float; } template<> inline FFieldNodeBase::EFieldType FFieldNode::StaticType() { return EFieldType::EField_FVector; } /** * FieldCommand * * Field commands are issued on the game thread and trigger field * evaluation during game play. The Commands will store the root * node in the evaluation graph, and will trigger a full evaluation * of all the nodes in the graph. The MetaData within the command * will be passed to the evaluation of the field. * */ class FFieldSystemCommand { public: FFieldSystemCommand() : TargetAttribute("") , RootNode(nullptr) , CommandName("") , TimeCreation(0.0) , BoundingBox(FVector(-FLT_MAX), FVector(FLT_MAX)) , PhysicsType(EFieldPhysicsType::Field_None) , MaxMagnitude(1.0) , CenterPosition(FVector::Zero()) {} FFieldSystemCommand(const FName& TargetAttributeIn, FFieldNodeBase * RootNodeIn) : TargetAttribute(TargetAttributeIn) , RootNode(RootNodeIn) , CommandName("") , TimeCreation(0.0) , BoundingBox(FVector(-FLT_MAX), FVector(FLT_MAX)) , PhysicsType(GetFieldPhysicsType(TargetAttributeIn)) , MaxMagnitude(1.0) , CenterPosition(FVector::Zero()) {} FFieldSystemCommand(const EFieldPhysicsType PhsyicsTypeIn, FFieldNodeBase* RootNodeIn) : TargetAttribute(GetFieldPhysicsName(PhsyicsTypeIn)) , RootNode(RootNodeIn) , CommandName("") , TimeCreation(0.0) , BoundingBox(FVector(-FLT_MAX), FVector(FLT_MAX)) , PhysicsType(PhsyicsTypeIn) , MaxMagnitude(1.0) , CenterPosition(FVector::Zero()) {} // Commands are copied when moved from the one thread to // another. This requires a full copy of all associated data. FFieldSystemCommand(const FFieldSystemCommand& Other) : TargetAttribute(Other.RootNode ? Other.TargetAttribute:"") , RootNode(Other.RootNode?Other.RootNode->NewCopy():nullptr) , CommandName(Other.CommandName) , TimeCreation(Other.TimeCreation) , BoundingBox(Other.BoundingBox) , PhysicsType(Other.RootNode ? Other.PhysicsType : EFieldPhysicsType::Field_None) , MaxMagnitude(Other.MaxMagnitude) , CenterPosition(Other.CenterPosition) { for (const TPair>& Meta : Other.MetaData) { MetaData.Add(Meta.Key).Reset(Meta.Value->NewCopy()); } } bool HasMetaData(const FFieldSystemMetaData::EMetaType Key) const { return MetaData.Contains(Key) && GetMetaData(Key) != nullptr; } const TUniquePtr& GetMetaData( const FFieldSystemMetaData::EMetaType Key) const { return MetaData[Key]; } template const TMetaData* GetMetaDataAs(const FFieldSystemMetaData::EMetaType Key) const { return static_cast(GetMetaData(Key).Get()); } void SetMetaData(const FFieldSystemMetaData::EMetaType Key, TUniquePtr&& Value) { MetaData.Add(Key, MoveTemp(Value)); } void SetMetaData(const FFieldSystemMetaData::EMetaType Key, FFieldSystemMetaData* Value) { MetaData[Key].Reset(Value); } void InitFieldNodes(const double TimeSeconds, const FName& Name) { CommandName = Name; TimeCreation = (float)TimeSeconds; } CHAOS_API void Serialize(FArchive& Ar); CHAOS_API bool operator==(const FFieldSystemCommand&) const; bool operator!=(const FFieldSystemCommand& Other) const { return !this->operator==(Other); } FName TargetAttribute; TUniquePtr RootNode; FName CommandName; float TimeCreation; FBox BoundingBox; EFieldPhysicsType PhysicsType; float MaxMagnitude; FVector CenterPosition; TMap > MetaData; }; /* * Equality testing for pointer wrapped FieldNodes */ template bool FieldsEqual(const TUniquePtr& NodeA, const TUniquePtr& NodeB) { if (NodeA.IsValid() == NodeB.IsValid()) { if (NodeA.IsValid()) { if (NodeA->SerializationType() == NodeB->SerializationType()) { return NodeA->operator==(*NodeB); } } else { return true; } } return false; }