Files
UnrealEngine/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestIgnoreManager.cpp
2025-05-18 13:04:45 +08:00

264 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Chaos/ContactModification.h"
#include "ChaosSolversModule.h"
#include "HeadlessChaosTestUtility.h"
#include "PBDRigidsSolver.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "Algo/Count.h"
namespace ChaosTest
{
using namespace Chaos;
class FCollisionReportingCallback : public TSimCallbackObject<FSimCallbackNoInput, FSimCallbackNoOutput, Chaos::ESimCallbackOptions::Presimulate | Chaos::ESimCallbackOptions::ContactModification>
{
public:
TArray<TTuple<FUniqueIdx, FUniqueIdx>> CollisionPairs;
private:
virtual void OnPreSimulate_Internal() override {}
virtual void OnContactModification_Internal(Chaos::FCollisionContactModifier& Modifier) override
{
for(FContactPairModifier& PairModifier : Modifier)
{
TVec2<FGeometryParticleHandle*> Particles = PairModifier.GetParticlePair();
if(Particles[0] && Particles[1])
{
FUniqueIdx Ids[2] = {Particles[0]->UniqueIdx(), Particles[1]->UniqueIdx()};
if(Ids[1] < Ids[0])
{
Swap(Ids[0], Ids[1]);
}
CollisionPairs.AddUnique({ Ids[0], Ids[1] });
}
}
}
};
void FlushSolver(FPBDRigidsSolver* InSolver)
{
InSolver->AdvanceAndDispatch_External(0);
InSolver->WaitOnPendingTasks_External();
// Populate the spacial acceleration
FPBDRigidsSolver::FPBDRigidsEvolution* Evolution = InSolver->GetEvolution();
if(Evolution)
{
Evolution->FlushSpatialAcceleration();
}
}
void RunSolver(FPBDRigidsSolver* InSolver, float InDt, int32 NumSteps)
{
for(int Step = 0; Step < NumSteps; ++Step)
{
InSolver->AdvanceAndDispatch_External(InDt);
InSolver->UpdateGameThreadStructures();
}
}
GTEST_TEST(AllTraits, IgnoreManager_IgnoresCollisions)
{
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
auto* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/-1);
InitSolverSettings(Solver);
Solver->SetThreadingMode_External(EThreadingModeTemp::SingleThread);
// create a static floor and two boxes falling onto it.
// One box has collisions ignored and should fall through, one should collide.
// simulated cube with downward velocity,should collide with floor and not fall through.
FSingleParticlePhysicsProxy* CollidingCubeProxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
FRigidBodyHandle_External& CollidingCubeParticle = CollidingCubeProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr CollidingCubeGeom(new TBox<FReal, 3>(FVec3(-100), FVec3(100)));
CollidingCubeParticle.SetGeometry(CollidingCubeGeom);
Solver->RegisterObject(CollidingCubeProxy);
CollidingCubeParticle.SetGravityEnabled(false);
CollidingCubeParticle.SetV(FVec3(0, 0, -100));
CollidingCubeParticle.SetX(FVec3(200, 0, 500));
SetCubeInertiaTensor(CollidingCubeParticle, /*Dimension=*/200, /*Mass=*/1);
ChaosTest::SetParticleSimDataToCollide({ CollidingCubeProxy->GetParticle_LowLevel() });
// Simulated cube with downward velocity, collision ignore disables collision with floor, should fall through.
FSingleParticlePhysicsProxy* NonCollidableProxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
FRigidBodyHandle_External& NonCollidableParticle = NonCollidableProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr NonCollidableCubeGeom(new TBox<FReal, 3>(FVec3(-100), FVec3(100)));
NonCollidableParticle.SetGeometry(NonCollidableCubeGeom);
Solver->RegisterObject(NonCollidableProxy);
NonCollidableParticle.SetGravityEnabled(false);
NonCollidableParticle.SetV(FVec3(0, 0, -100));
NonCollidableParticle.SetX(FVec3(-200, 0, 500));
SetCubeInertiaTensor(NonCollidableParticle, /*Dimension=*/200, /*Mass=*/1);
ChaosTest::SetParticleSimDataToCollide({ NonCollidableProxy->GetParticle_LowLevel() });
// static floor at origin, occupying Z = [-100,0]
FSingleParticlePhysicsProxy* FloorProxy = FSingleParticlePhysicsProxy::Create(Chaos::FGeometryParticle::CreateParticle());
FRigidBodyHandle_External& FloorParticle = FloorProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr FloorGeom(new TBox<FReal, 3>(FVec3(-500, -500, -100), FVec3(500, 500, 0)));
FloorParticle.SetGeometry(FloorGeom);
Solver->RegisterObject(FloorProxy);
FloorParticle.SetX(FVec3(0, 0, 0));
ChaosTest::SetParticleSimDataToCollide({ FloorProxy->GetParticle_LowLevel() });
FCollisionReportingCallback* Callback = Solver->CreateAndRegisterSimCallbackObject_External<FCollisionReportingCallback>();
// Named Ids for the particles
const FUniqueIdx CollidableId = CollidingCubeParticle.UniqueIdx();
const FUniqueIdx NonCollidableId = NonCollidableParticle.UniqueIdx();
const FUniqueIdx FloorId = FloorParticle.UniqueIdx();
// Flush the solver
FlushSolver(Solver);
// Set up the ignore manager
FIgnoreCollisionManager& IgnoreManager = Solver->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager();
IgnoreManager.AddIgnoreCollisions(NonCollidableProxy->GetHandle_LowLevel(), FloorProxy->GetHandle_LowLevel());
RunSolver(Solver, 1.0f, 10);
auto CountPred = [Callback](FUniqueIdx Idx) -> int32
{
return Algo::CountIf(Callback->CollisionPairs, [Idx](const TTuple<FUniqueIdx, FUniqueIdx>& Hit)
{
return Hit.Get<0>() == Idx || Hit.Get<1>() == Idx;
});
};
TMap<FUniqueIdx, int32> Hits;
Hits.Add({ CollidableId, CountPred(CollidableId) });
Hits.Add({ NonCollidableId, CountPred(NonCollidableId) });
Hits.Add({ FloorId, CountPred(FloorId) });
EXPECT_EQ(Hits[CollidableId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[FloorId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[NonCollidableId], 0); // Non collidable ignored all collisions with the floor
Solver->UnregisterAndFreeSimCallbackObject_External(Callback);
Solver->UnregisterObject(CollidingCubeProxy);
Solver->UnregisterObject(NonCollidableProxy);
Solver->UnregisterObject(FloorProxy);
Module->DestroySolver(Solver);
}
GTEST_TEST(AllTraits, IgnoreManager_IgnoresCollisions_MultipleSources)
{
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
auto* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/-1);
InitSolverSettings(Solver);
Solver->SetThreadingMode_External(EThreadingModeTemp::SingleThread);
// create a static floor and two boxes falling onto it.
// One box has collisions ignored and should fall through, one should collide.
// simulated cube with downward velocity,should collide with floor and not fall through.
FSingleParticlePhysicsProxy* CollidingCubeProxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
FRigidBodyHandle_External& CollidingCubeParticle = CollidingCubeProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr CollidingCubeGeom(new TBox<FReal, 3>(FVec3(-100), FVec3(100)));
CollidingCubeParticle.SetGeometry(CollidingCubeGeom);
Solver->RegisterObject(CollidingCubeProxy);
CollidingCubeParticle.SetGravityEnabled(false);
CollidingCubeParticle.SetV(FVec3(0, 0, -100));
CollidingCubeParticle.SetX(FVec3(200, 0, 500));
SetCubeInertiaTensor(CollidingCubeParticle, /*Dimension=*/200, /*Mass=*/1);
ChaosTest::SetParticleSimDataToCollide({ CollidingCubeProxy->GetParticle_LowLevel() });
// Simulated cube with downward velocity, collision ignore disables collision with floor, should fall through.
FSingleParticlePhysicsProxy* NonCollidableProxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
FRigidBodyHandle_External& NonCollidableParticle = NonCollidableProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr NonCollidableCubeGeom(new TBox<FReal, 3>(FVec3(-100), FVec3(100)));
NonCollidableParticle.SetGeometry(NonCollidableCubeGeom);
Solver->RegisterObject(NonCollidableProxy);
NonCollidableParticle.SetGravityEnabled(false);
NonCollidableParticle.SetV(FVec3(0, 0, -10));
NonCollidableParticle.SetX(FVec3(-200, 0, 110));
SetCubeInertiaTensor(NonCollidableParticle, /*Dimension=*/200, /*Mass=*/1);
ChaosTest::SetParticleSimDataToCollide({ NonCollidableProxy->GetParticle_LowLevel() });
// static floor at origin, occupying Z = [-100,0]
FSingleParticlePhysicsProxy* FloorProxy = FSingleParticlePhysicsProxy::Create(Chaos::FGeometryParticle::CreateParticle());
FRigidBodyHandle_External& FloorParticle = FloorProxy->GetGameThreadAPI();
Chaos::FImplicitObjectPtr FloorGeom(new TBox<FReal, 3>(FVec3(-500, -500, -100), FVec3(500, 500, 0)));
FloorParticle.SetGeometry(FloorGeom);
Solver->RegisterObject(FloorProxy);
FloorParticle.SetX(FVec3(0, 0, 0));
ChaosTest::SetParticleSimDataToCollide({ FloorProxy->GetParticle_LowLevel() });
FCollisionReportingCallback* Callback = Solver->CreateAndRegisterSimCallbackObject_External<FCollisionReportingCallback>();
// Named Ids for the particles
const FUniqueIdx CollidableId = CollidingCubeParticle.UniqueIdx();
const FUniqueIdx NonCollidableId = NonCollidableParticle.UniqueIdx();
const FUniqueIdx FloorId = FloorParticle.UniqueIdx();
// Flush the solver
FlushSolver(Solver);
// Set up the ignore manager (Twice as if two systems requested it)
FIgnoreCollisionManager& IgnoreManager = Solver->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager();
IgnoreManager.AddIgnoreCollisions(NonCollidableProxy->GetHandle_LowLevel(), FloorProxy->GetHandle_LowLevel());
IgnoreManager.AddIgnoreCollisions(NonCollidableProxy->GetHandle_LowLevel(), FloorProxy->GetHandle_LowLevel());
const float Dt = 1.0f;
const int32 Steps = 10;
for(int Step = 0; Step < Steps; ++Step)
{
Solver->AdvanceAndDispatch_External(Dt);
Solver->UpdateGameThreadStructures();
}
auto CountPred = [Callback](FUniqueIdx Idx) -> int32
{
return Algo::CountIf(Callback->CollisionPairs, [Idx](const TTuple<FUniqueIdx, FUniqueIdx>& Hit)
{
return Hit.Get<0>() == Idx || Hit.Get<1>() == Idx;
});
};
TMap<FUniqueIdx, int32> Hits;
Hits.Add({ CollidableId, CountPred(CollidableId) });
Hits.Add({ NonCollidableId, CountPred(NonCollidableId) });
Hits.Add({ FloorId, CountPred(FloorId) });
EXPECT_EQ(Hits[CollidableId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[FloorId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[NonCollidableId], 0); // Non collidable ignored all collisions with the floor
// Remove one source
IgnoreManager.RemoveIgnoreCollisions(NonCollidableProxy->GetHandle_LowLevel(), FloorProxy->GetHandle_LowLevel());
Callback->CollisionPairs.Reset();
RunSolver(Solver, 1.0f, 10);
Hits[CollidableId] = CountPred(CollidableId);
Hits[NonCollidableId] = CountPred(NonCollidableId);
Hits[FloorId] = CountPred(FloorId);
EXPECT_EQ(Hits[CollidableId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[FloorId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[NonCollidableId], 0); // Non collidable ignored all collisions with the floor
// Remove final source
IgnoreManager.RemoveIgnoreCollisions(NonCollidableProxy->GetHandle_LowLevel(), FloorProxy->GetHandle_LowLevel());
Callback->CollisionPairs.Reset();
RunSolver(Solver, 1.0f, 10);
Hits[CollidableId] = CountPred(CollidableId);
Hits[NonCollidableId] = CountPred(NonCollidableId);
Hits[FloorId] = CountPred(FloorId);
EXPECT_EQ(Hits[CollidableId], 1); // Collidable hit the floor
EXPECT_EQ(Hits[FloorId], 2); // Collidable and NonCollidable hit the floor
EXPECT_EQ(Hits[NonCollidableId], 1); // Non collidable is now collidable and hits the floor
Solver->UnregisterAndFreeSimCallbackObject_External(Callback);
Solver->UnregisterObject(CollidingCubeProxy);
Solver->UnregisterObject(NonCollidableProxy);
Solver->UnregisterObject(FloorProxy);
Module->DestroySolver(Solver);
}
}