1220 lines
38 KiB
C++
1220 lines
38 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "HeadlessChaosTestBroadphase.h"
|
|
|
|
#include "HeadlessChaos.h"
|
|
#include "Chaos/Box.h"
|
|
#include "Chaos/BoundingVolume.h"
|
|
#include "Chaos/ParticleHandle.h"
|
|
#include "Chaos/PBDRigidsSOAs.h"
|
|
#include "Chaos/PBDRigidsEvolutionGBF.h"
|
|
#include "Chaos/AABBTree.h"
|
|
#include "ChaosLog.h"
|
|
#include "PBDRigidsSolver.h"
|
|
#include "Chaos/SpatialAccelerationCollection.h"
|
|
|
|
namespace ChaosTest
|
|
{
|
|
using namespace Chaos;
|
|
|
|
/*In general we want to test the following for each broadphase type:
|
|
- simple intersection test as used by sim (IntersectAll)
|
|
- ray, sweep, overlap
|
|
- miss entire structure
|
|
- stop mid structure
|
|
- multi overlap
|
|
- multi block (adjust length)
|
|
- any
|
|
*/
|
|
|
|
struct FVisitor : ISpatialVisitor<int32>
|
|
{
|
|
const FGeometryParticles& Boxes;
|
|
const FVec3 Start;
|
|
const FVec3 Dir;
|
|
FVec3 HalfExtents;
|
|
const FReal Thickness;
|
|
int32 BlockAfterN;
|
|
bool bAny;
|
|
|
|
FVisitor(const FVec3& InStart, const FVec3& InDir, const FReal InThickness, const FGeometryParticles& InBoxes)
|
|
: Boxes(InBoxes)
|
|
, Start(InStart)
|
|
, Dir(InDir)
|
|
, HalfExtents(0)
|
|
, Thickness(InThickness)
|
|
, BlockAfterN(TNumericLimits<int32>::Max())
|
|
, bAny(false)
|
|
{}
|
|
|
|
enum class SQType
|
|
{
|
|
Raycast,
|
|
Sweep,
|
|
Overlap
|
|
};
|
|
|
|
template <SQType>
|
|
bool Visit(int32 Idx, FQueryFastData& CurData)
|
|
{
|
|
const FRigidTransform3 BoxTM(Boxes.GetX(Idx), Boxes.GetR(Idx));
|
|
FAABB3 Box = static_cast<const TBox<FReal, 3>*>(Boxes.GetGeometry(Idx).GetReference())->BoundingBox().TransformedAABB(BoxTM);
|
|
FAABB3 ThicknedBox(Box.Min() - HalfExtents, Box.Max() + HalfExtents);
|
|
|
|
FReal NewLength;
|
|
FVec3 Position;
|
|
FVec3 Normal;
|
|
int32 FaceIndex;
|
|
const FReal OldLength = CurData.CurrentLength;
|
|
if (ThicknedBox.Raycast(Start, Dir, CurData.CurrentLength, 0, NewLength, Position, Normal, FaceIndex))
|
|
{
|
|
Instances.Add(Idx);
|
|
if (bAny)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisitRaycast(TSpatialVisitorData<int32> Idx, FQueryFastData& CurData)
|
|
{
|
|
return Visit<SQType::Raycast>(Idx.Payload, CurData);
|
|
}
|
|
|
|
bool VisitSweep(TSpatialVisitorData<int32> Idx, FQueryFastData& CurData)
|
|
{
|
|
return Visit<SQType::Sweep>(Idx.Payload, CurData);
|
|
}
|
|
|
|
bool VisitOverlap(TSpatialVisitorData<int32> Idx)
|
|
{
|
|
check(false);
|
|
return false;
|
|
}
|
|
|
|
virtual bool Overlap(const TSpatialVisitorData<int32>& Instance) override
|
|
{
|
|
return VisitOverlap(Instance);
|
|
}
|
|
|
|
virtual bool Raycast(const TSpatialVisitorData<int32>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitRaycast(Instance, CurData);
|
|
}
|
|
|
|
virtual bool Sweep(const TSpatialVisitorData<int32>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitSweep(Instance, CurData);
|
|
}
|
|
|
|
virtual bool HasBlockingHit() const override
|
|
{
|
|
return Instances.Num() >= BlockAfterN;
|
|
}
|
|
|
|
TArray<int32> Instances;
|
|
};
|
|
|
|
struct FOverlapVisitor : public ISpatialVisitor<int32>
|
|
{
|
|
const FGeometryParticles& Boxes;
|
|
const FAABB3 Bounds;
|
|
bool bAny;
|
|
|
|
FOverlapVisitor(const FAABB3& InBounds, const FGeometryParticles& InBoxes)
|
|
: Boxes(InBoxes)
|
|
, Bounds(InBounds)
|
|
, bAny(false)
|
|
{}
|
|
|
|
bool VisitOverlap(TSpatialVisitorData<int32> Instance)
|
|
{
|
|
const int32 Idx = Instance.Payload;
|
|
const FRigidTransform3 BoxTM(Boxes.GetX(Idx), Boxes.GetR(Idx));
|
|
FAABB3 Box = static_cast<const TBox<FReal, 3>*>(Boxes.GetGeometry(Idx).GetReference())->BoundingBox().TransformedAABB(BoxTM);
|
|
|
|
if (Box.Intersects(Bounds))
|
|
{
|
|
Instances.Add(Idx);
|
|
if (bAny)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisitRaycast(TSpatialVisitorData<int32> Idx, FQueryFastData&)
|
|
{
|
|
check(false);
|
|
return false;
|
|
}
|
|
|
|
bool VisitSweep(TSpatialVisitorData<int32> Idx, FQueryFastData&)
|
|
{
|
|
check(false);
|
|
return false;
|
|
}
|
|
|
|
virtual bool Overlap(const TSpatialVisitorData<int32>& Instance) override
|
|
{
|
|
return VisitOverlap(Instance);
|
|
}
|
|
|
|
virtual bool Raycast(const TSpatialVisitorData<int32>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitRaycast(Instance, CurData);
|
|
}
|
|
|
|
virtual bool Sweep(const TSpatialVisitorData<int32>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitSweep(Instance, CurData);
|
|
}
|
|
|
|
TArray<int32> Instances;
|
|
};
|
|
|
|
struct FStressTestVisitor : ISpatialVisitor<FAccelerationStructureHandle>
|
|
{
|
|
using FPayload = FAccelerationStructureHandle;
|
|
|
|
FStressTestVisitor() {}
|
|
|
|
enum class SQType
|
|
{
|
|
Raycast,
|
|
Sweep,
|
|
Overlap
|
|
};
|
|
|
|
bool VisitRaycast(const TSpatialVisitorData<FPayload>& Data, FQueryFastData& CurData)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool VisitSweep(const TSpatialVisitorData<FPayload>& Data, FQueryFastData& CurData)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool VisitOverlap(const TSpatialVisitorData<FPayload>& Data)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual bool Overlap(const TSpatialVisitorData<FPayload>& Instance) override
|
|
{
|
|
return VisitOverlap(Instance);
|
|
}
|
|
|
|
virtual bool Raycast(const TSpatialVisitorData<FPayload>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitRaycast(Instance, CurData);
|
|
}
|
|
|
|
virtual bool Sweep(const TSpatialVisitorData<FPayload>& Instance, FQueryFastData& CurData) override
|
|
{
|
|
return VisitSweep(Instance, CurData);
|
|
}
|
|
};
|
|
|
|
|
|
auto BuildBoxes(FImplicitObjectPtr& Box, FReal BoxSize = 100, const FVec3& BoxGridDimensions = FVec3(10,10,10), const FVec3 Offset = FVec3(0, 0, 0))
|
|
{
|
|
Box = MakeImplicitObjectPtr<TBox<FReal, 3>>(FVec3(0, 0, 0), FVec3(BoxSize, BoxSize, BoxSize));
|
|
auto Boxes = MakeUnique<FGeometryParticles>();
|
|
const int32 NumCols = BoxGridDimensions.X;
|
|
const int32 NumRows = BoxGridDimensions.Y;
|
|
const int32 NumHeight = BoxGridDimensions.Z;
|
|
|
|
Boxes->AddParticles(NumRows * NumCols * NumHeight);
|
|
|
|
int32 Idx = 0;
|
|
for (int32 Height = 0; Height < NumHeight; ++Height)
|
|
{
|
|
for (int32 Row = 0; Row < NumRows; ++Row)
|
|
{
|
|
for (int32 Col = 0; Col < NumCols; ++Col)
|
|
{
|
|
Boxes->SetX(Idx, FVec3(Col * 100, Row * 100, Height * 100) + Offset);
|
|
Boxes->SetR(Idx, FRotation3::Identity);
|
|
Boxes->SetGeometry(Idx, Box);
|
|
++Idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Boxes;
|
|
}
|
|
|
|
template <typename TSpatial>
|
|
void SpatialTestHelper(TSpatial& Spatial, FGeometryParticles* Boxes, FImplicitObjectPtr& Box, FSpatialAccelerationIdx SpatialIdx = FSpatialAccelerationIdx())
|
|
{
|
|
//raycast
|
|
//miss
|
|
{
|
|
FVisitor Visitor(FVec3(-100, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 0);
|
|
}
|
|
|
|
//gather along ray
|
|
{
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 10);
|
|
}
|
|
|
|
//gather along ray and then make modifications
|
|
{
|
|
auto Spatial2 = Spatial.Copy();
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 10);
|
|
|
|
//remove from structure
|
|
Spatial2->RemoveElementFrom(Visitor.Instances[0], SpatialIdx);
|
|
|
|
FVisitor Visitor2(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor2.Start, Visitor2.Dir, 1000, Visitor2);
|
|
EXPECT_EQ(Visitor2.Instances.Num(), 9);
|
|
|
|
//move instance away
|
|
{
|
|
const int32 MoveIdx = Visitor2.Instances[0];
|
|
Boxes->SetX(MoveIdx, Boxes->GetX(MoveIdx) + FVec3(1000, 0, 0));
|
|
FAABB3 NewBounds = Boxes->GetGeometry(MoveIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(MoveIdx), Boxes->GetR(MoveIdx)));
|
|
Spatial2->UpdateElementIn(MoveIdx, NewBounds, true, SpatialIdx);
|
|
|
|
FVisitor Visitor3(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor3.Start, Visitor3.Dir, 1000, Visitor3);
|
|
EXPECT_EQ(Visitor3.Instances.Num(), 8);
|
|
|
|
//move instance back
|
|
Boxes->SetX(MoveIdx, Boxes->GetX(MoveIdx) - FVec3(1000, 0, 0));
|
|
NewBounds = Boxes->GetGeometry(MoveIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(MoveIdx), Boxes->GetR(MoveIdx)));
|
|
Spatial2->UpdateElementIn(MoveIdx, NewBounds, true, SpatialIdx);
|
|
|
|
FVisitor Visitor4(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor4.Start, Visitor4.Dir, 1000, Visitor4);
|
|
EXPECT_EQ(Visitor4.Instances.Num(), 9);
|
|
}
|
|
|
|
//move other instance into view
|
|
{
|
|
const int32 MoveIdx = 5 * 5 * 5;
|
|
const FVec3 OldPos = Boxes->GetX(MoveIdx);
|
|
Boxes->SetX(MoveIdx, FVec3(0, 0, 0));
|
|
FAABB3 NewBounds = Boxes->GetGeometry(MoveIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(MoveIdx), Boxes->GetR(MoveIdx)));
|
|
Spatial2->UpdateElementIn(MoveIdx, NewBounds, true, SpatialIdx);
|
|
|
|
FVisitor Visitor3(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor3.Start, Visitor3.Dir, 1000, Visitor3);
|
|
EXPECT_EQ(Visitor3.Instances.Num(), 10);
|
|
|
|
//move instance back
|
|
Boxes->SetX(MoveIdx, OldPos);
|
|
NewBounds = Boxes->GetGeometry(MoveIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(MoveIdx), Boxes->GetR(MoveIdx)));
|
|
Spatial2->UpdateElementIn(MoveIdx, NewBounds, true, SpatialIdx);
|
|
}
|
|
|
|
//move instance outside of grid bounds
|
|
{
|
|
const int32 MoveIdx = 5 * 5 * 5;
|
|
const FVec3 OldPos = Boxes->GetX(MoveIdx);
|
|
Boxes->SetX(MoveIdx, FVec3(-50, 0, 0));
|
|
FAABB3 NewBounds = Boxes->GetGeometry(MoveIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(MoveIdx), Boxes->GetR(MoveIdx)));
|
|
Spatial2->UpdateElementIn(MoveIdx, NewBounds, true, SpatialIdx);
|
|
|
|
FVisitor Visitor3(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor3.Start, Visitor3.Dir, 1000, Visitor3);
|
|
EXPECT_EQ(Visitor3.Instances.Num(), 10);
|
|
|
|
//try ray outside of bounds which should hit
|
|
FVisitor Visitor4(FVec3(-20, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor4.Start, Visitor4.Dir, 1000, Visitor4);
|
|
EXPECT_EQ(Visitor4.Instances.Num(), 1);
|
|
|
|
//delete dirty instance
|
|
Spatial2->RemoveElementFrom(MoveIdx, SpatialIdx);
|
|
FVisitor Visitor5(FVec3(-20, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor5.Start, Visitor5.Dir, 1000, Visitor4);
|
|
EXPECT_EQ(Visitor5.Instances.Num(), 0);
|
|
|
|
//move instance back
|
|
Boxes->SetX(MoveIdx, OldPos);
|
|
|
|
//create a new box
|
|
const int32 NewIdx = Boxes->Size();
|
|
Boxes->AddParticles(1);
|
|
Boxes->SetX(NewIdx, FVec3(-20, 0, 0));
|
|
Boxes->SetR(NewIdx, FRotation3::Identity);
|
|
Boxes->SetGeometry(NewIdx, Box);
|
|
NewBounds = Boxes->GetGeometry(NewIdx)->template GetObject<TBox<FReal, 3>>()->BoundingBox().TransformedAABB(FRigidTransform3(Boxes->GetX(NewIdx), Boxes->GetR(NewIdx)));
|
|
Spatial2->UpdateElementIn(NewIdx, NewBounds, true, SpatialIdx);
|
|
FVisitor Visitor6(FVec3(-20, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial2->Raycast(Visitor6.Start, Visitor6.Dir, 1000, Visitor6);
|
|
EXPECT_EQ(Visitor6.Instances.Num(), 1);
|
|
}
|
|
}
|
|
|
|
//stop half way through
|
|
{
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 499, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 5);
|
|
}
|
|
|
|
//any
|
|
{
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.bAny = true;
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 1);
|
|
}
|
|
|
|
//sweep
|
|
//miss
|
|
{
|
|
FVisitor Visitor(FVec3(-100, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.HalfExtents = FVec3(10, 0, 0);
|
|
Spatial.Sweep(Visitor.Start, Visitor.Dir, 1000, Visitor.HalfExtents, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 0);
|
|
}
|
|
|
|
//gather along ray
|
|
{
|
|
FVisitor Visitor(FVec3(-100, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.HalfExtents = FVec3(110, 0, 0);
|
|
Spatial.Sweep(Visitor.Start, Visitor.Dir, 1000, Visitor.HalfExtents, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 10);
|
|
}
|
|
|
|
//stop half way through
|
|
{
|
|
FVisitor Visitor(FVec3(-100, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.HalfExtents = FVec3(110, 0, 0);
|
|
Spatial.Sweep(Visitor.Start, Visitor.Dir, 499, Visitor.HalfExtents, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 5);
|
|
}
|
|
|
|
//right on edge and corner
|
|
{
|
|
FVisitor Visitor(FVec3(100, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.HalfExtents = FVec3(10, 0, 0);
|
|
Spatial.Sweep(Visitor.Start, Visitor.Dir, 499, Visitor.HalfExtents, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 10);
|
|
}
|
|
|
|
//overlap
|
|
//miss
|
|
{
|
|
FOverlapVisitor Visitor(FAABB3(FVec3(-100, 0, 0), FVec3(-10, 0, 0)), *Boxes);
|
|
Spatial.Overlap(Visitor.Bounds, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 0);
|
|
}
|
|
|
|
//overlap some
|
|
{
|
|
FOverlapVisitor Visitor(FAABB3(FVec3(-100, 0, -10), FVec3(110, 110, 10)), *Boxes);
|
|
Spatial.Overlap(Visitor.Bounds, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 4);
|
|
}
|
|
|
|
//overlap any
|
|
{
|
|
FOverlapVisitor Visitor(FAABB3(FVec3(-100, 0, -10), FVec3(110, 110, 10)), *Boxes);
|
|
Visitor.bAny = true;
|
|
Spatial.Overlap(Visitor.Bounds, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 1);
|
|
}
|
|
}
|
|
|
|
void GridBPTest()
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TBoundingVolume<int32> Spatial(MakeParticleView(Boxes.Get()));
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
|
|
void GridBPEarlyExitTest()
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TBoundingVolume<int32> Spatial(MakeParticleView(Boxes.Get()));
|
|
// SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
|
|
//gather along ray
|
|
{
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 10);
|
|
EXPECT_EQ(Visitor.Instances[0], 0);
|
|
EXPECT_EQ(Visitor.Instances[9], 90);
|
|
}
|
|
|
|
// Stop after first hits in the first cell
|
|
{
|
|
FVisitor Visitor(FVec3(10, 0, 0), FVec3(0, 1, 0), 0, *Boxes);
|
|
Visitor.BlockAfterN = 1;
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 1);
|
|
EXPECT_EQ(Visitor.Instances[0], 0);
|
|
}
|
|
|
|
// Stop after first hits in the first cell, going backward
|
|
{
|
|
FVisitor Visitor(FVec3(10, 1000, 0), FVec3(0, -1, 0), 0, *Boxes);
|
|
Visitor.BlockAfterN = 1;
|
|
Spatial.Raycast(Visitor.Start, Visitor.Dir, 1000, Visitor);
|
|
EXPECT_EQ(Visitor.Instances.Num(), 1);
|
|
EXPECT_EQ(Visitor.Instances[0], 90);
|
|
}
|
|
}
|
|
|
|
void GridBPTest2()
|
|
{
|
|
FImplicitObjectPtr Box( new TBox<FReal, 3>(FVec3(0, 0, 0), FVec3(100, 100, 100)));
|
|
FParticleUniqueIndicesMultithreaded UniqueIndices;
|
|
FPBDRigidsSOAs SOAs(UniqueIndices);
|
|
const int32 NumRows = 10;
|
|
const int32 NumCols = 10;
|
|
const int32 NumHeight = 10;
|
|
|
|
SOAs.CreateStaticParticles(NumRows * NumCols * NumHeight);
|
|
auto& Boxes = SOAs.GetNonDisabledStaticParticles();
|
|
int32 Idx = 0;
|
|
for (int32 Height = 0; Height < NumHeight; ++Height)
|
|
{
|
|
for (int32 Row = 0; Row < NumRows; ++Row)
|
|
{
|
|
for (int32 Col = 0; Col < NumCols; ++Col)
|
|
{
|
|
Boxes.SetX(Idx, FVec3(Col * 100, Row * 100, Height * 100));
|
|
Boxes.SetR(Idx, FRotation3::Identity);
|
|
Boxes.SetGeometry(Idx, Box);
|
|
++Idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<TSOAView<FGeometryParticles>> TmpArray = { &Boxes };
|
|
TBoundingVolume<FGeometryParticleHandle*> BV(MakeParticleView(MoveTemp(TmpArray)));
|
|
TArray<FGeometryParticleHandle*> Handles = BV.FindAllIntersections(FAABB3(FVec3(0), FVec3(10)));
|
|
EXPECT_EQ(Handles.Num(), 1);
|
|
EXPECT_EQ(Handles[0], Boxes.Handle(0));
|
|
|
|
Handles = BV.FindAllIntersections(FAABB3(FVec3(0), FVec3(0, 0, 110)));
|
|
EXPECT_EQ(Handles.Num(), 2);
|
|
|
|
//create BV with an array of handles instead (useful for partial structures)
|
|
{
|
|
TBoundingVolume<FGeometryParticleHandle*> BV2(MakeHandleView(Handles));
|
|
TArray<FGeometryParticleHandle*> Handles2 = BV2.FindAllIntersections(FAABB3(FVec3(0), FVec3(10)));
|
|
EXPECT_EQ(Handles2.Num(), 1);
|
|
EXPECT_EQ(Handles2[0], Boxes.Handle(0));
|
|
|
|
Handles2 = BV2.FindAllIntersections(FAABB3(FVec3(0), FVec3(0, 0, 110)));
|
|
EXPECT_EQ(Handles2.Num(), 2);
|
|
}
|
|
}
|
|
|
|
void AABBTreeTest()
|
|
{
|
|
using TreeType = TAABBTree<int32, TBoundingVolume<int32>>;
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TreeType Spatial(MakeParticleView(Boxes.Get()));
|
|
|
|
while (!Spatial.IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial.ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TreeType Spatial(MakeParticleView(Boxes.Get()));
|
|
|
|
while (!Spatial.IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial.ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
|
|
{
|
|
//too many boxes so reoptimize
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TreeType Spatial(MakeParticleView(Boxes.Get()));
|
|
|
|
while(!Spatial.IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial.ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
EXPECT_EQ(Spatial.NumDirtyElements(),0);
|
|
|
|
//fill up until dirty limit
|
|
int32 Count;
|
|
for(Count = 1; Count <= 10; ++Count)
|
|
{
|
|
auto Boxes2 = BuildBoxes(Box);
|
|
for(uint32 Idx = 0; Idx < Boxes2->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx + Boxes->Size() * Count,Boxes2->WorldSpaceInflatedBounds(Idx),true);
|
|
}
|
|
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), (Count)*Boxes->Size());
|
|
}
|
|
|
|
//finally pass dirty limit so reset to 0 and then add the remaining new boxes
|
|
auto Boxes2 = BuildBoxes(Box);
|
|
for(uint32 Idx = 0; Idx < Boxes2->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx + Boxes->Size() * (Count),Boxes2->WorldSpaceInflatedBounds(Idx),true);
|
|
}
|
|
|
|
EXPECT_EQ(Spatial.NumDirtyElements(),Boxes->Size() - 1);
|
|
}
|
|
}
|
|
|
|
|
|
void AABBTreeTestDynamic()
|
|
{
|
|
using TreeType = TAABBTree<int32, TAABBTreeLeafArray<int32>, true>;
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box, 100, FVec3(10,10,10));
|
|
TreeType Spatial(MakeParticleView(Boxes.Get()), TreeType::DefaultMaxChildrenInLeaf, TreeType::DefaultMaxTreeDepth, TreeType::DefaultMaxPayloadBounds, TreeType::DefaultMaxNumToProcess, true);
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), 0);
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), 0);
|
|
}
|
|
|
|
}
|
|
|
|
void AABBTreeDirtyTreeTest()
|
|
{
|
|
using TreeType = TAABBTree<int32, TAABBTreeLeafArray<int32>, true>;
|
|
|
|
// Do the standard tests
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
|
|
TArray<TSOAView<FGeometryParticles>> EmptyArray;
|
|
TreeType Spatial{ MakeParticleView(MoveTemp(EmptyArray)),5, 5, 10000.0f, 1000, false, true};
|
|
|
|
int32 Idx;
|
|
for (Idx = 0; Idx < (int32)Boxes->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx, Boxes->WorldSpaceInflatedBounds(Idx), true);
|
|
}
|
|
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
}
|
|
|
|
void AABBTreeDirtyGridTest()
|
|
{
|
|
using TreeType = TAABBTree<int32, TBoundingVolume<int32>>;
|
|
|
|
// Save CVARS
|
|
int32 DirtyElementGridCellSize = FAABBTreeDirtyGridCVars::DirtyElementGridCellSize;
|
|
int32 DirtyElementMaxGridCellQueryCount = FAABBTreeDirtyGridCVars::DirtyElementMaxGridCellQueryCount;
|
|
int32 DirtyElementMaxPhysicalSizeInCells = FAABBTreeDirtyGridCVars::DirtyElementMaxPhysicalSizeInCells;
|
|
int32 DirtyElementMaxCellCapacity = FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity;
|
|
|
|
// Set CVARS to known values
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = 100;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxGridCellQueryCount = 10000;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxPhysicalSizeInCells = 20;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity = 20;
|
|
|
|
|
|
// Do the standard tests
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TreeType Spatial{};
|
|
|
|
int32 Idx;
|
|
for (Idx = 0; Idx < (int32)Boxes->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx, Boxes->WorldSpaceInflatedBounds(Idx), true);
|
|
}
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), Boxes->Size());
|
|
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
|
|
|
|
// Repeat the standard tests with low cell capacity and different cell sizes
|
|
{
|
|
// Set CVARS to known values
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = 44;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity = 2;
|
|
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
TreeType Spatial{};
|
|
|
|
int32 Idx;
|
|
for (Idx = 0; Idx < (int32)Boxes->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx, Boxes->WorldSpaceInflatedBounds(Idx), true);
|
|
}
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), Boxes->Size());
|
|
|
|
SpatialTestHelper(Spatial, Boxes.Get(), Box);
|
|
}
|
|
|
|
|
|
// Make sure we get the same results, with and without the grid for sweeps and raycasts
|
|
{
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity = 7;
|
|
FImplicitObjectPtr Box;
|
|
FVec3 LargeOffset(10000000, 10000000, 10000000); // Test for floating point precision errors at large world offsets
|
|
auto Boxes = BuildBoxes(Box, 100, FVec3(40, 40, 1), FVec3(-2000, -2000, -50) + LargeOffset);
|
|
|
|
for (float Angle = 0.0; Angle < 2 * PI; Angle += (10.0f / 360.0f) * 2.0f * PI)
|
|
{
|
|
|
|
FVec3 Direction{ FMath::Cos(Angle), FMath::Sin(Angle), 0 };
|
|
// With the grid
|
|
FVisitor VisitorGrid(FVec3(53, 27, 0) + LargeOffset, Direction, 0, *Boxes);
|
|
VisitorGrid.HalfExtents = FVec3(102, 20, 2);
|
|
{
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = 100;
|
|
|
|
TreeType Spatial{};
|
|
|
|
int32 Idx;
|
|
for (Idx = 0; Idx < (int32)Boxes->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx, Boxes->WorldSpaceInflatedBounds(Idx), true);
|
|
}
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), Boxes->Size());
|
|
|
|
Spatial.Raycast(VisitorGrid.Start, VisitorGrid.Dir, 1900, VisitorGrid);
|
|
Spatial.Sweep(VisitorGrid.Start, VisitorGrid.Dir, 1800, VisitorGrid.HalfExtents, VisitorGrid);
|
|
}
|
|
|
|
// Without the grid
|
|
FVisitor VisitorNoGrid(FVec3(53, 27, 0) + LargeOffset, Direction, 0, *Boxes);
|
|
VisitorNoGrid.HalfExtents = FVec3(102, 20, 2);
|
|
{
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = 0;
|
|
TreeType Spatial{};
|
|
|
|
int32 Idx;
|
|
for (Idx = 0; Idx < (int32)Boxes->Size(); ++Idx)
|
|
{
|
|
Spatial.UpdateElement(Idx, Boxes->WorldSpaceInflatedBounds(Idx), true);
|
|
}
|
|
EXPECT_EQ(Spatial.NumDirtyElements(), Boxes->Size());
|
|
|
|
Spatial.Raycast(VisitorNoGrid.Start, VisitorNoGrid.Dir, 1900, VisitorNoGrid);
|
|
Spatial.Sweep(VisitorNoGrid.Start, VisitorNoGrid.Dir, 1800, VisitorNoGrid.HalfExtents, VisitorNoGrid);
|
|
}
|
|
// These will be in the same order, but we can drop this requirement in the future
|
|
EXPECT_TRUE(VisitorNoGrid.Instances == VisitorGrid.Instances);
|
|
}
|
|
|
|
}
|
|
|
|
// Test a case that failed before (with an assert)
|
|
{
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = 1000;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity = 7;
|
|
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(-3000, -1000, -50)); // Just one box
|
|
TreeType Spatial{};
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true);
|
|
|
|
// Move the Box
|
|
Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(-4000, -1000, -50)); // Change position of box
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true); // Check for no ensures
|
|
|
|
// Move the Box
|
|
Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(3000, 1000, -50)); // Change position of box
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true);
|
|
|
|
Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(4000, 1000, -50)); // Change position of box
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true); // Check for no ensures
|
|
|
|
// Move the Box
|
|
Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(-10000003000.0f, -1000, -50)); // Change position of box
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true); // Check for no ensures
|
|
|
|
// Move the Box
|
|
Boxes = BuildBoxes(Box, 100, FVec3(1, 1, 1), FVec3(-10000004000.0f, -1000, -50)); // Change position of box
|
|
Spatial.UpdateElement(0, Boxes->WorldSpaceInflatedBounds(0), true); // Check for no ensures
|
|
|
|
}
|
|
|
|
|
|
// Restore CVARS
|
|
FAABBTreeDirtyGridCVars::DirtyElementGridCellSize = DirtyElementGridCellSize;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxGridCellQueryCount = DirtyElementMaxGridCellQueryCount;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxPhysicalSizeInCells = DirtyElementMaxPhysicalSizeInCells;
|
|
FAABBTreeDirtyGridCVars::DirtyElementMaxCellCapacity = DirtyElementMaxCellCapacity;
|
|
}
|
|
|
|
void DoForSweepIntersectCellsImpTest()
|
|
{
|
|
{
|
|
int32 NumFuncCalled = 0;
|
|
int32 XArray[2];
|
|
int32 YArray[2];
|
|
|
|
DoForSweepIntersectCellsImp(1.4484817992026819, 1.4432470701435705, 1251.1886035677471, -1183.6311465697545, -866.67708504199993, -747.83750413730752, 1000.0, 0.001,
|
|
[&](auto X, auto Y) {
|
|
XArray[NumFuncCalled] = X;
|
|
YArray[NumFuncCalled] = Y;
|
|
++NumFuncCalled;
|
|
|
|
});
|
|
|
|
EXPECT_EQ(NumFuncCalled, 2);
|
|
EXPECT_EQ(XArray[0], 1000);
|
|
EXPECT_EQ(YArray[0], -2000);
|
|
|
|
EXPECT_EQ(XArray[1], 0);
|
|
EXPECT_EQ(YArray[1], -2000);
|
|
}
|
|
|
|
{
|
|
int32 NumFuncCalled = 0;
|
|
int32 XArray[2];
|
|
int32 YArray[2];
|
|
|
|
DoForSweepIntersectCellsImp(1.4484817992026819, 1.4432470701435705, 1251.1886035677471, -1183.6311465697545, 866.67708504199993, -747.83750413730752, 1000.0, 0.001,
|
|
[&](auto X, auto Y) {
|
|
XArray[NumFuncCalled] = X;
|
|
YArray[NumFuncCalled] = Y;
|
|
++NumFuncCalled;
|
|
|
|
});
|
|
|
|
EXPECT_EQ(NumFuncCalled, 2);
|
|
EXPECT_EQ(XArray[0], 1000);
|
|
EXPECT_EQ(YArray[0], -2000);
|
|
|
|
EXPECT_EQ(XArray[1], 2000);
|
|
EXPECT_EQ(YArray[1], -2000);
|
|
}
|
|
|
|
{
|
|
int32 NumFuncCalled = 0;
|
|
int32 XArray[3];
|
|
int32 YArray[3];
|
|
|
|
DoForSweepIntersectCellsImp(1.2928878353696973, 1.2928878353697257, -1013.1421764369597, 210.55865232178132, 712.84350045280678, -265.39563631071809, 1000.0, 0.001,
|
|
[&](auto X, auto Y) {
|
|
XArray[NumFuncCalled] = X;
|
|
YArray[NumFuncCalled] = Y;
|
|
++NumFuncCalled;
|
|
|
|
});
|
|
|
|
EXPECT_EQ(NumFuncCalled, 3);
|
|
EXPECT_EQ(XArray[0], -2000);
|
|
EXPECT_EQ(YArray[0], 0);
|
|
|
|
EXPECT_EQ(XArray[1], -1000);
|
|
EXPECT_EQ(YArray[1], 0);
|
|
|
|
EXPECT_EQ(XArray[2], -1000);
|
|
EXPECT_EQ(YArray[2], -1000);
|
|
|
|
}
|
|
{
|
|
int32 NumFuncCalled = 0;
|
|
DoForSweepIntersectCellsImp(4000, 4000, 0, 0, 7000 - 0.01, 3000 - 0.01, 1000.0, 0.001,
|
|
[&](auto X, auto Y) {
|
|
++NumFuncCalled;
|
|
|
|
});
|
|
|
|
// This was verified manually on a paper grid
|
|
EXPECT_EQ(NumFuncCalled, 153);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void AABBTreeTimesliceTest()
|
|
{
|
|
using TreeType = TAABBTree<int32, TAABBTreeLeafArray<int32>>;
|
|
|
|
FImplicitObjectPtr Box;
|
|
|
|
// If we are time slicing by a milliseconds budget, create a large tree so it takes time to process
|
|
auto Boxes = FAABBTimeSliceCVars::bUseTimeSliceMillisecondBudget ? BuildBoxes(Box, 50, FVec3(50.0f,50.0f,50.0f)) : BuildBoxes(Box) ;
|
|
|
|
// build AABB in one go
|
|
TreeType SpatialBuildImmediate(
|
|
MakeParticleView(Boxes.Get())
|
|
, TreeType::DefaultMaxChildrenInLeaf
|
|
, TreeType::DefaultMaxTreeDepth
|
|
, TreeType::DefaultMaxPayloadBounds
|
|
, 0); // build entire tree in one go, no timeslicing
|
|
|
|
EXPECT_TRUE(SpatialBuildImmediate.IsAsyncTimeSlicingComplete());
|
|
|
|
const double SlicedTreeGenerationStartTime = FPlatformTime::Seconds();
|
|
|
|
// build AABB in time-sliced sections
|
|
TreeType SpatialTimesliced(
|
|
MakeParticleView(Boxes.Get())
|
|
, TreeType::DefaultMaxChildrenInLeaf
|
|
, TreeType::DefaultMaxTreeDepth
|
|
, TreeType::DefaultMaxPayloadBounds
|
|
, 20); // build in small iteration steps, 20 iterations per call to ProgressAsyncTimeSlicing
|
|
|
|
EXPECT_FALSE(!FAABBTimeSliceCVars::bUseTimeSliceMillisecondBudget && SpatialTimesliced.IsAsyncTimeSlicingComplete());
|
|
|
|
// This is far from accurate, but give us some wiggle room to test it with default settings without needed to implement code to simulate a precise pause inside the Tree implementation.
|
|
const float MaxSliceDurationWithErrorMargin = FAABBTimeSliceCVars::MaxProcessingTimePerSliceSeconds + 0.01f;
|
|
double LargestSliceDuration = 0;
|
|
|
|
int32 IterationNumber = 1;
|
|
bool bSliceDoneWithinBudget = true;
|
|
|
|
while (!SpatialTimesliced.IsAsyncTimeSlicingComplete())
|
|
{
|
|
const double SliceStartTime = FPlatformTime::Seconds();
|
|
|
|
SpatialTimesliced.ProgressAsyncTimeSlicing(false);
|
|
|
|
if (FAABBTimeSliceCVars::bUseTimeSliceMillisecondBudget)
|
|
{
|
|
const double ElapsedTime = FPlatformTime::Seconds() - SliceStartTime;
|
|
|
|
bSliceDoneWithinBudget &= ElapsedTime < MaxSliceDurationWithErrorMargin;
|
|
|
|
LargestSliceDuration = FMath::Max(LargestSliceDuration, ElapsedTime);
|
|
}
|
|
|
|
IterationNumber++;
|
|
}
|
|
|
|
EXPECT_TRUE(bSliceDoneWithinBudget);
|
|
|
|
const FStringView TimeSliceMode = FAABBTimeSliceCVars::bUseTimeSliceMillisecondBudget ? TEXT("MillisecondsBudget") : TEXT("AmountOfWorkToDo");
|
|
const double TotalGenerationTime = FPlatformTime::Seconds() - SlicedTreeGenerationStartTime;
|
|
|
|
UE_LOG(LogHeadlessChaos, Verbose, TEXT("Time Sliced Tree Generation took [%f] seconds | Using Mode [%s] | In [%d] Iterations | LargestSliceDuration [%f] | EvaluatedMaxTimeSlicedDurarion [%f]"), TotalGenerationTime, TimeSliceMode.GetData(), IterationNumber, LargestSliceDuration, MaxSliceDurationWithErrorMargin);
|
|
|
|
// now check both AABBs have the same hierarchy
|
|
// (indices will be different but walking tree should give same results)
|
|
|
|
FAABB3 Tmp = FAABB3::ZeroAABB();
|
|
|
|
TArray<FAABB3> AllBoundsBuildImmediate;
|
|
SpatialBuildImmediate.GetAsBoundsArray(AllBoundsBuildImmediate, 0, -1, Tmp);
|
|
|
|
TArray<FAABB3> AllBoundsTimesliced;
|
|
SpatialTimesliced.GetAsBoundsArray(AllBoundsTimesliced, 0, -1, Tmp);
|
|
|
|
EXPECT_EQ(AllBoundsBuildImmediate.Num(), AllBoundsTimesliced.Num());
|
|
|
|
for (int i=0; i<AllBoundsBuildImmediate.Num(); i++)
|
|
{
|
|
EXPECT_EQ(AllBoundsBuildImmediate[i].Center(), AllBoundsTimesliced[i].Center());
|
|
EXPECT_EQ(AllBoundsBuildImmediate[i].Extents(), AllBoundsTimesliced[i].Extents());
|
|
}
|
|
}
|
|
|
|
// specific sweep query seems to generate infinite loop
|
|
// the values are outside of the function to avoid optimizing away the values
|
|
// (the issue would only appear when optimization is enable)
|
|
namespace EdgeCase
|
|
{
|
|
FVec3 QueryHalfExtents{ 5.51277924f, 4.77557945f, 4.96569443f };
|
|
FVec3 StartPoint{ -40.7950134f, -4.77560043f, -11.2947388f };
|
|
FVec3 Dir{ 0, 0, -1 };
|
|
FReal CurrentLength = 146.779785f;
|
|
}
|
|
|
|
namespace LargeSweep
|
|
{
|
|
const FVec3 QueryHalfExtents{34, 34, 90};
|
|
const FVec3 StartPoint{5000000000270, 11630, 187.15000295639038};
|
|
const FVec3 Dir{-1.0000000172032004, 0, 0.00000000000040509186487903264};
|
|
const FReal CurrentLength = 4999999913984;
|
|
};
|
|
|
|
void AABBTreeDirtyGridFunctionsWithEdgeCase()
|
|
{
|
|
constexpr FReal DirtyElementGridCellSize = 1000.0;
|
|
constexpr FReal DirtyElementGridCellSizeInv = 1.0 / DirtyElementGridCellSize;
|
|
constexpr int32 DirtyElementMaxGridCellQueryCount = 340;
|
|
|
|
TArray<TVec2<FReal>> VisitedCells;
|
|
// this should report only be as much as 4 hits as teh grid is 2D and the ray downward vertical and the halfextends are smaller than a cell size
|
|
{
|
|
DoForSweepIntersectCells(EdgeCase::QueryHalfExtents, EdgeCase::StartPoint, EdgeCase::Dir, EdgeCase::CurrentLength, DirtyElementGridCellSize, DirtyElementGridCellSizeInv,
|
|
[&VisitedCells](FReal X, FReal Y)
|
|
{
|
|
VisitedCells.Add({ X,Y });
|
|
EXPECT_TRUE(VisitedCells.Num() <= 4 );
|
|
});
|
|
EXPECT_TRUE(VisitedCells.Num() <= 4);
|
|
}
|
|
|
|
// 50 trillions unit long sweep test reporting that there's not too many cells
|
|
{
|
|
const bool bTooManyCell = TooManySweepQueryCells(LargeSweep::QueryHalfExtents, LargeSweep::StartPoint, LargeSweep::Dir, LargeSweep::CurrentLength, DirtyElementGridCellSizeInv, DirtyElementMaxGridCellQueryCount);
|
|
EXPECT_TRUE(bTooManyCell);
|
|
}
|
|
}
|
|
|
|
void BroadphaseCollectionTest()
|
|
{
|
|
using TreeType = TAABBTree<int32, TAABBTreeLeafArray<int32>>;
|
|
{
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes = BuildBoxes(Box);
|
|
auto Spatial = MakeUnique<TreeType>(MakeParticleView(Boxes.Get()));
|
|
|
|
while (!Spatial->IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial->ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
TSpatialAccelerationCollection<TreeType> AccelerationCollection;
|
|
AccelerationCollection.AddSubstructure(MoveTemp(Spatial), 0, 0);
|
|
FSpatialAccelerationIdx SpatialIdx = { 0,0 };
|
|
SpatialTestHelper(AccelerationCollection, Boxes.Get(), Box, SpatialIdx);
|
|
}
|
|
|
|
{
|
|
using BVType = TBoundingVolume<int32>;
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes0 = BuildBoxes(Box);
|
|
auto Spatial0 = MakeUnique<TreeType>(MakeParticleView(Boxes0.Get()));
|
|
while (!Spatial0->IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial0->ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
FGeometryParticles EmptyBoxes;
|
|
auto Spatial1 = MakeUnique<BVType>(MakeParticleView(&EmptyBoxes));
|
|
while (!Spatial1->IsAsyncTimeSlicingComplete())
|
|
{
|
|
Spatial1->ProgressAsyncTimeSlicing(false);
|
|
}
|
|
|
|
TSpatialAccelerationCollection<TreeType, BVType> AccelerationCollection;
|
|
AccelerationCollection.AddSubstructure(MoveTemp(Spatial0), 0, 0);
|
|
AccelerationCollection.AddSubstructure(MoveTemp(Spatial1), 1, 0);
|
|
|
|
FSpatialAccelerationIdx SpatialIdx = { 0,0 };
|
|
SpatialTestHelper(AccelerationCollection, Boxes0.Get(), Box, SpatialIdx);
|
|
}
|
|
|
|
{
|
|
using BVType = TBoundingVolume<int32>;
|
|
FImplicitObjectPtr Box;
|
|
auto Boxes1 = BuildBoxes(Box);
|
|
FGeometryParticles EmptyBoxes;
|
|
|
|
auto Spatial0 = MakeUnique<TreeType>(MakeParticleView(&EmptyBoxes));
|
|
auto Spatial1 = MakeUnique<BVType>(MakeParticleView(Boxes1.Get()));
|
|
|
|
TSpatialAccelerationCollection<TreeType, BVType> AccelerationCollection;
|
|
AccelerationCollection.AddSubstructure(MoveTemp(Spatial0), 0, 0);
|
|
AccelerationCollection.AddSubstructure(MoveTemp(Spatial1), 1, 0);
|
|
|
|
FSpatialAccelerationIdx SpatialIdx = { 1,0 };
|
|
SpatialTestHelper(AccelerationCollection, Boxes1.Get(), Box, SpatialIdx);
|
|
}
|
|
}
|
|
|
|
// Verify we don't generate a NaN or invalid bounds if we build BoundingVolume with particles that have no bounds.
|
|
void BoundingVolumeNoBoundsTest()
|
|
{
|
|
FImplicitObjectPtr Box( new TBox<FReal, 3>(FVec3(0, 0, 0), FVec3(100)));
|
|
auto Boxes = MakeUnique<FGeometryParticles>();
|
|
|
|
Boxes->AddParticles(1);
|
|
|
|
// Construct a particle and set HasBounds to false.
|
|
int32 Idx = 0;
|
|
Boxes->SetX(Idx, FVec3(0));
|
|
Boxes->SetR(Idx, FRotation3::Identity);
|
|
Boxes->SetGeometry(Idx, Box);
|
|
|
|
// Tell BV we have no bounds, this used to cause issues.
|
|
Boxes->HasBounds(Idx) = false;
|
|
|
|
// Make Bounding Volume with only particles that have no bounds.
|
|
auto Spatial1 = MakeUnique<TBoundingVolume<int32>>(MakeParticleView(Boxes.Get()));
|
|
|
|
EXPECT_EQ(Spatial1->GetBounds().Min().ContainsNaN(), false);
|
|
EXPECT_EQ(Spatial1->GetBounds().Max().ContainsNaN(), false);
|
|
EXPECT_EQ(Spatial1->GetBounds().Extents().ContainsNaN(), false);
|
|
}
|
|
|
|
|
|
void SpatialAccelerationDirtyAndGlobalQueryStrestTest()
|
|
{
|
|
using AABBTreeType = TAABBTree<FAccelerationStructureHandle, TAABBTreeLeafArray<FAccelerationStructureHandle>>;
|
|
|
|
// Construct 100000 Particles
|
|
const int32 NumRows = 100;
|
|
const int32 NumCols = 100;
|
|
const int32 NumHeight = 10;
|
|
const int32 ParticleCount = NumRows * NumCols * NumHeight;
|
|
const FReal BoxSize = 100;
|
|
|
|
FParticleUniqueIndicesMultithreaded UniqueIndices;
|
|
FPBDRigidsSOAs Particles(UniqueIndices);
|
|
TArray<FPBDRigidParticleHandle*> ParticleHandles = Particles.CreateDynamicParticles(ParticleCount);
|
|
for (auto& Handle : ParticleHandles)
|
|
{
|
|
Handle->GTGeometryParticle() = FGeometryParticle::CreateParticle().Release();
|
|
}
|
|
const auto& ParticlesView = Particles.GetAllParticlesView();
|
|
|
|
// ensure these can't be filtered out.
|
|
FCollisionFilterData FilterData;
|
|
FilterData.Word0 = TNumericLimits<uint32>::Max();
|
|
FilterData.Word1 = TNumericLimits<uint32>::Max();
|
|
FilterData.Word2 = TNumericLimits<uint32>::Max();
|
|
FilterData.Word3 = TNumericLimits<uint32>::Max();
|
|
|
|
Chaos::FImplicitObjectPtr Box( new TBox<FReal, 3>(FVec3(0, 0, 0), FVec3(BoxSize, BoxSize, BoxSize)));
|
|
|
|
int32 Idx = 0;
|
|
for (int32 Height = 0; Height < NumHeight; ++Height)
|
|
{
|
|
for (int32 Row = 0; Row < NumRows; ++Row)
|
|
{
|
|
for (int32 Col = 0; Col < NumCols; ++Col)
|
|
{
|
|
FGeometryParticle* GTParticle = ParticleHandles[Idx]->GTGeometryParticle();
|
|
FPBDRigidParticleHandle* Handle = ParticleHandles[Idx];
|
|
|
|
Handle->SetX(FVec3(Col * BoxSize, Row * BoxSize, Height * BoxSize));
|
|
GTParticle->SetX(FVec3(Col * BoxSize, Row * BoxSize, Height * BoxSize));
|
|
Handle->SetR(FRotation3::Identity);
|
|
GTParticle->SetR(FRotation3::Identity);
|
|
Handle->SetGeometry(Box);
|
|
Handle->ShapesArray()[0]->SetQueryData(FilterData);
|
|
GTParticle->SetGeometry(Box);
|
|
GTParticle->ShapesArray()[0]->SetQueryData(FilterData);
|
|
Handle->SetUniqueIdx(FUniqueIdx(Idx));
|
|
GTParticle->SetUniqueIdx(FUniqueIdx(Idx));
|
|
++Idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 DirtyNum = 800;
|
|
int32 Queries = 500;
|
|
ensure(DirtyNum < ParticleCount);
|
|
|
|
// Construct tree
|
|
AABBTreeType Spatial(ParticlesView);
|
|
|
|
// Update DirtyNum elements, so they are pulled out of leaves.
|
|
for (int32 i = 0; i < DirtyNum; ++i)
|
|
{
|
|
FAccelerationStructureHandle Payload(ParticleHandles[i]->GTGeometryParticle());
|
|
FAABB3 Bounds = ParticleHandles[i]->WorldSpaceInflatedBounds();
|
|
Spatial.UpdateElement(Payload, Bounds, true);
|
|
}
|
|
|
|
// RAYCASTS
|
|
{
|
|
// Setup raycast params
|
|
const FVec3 Start(500, 500, 500);
|
|
const FVec3 Dir(1, 0, 0);
|
|
const FReal Length = 1000;
|
|
FStressTestVisitor Visitor;
|
|
|
|
// Measure raycasts
|
|
uint32 Cycles = 0.0;
|
|
for (int32 Query = 0; Query < Queries; ++Query)
|
|
{
|
|
uint32 StartTime = FPlatformTime::Cycles();
|
|
|
|
Spatial.Raycast(Start, Dir, Length, Visitor);
|
|
|
|
Cycles += FPlatformTime::Cycles() - StartTime;
|
|
}
|
|
|
|
float Milliseconds = FPlatformTime::ToMilliseconds(Cycles);
|
|
float AvgMicroseconds = (Milliseconds * 1000) / Queries;
|
|
|
|
UE_LOG(LogHeadlessChaos, Warning, TEXT("Raycast Test: Dirty Particles: %d, Queries: %d, Avg Query Time: %f(us), Total:%f(ms)"), DirtyNum, Queries, AvgMicroseconds, Milliseconds);
|
|
}
|
|
|
|
// SWEEPS
|
|
{
|
|
// Setup Sweep params
|
|
const FVec3 Start(500, 500, 500);
|
|
const FVec3 Dir(1, 0, 0);
|
|
const FReal Length = 1000;
|
|
const FVec3 HalfExtents(50, 50, 50);
|
|
FStressTestVisitor Visitor;
|
|
|
|
// Measure raycasts
|
|
uint32 Cycles = 0.0;
|
|
for (int32 Query = 0; Query < Queries; ++Query)
|
|
{
|
|
uint32 StartTime = FPlatformTime::Cycles();
|
|
|
|
Spatial.Sweep(Start, Dir, Length, HalfExtents, Visitor);
|
|
|
|
Cycles += FPlatformTime::Cycles() - StartTime;
|
|
}
|
|
|
|
float Milliseconds = FPlatformTime::ToMilliseconds(Cycles);
|
|
float AvgMicroseconds = (Milliseconds * 1000) / Queries;
|
|
|
|
UE_LOG(LogHeadlessChaos, Warning, TEXT("Sweep Test: Dirty Particles: %d, Queries: %d, Avg Query Time: %f(us), Total:%f(ms)"), DirtyNum, Queries, AvgMicroseconds, Milliseconds);
|
|
}
|
|
|
|
// OVERLAPS
|
|
{
|
|
FStressTestVisitor Visitor;
|
|
const FAABB3 QueryBounds(FVec3(-50, -50, -50), FVec3(50,50,50));
|
|
|
|
// Measure raycasts
|
|
uint32 Cycles = 0.0;
|
|
for (int32 Query = 0; Query < Queries; ++Query)
|
|
{
|
|
uint32 StartTime = FPlatformTime::Cycles();
|
|
|
|
Spatial.Overlap(QueryBounds, Visitor);
|
|
|
|
Cycles += FPlatformTime::Cycles() - StartTime;
|
|
}
|
|
|
|
float Milliseconds = FPlatformTime::ToMilliseconds(Cycles);
|
|
float AvgMicroseconds = (Milliseconds * 1000) / Queries;
|
|
|
|
UE_LOG(LogHeadlessChaos, Error, TEXT("Overlap Test: Dirty Particles: %d, Queries: %d, Avg Query Time: %f(us), Total:%f(ms)"), DirtyNum, Queries, AvgMicroseconds, Milliseconds);
|
|
}
|
|
}
|
|
|
|
}
|