Files
UnrealEngine/Engine/Plugins/Experimental/Buoyancy/Source/Runtime/Public/BuoyancyParticleData.h
2025-05-18 13:04:45 +08:00

313 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
//#include "Containers/HashTable.h"
#include "CoreMinimal.h"
#include "Chaos/SimCallbackObject.h"
#include "Chaos/Framework/PhysicsProxyBase.h"
#include "Chaos/PBDRigidsEvolutionFwd.h"
#include "PBDRigidsSolver.h"
#include "Engine/EngineBaseTypes.h"
#include "PhysicsProxy/SingleParticlePhysicsProxyFwd.h"
#include "BuoyancyWaterSplineData.h"
#include "BuoyancyStats.h"
enum class EWaterSamplerType : uint8
{
ConstantSpline,
ShallowWater,
Invalid,
};
// abstract class to sample water for a given particle
class FBuoyancyWaterSampler
{
public:
FBuoyancyWaterSampler() : SamplerType(EWaterSamplerType::Invalid), bUsePerShapeWaterPlane(true),
ClosestWaterPointToParticle(0,0,0), AverageVelocity(0,0,0)
{
};
virtual ~FBuoyancyWaterSampler() {}
bool UsePerShapeWaterPlane() const { return bUsePerShapeWaterPlane; }
FVector GetClosestWaterPointToParticle() const { return ClosestWaterPointToParticle; }
// average fluid velocity for the particle
FVector GetAverageWaterVelocity() const { return AverageVelocity; }
virtual void InitializeForSubmersion() {}
virtual void SampleWaterAtPosition(const FVector& WorldPosition, FVector& OutVelocity, float& OutHeight, float& OutDepth) const = 0;
virtual FVector SampleVelocityAtPosition(const FVector& WorldPosition) const = 0;
virtual FVector SampleNormalAtPosition(const FVector& WorldPosition) const = 0;
EWaterSamplerType GetSamplerType() const { return SamplerType; }
protected:
EWaterSamplerType SamplerType;
bool bUsePerShapeWaterPlane;
FVector ClosestWaterPointToParticle;
FVector AverageVelocity;
};
// sample water from a baked shallow water simulation
class FBuoyancyShallowWaterSampler : public FBuoyancyWaterSampler
{
public:
FBuoyancyShallowWaterSampler(const FShallowWaterSimulationGrid& InWaterSampler, const FVector& InClosestWaterPointToParticle, const FVector &InAverageVelocity) :
WaterSampler(InWaterSampler)
{
SamplerType = EWaterSamplerType::ShallowWater;
bUsePerShapeWaterPlane = true;
ClosestWaterPointToParticle = InClosestWaterPointToParticle;
AverageVelocity = InAverageVelocity;
}
virtual void SampleWaterAtPosition(const FVector& WorldPosition, FVector& OutVelocity, float& OutHeight, float& OutDepth) const override
{
WaterSampler.SampleShallowWaterSimulationAtPosition(WorldPosition, OutVelocity, OutHeight, OutDepth);
}
virtual FVector SampleVelocityAtPosition(const FVector& WorldPosition) const override
{
FVector WaterVelocity;
float TmpWaterVal;
WaterSampler.SampleShallowWaterSimulationAtPosition(WorldPosition, WaterVelocity, TmpWaterVal, TmpWaterVal);
return WaterVelocity;
}
virtual FVector SampleNormalAtPosition(const FVector& WorldPosition) const override
{
return WaterSampler.ComputeShallowWaterSimulationNormalAtPosition(WorldPosition);
}
private:
const FShallowWaterSimulationGrid& WaterSampler;
};
// sampler for spline rivers, but we are only doing 1 spline sample per interaction to
// match the perf of the old system. Also, spline velocities don't vary much, so the net
// effect isn't too different
class FBuoyancyConstantSplineSampler : public FBuoyancyWaterSampler
{
public:
FBuoyancyConstantSplineSampler(const FBuoyancyWaterSplineData& InWaterSpline, const FVector& InClosestPoint,
const FVector& InClosestPointDerivative, const float InClosestSplineKey, const FVector& InWaterN)
: WaterSpline(InWaterSpline), ClosestPointDerivative(InClosestPointDerivative),
ClosestSplineKey(InClosestSplineKey), WaterN(InWaterN), WaterVel(FVector(0, 0, 0)), WaterVelSet(false)
{
SamplerType = EWaterSamplerType::ConstantSpline;
bUsePerShapeWaterPlane = false;
ClosestWaterPointToParticle = InClosestPoint;
AverageVelocity = WaterVel;
}
virtual void InitializeForSubmersion()
{
WaterVel = WaterSpline.Velocity.IsSet()
? WaterSpline.Velocity->Eval(ClosestSplineKey) * ClosestPointDerivative.GetSafeNormal()
: Chaos::FVec3::ZeroVector;
}
virtual void SampleWaterAtPosition(const FVector& WorldPosition, FVector& OutVelocity, float& OutHeight, float& OutDepth) const override
{
OutVelocity = WaterVel;
OutHeight = ClosestWaterPointToParticle.Z;
// #todo(dmp): We technically don't use depth other than needing to know it is non zero
// We should separate out the queries a bit more to avoid needing to set it here
OutDepth = 1;
}
virtual FVector SampleVelocityAtPosition(const FVector& WorldPosition) const override
{
return WaterVel;
}
virtual FVector SampleNormalAtPosition(const FVector& WorldPosition) const override
{
return WaterN;
}
public:
const FBuoyancyWaterSplineData &WaterSpline;
FVector ClosestPointDerivative = FVector::ZeroVector;
float ClosestSplineKey = 0;
FVector WaterN = FVector::ZeroVector;
FVector WaterVel = FVector::ZeroVector;
bool WaterVelSet = false;
};
// Each particle will have a list of potential midphases to process,
// which must be sorted in descending Z order. This struct is used
// to store them
struct FBuoyancyInteraction
{
Chaos::FPBDRigidParticleHandle* RigidParticle = nullptr;
Chaos::FGeometryParticleHandle* WaterParticle = nullptr;
TSharedPtr<FBuoyancyWaterSampler> WaterSampler;
};
static constexpr int32 MaxNumBuoyancyInteractions = 2;
using FBuoyancyInteractionArray = TArray<FBuoyancyInteraction, TInlineAllocator<MaxNumBuoyancyInteractions>>;
// A minimal struct of data tracking all the submersions in a frame.
struct FBuoyancySubmersion
{
Chaos::FPBDRigidParticleHandle* Particle = nullptr;
TWeakPtr<FProxyTimestampBase, ESPMode::ThreadSafe> SyncTimestamp = nullptr;
float Vol = 0.f;
FVector CoM = FVector::ZeroVector;
FVector Vel = FVector::ZeroVector;
FVector Norm = FVector::ZeroVector;
// #todo(dmp): only used for accurate submersion calculations for now because we integrate those per body. Ideally we'd
// convert to only using this
bool HasIntegratedQuantities = false;
FVector DeltaV = FVector::ZeroVector;
FVector DeltaW = FVector::ZeroVector;
};
// Metadata for submersions, used for event callbacks
struct FBuoyancySubmersionMetaData
{
struct FWaterContact
{
Chaos::FGeometryParticleHandle* Water = nullptr;
TWeakPtr<FProxyTimestampBase, ESPMode::ThreadSafe> SyncTimestamp = nullptr;
float Vol = 0.f;
FVector CoM = FVector::ZeroVector;
FVector Vel = FVector::ZeroVector;
};
// How many metadata's allowed per submerged particle
static constexpr int32 MaxNumWaterContacts = 3;
TArray<FWaterContact, TInlineAllocator<MaxNumWaterContacts>> WaterContacts;
};
struct FBuoyancyParticleData
{
private:
// Access an element in a specific array, adding an uninitialized one if
// it doesn't exist yet.
template <typename TData>
TData& GetData(const Chaos::FGeometryParticleHandle& ParticleHandle, TSparseArray<TData>& DataArray)
{
SCOPE_CYCLE_COUNTER(STAT_BuoyancyParticleData_GetData)
const int32 DataIndex = AddOrGetIndex(ParticleHandle);
if (DataArray.IsValidIndex(DataIndex) == false)
{
LLM_SCOPE_BYTAG(BuoyancyParticleDataTag);
DataArray.Insert(DataIndex, TData());
}
return DataArray[DataIndex];
}
public:
FBuoyancyParticleData();
~FBuoyancyParticleData();
// Clear all data without freeing memory, for quick repopulation
void Reset();
// For memory debugging, log the number of bytes that this class has allocated
SIZE_T GetAllocatedSize() const;
// Internal index accessor
int32 GetIndex(const Chaos::FGeometryParticleHandle& ParticleHandle);
int32 AddOrGetIndex(const Chaos::FGeometryParticleHandle& ParticleHandle);
bool RemoveIndex(const Chaos::FGeometryParticleHandle& ParticleHandle);
// Bookkeeping arrays
//
// Map of indices from unique particle indices to internal array indices
//TSparseArray<int32> IndexMap;
FHashTable IndexMap;
TSparseArray<int32> ReverseIndexMap;
// Sparse array of arrays of buoyancy interactions - the outer array has one
// entry per particle, the inner array has an entry per water body that it interacts
// with. Each will be a very small array, sorted by Z.
//
// We use inline allocator to avoid more heap allocations, and to express the
// assumption that a single particle is unlikely to exceed interactions with a
// certain number of waterbodies at a time.
TSparseArray<FBuoyancyInteractionArray> Interactions;
// This sparse array of submersion events is indexed on particle unique indices.
// All buoyant forces due to submersions are applied at once. It's stored as
// a member variable and reset every frame, to avoid reallocation of similarly
// sized data.
TSparseArray<FBuoyancySubmersion> Submersions;
TSparseArray<FBuoyancySubmersion> PrevSubmersions;
// Another sparse array to be kept in sync with Submersions, which will contain
// metadata useful for event callbacks
TSparseArray<FBuoyancySubmersionMetaData> SubmersionMetaData;
TSparseArray<FBuoyancySubmersionMetaData> PrevSubmersionMetaData;
// This is a sparse array of bit arrays representing which shapes in an object
// have already been accounted for when submerging an object. For example, if
// a massive BVH object has two leaf node shapes submerged in different pools
// of water and we've already detected that leaf A is submerged, we don't
// need to test A again. This helps to avoid double counting submerged shapes.
//
// Just like Submersions, we have this as a member variable only to keep the
// memory hot - the array is reset, repopulated and traversed, every frame,
// so we want to minimize allocations.
TSparseArray<TBitArray<>> SubmergedShapes;
// Element accessors
FBuoyancyInteractionArray& GetInteractions(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, Interactions);
}
FBuoyancySubmersion& GetSubmersion(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, Submersions);
}
FBuoyancySubmersion& GetPrevSubmersion(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, PrevSubmersions);
}
FBuoyancySubmersionMetaData& GetSubmersionMetaData(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, SubmersionMetaData);
}
FBuoyancySubmersionMetaData& GetPrevSubmersionMetaData(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, PrevSubmersionMetaData);
}
TBitArray<>& GetSubmergedShapes(const Chaos::FGeometryParticleHandle& ParticleHandle)
{
return GetData(ParticleHandle, SubmergedShapes);
}
private:
int32 GetIndex(const Chaos::FGeometryParticleHandle& ParticleHandle, int32& OutParticleIndex, int32& OutParticleKey);
int32 GetIndex(const int32 ParticleIndex, const int32 ParticleKey);
/** Shrink or grow internal memory - this operation may be slow when a resize is performed,
but should result in more optimal storage and faster data accesses on average. */
void OptimizeMemory();
};