// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionTestSimulationSolver.h" #include "GeometryCollection/GeometryCollectionTestFramework.h" #include "GeometryCollection/GeometryCollectionTestUtility.h" #include "GeometryCollection/GeometryCollection.h" #include "GeometryCollection/GeometryCollectionAlgo.h" #include "GeometryCollection/GeometryCollectionClusteringUtility.h" #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/TransformCollection.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "Field/FieldSystem.h" #include "Field/FieldSystemNodes.h" #include "GeometryCollectionProxyData.h" #include "GeometryCollection/GeometryCollectionSimulationTypes.h" #include "PhysicsProxy/GeometryCollectionPhysicsProxy.h" #include "PhysicsProxy/PhysicsProxies.h" #include "Chaos/ErrorReporter.h" #include "Chaos/Defines.h" #include "ChaosSolversModule.h" #include "PhysicsSolver.h" #include "EventsData.h" #include "HeadlessChaosTestUtility.h" #define SMALL_THRESHOLD 1e-4 namespace GeometryCollectionTest { using namespace ChaosTest; GTEST_TEST(AllTraits, GeometryCollection_Solver_AdvanceNoObjects) { FFramework UnitTest; UnitTest.Initialize(); UnitTest.Advance(); { // just makiung sure we did not crash in this test } } GTEST_TEST(AllTraits, GeometryCollection_Solver_AdvanceDisabledObjects) { CreationParameters Params; Params.Simulating = false; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); FFramework UnitTest; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); UnitTest.Advance(); { // never touched TManagedArray& RestTransform = Collection->RestCollection->Transform; EXPECT_LT(FMath::Abs(RestTransform[0].GetTranslation().Z), SMALL_THRESHOLD); // simulated EXPECT_EQ(Collection->DynamicCollection->GetNumTransforms(), 1); EXPECT_LT(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z), SMALL_THRESHOLD); } } GTEST_TEST(AllTraits, GeometryCollection_Solver_AdvanceDisabledClusteredObjects) { FFramework UnitTest; RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); UnitTest.AddSimulationObject(Floor); TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, -10, 10)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, 10, 10)), FVector(1.0))); EXPECT_EQ(RestCollection->Transform.Num(), 2); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); EXPECT_EQ(RestCollection->Transform.Num(), 3); RestCollection->Transform[2] = FTransform3f(FQuat4f::MakeFromEuler(FVector3f(90.f, 0, 0.)), FVector3f(0, 0, 40)); //GeometryCollectionAlgo::PrintParentHierarchy(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 1000.f }; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FVector3f StartingClusterPosition; FReal StartingRigidDistance; UnitTest.Solver->RegisterSimOneShotCallback([&]() { // Dynamic collection doesn't change the simulation type, RestCollection can be used instead TManagedArray& SimulationType = RestCollection->SimulationType; EXPECT_EQ(SimulationType[0],FGeometryCollection::ESimulationTypes::FST_Rigid); EXPECT_EQ(SimulationType[1],FGeometryCollection::ESimulationTypes::FST_Rigid); EXPECT_EQ(SimulationType[2],FGeometryCollection::ESimulationTypes::FST_Clustered); EXPECT_EQ(Collection->DynamicCollection->GetParent(0),2); EXPECT_EQ(Collection->DynamicCollection->GetParent(1),2); EXPECT_EQ(Collection->DynamicCollection->GetParent(2),-1); // Set the one cluster to disabled UnitTest.Solver->GetEvolution()->DisableParticle(Collection->PhysObject->GetSolverClusterHandle_Internal(0)); StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); EXPECT_LT(StartingRigidDistance - 20.0f,SMALL_THRESHOLD); StartingClusterPosition = Collection->DynamicCollection->GetTransform(2).GetTranslation(); }); FReal CurrentRigidDistance = 0.0; for (int Frame = 0; Frame < 10; Frame++) { UnitTest.Advance(); // Distance between gc cubes remains the same CurrentRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); EXPECT_LT(StartingRigidDistance-CurrentRigidDistance, SMALL_THRESHOLD); // Clustered particle doesn't move EXPECT_LT((StartingClusterPosition - Collection->DynamicCollection->GetTransform(2).GetTranslation()).Size(), SMALL_THRESHOLD); } } GTEST_TEST(AllTraits, DISABLED_GeometryCollection_Solver_ValidateReverseMapping) { // Todo: this test is missing code for InitCollections that seems to be no longer in the code base. // This makes it unclear how this test is setup /* TUniquePtr PhysicalMaterial = MakeUnique(); InitMaterialToZero(PhysicalMaterial.Get()); Chaos::FPBDRigidsSolver* Solver = FChaosSolversModule::GetModule()->CreateSolver(nullptr, ESolverFlags::Standalone); //Solver->SetHasFloor(true); Solver->SetEnabled(true); TArray > RestArray; TArray > DynamicArray; for (int32 i = 0; i < 10; i++) { TSharedPtr RestCollection = nullptr; TSharedPtr DynamicCollection = nullptr; //InitCollectionsParameters InitParams = { FTransform::Identity, FVector(1.0), nullptr, (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic }; //InitCollections(PhysicalMaterial, RestCollection, DynamicCollection, InitParams); FGeometryCollectionPhysicsProxy* PhysObject = RigidBodySetup(PhysicalMaterial, RestCollection, DynamicCollection); #if CHAOS_PARTICLEHANDLE_TODO Solver->RegisterObject(PhysObject); #endif //PhysObject->ActivateBodies(); RestArray.Add(RestCollection); DynamicArray.Add(DynamicCollection); } Solver->AdvanceSolverBy(1 / 24.); #if TODO_REIMPLEMENT_PHYSICS_PROXY_REVERSE_MAPPING const Chaos::TArrayCollectionArray & SolverMapping = Solver->GetPhysicsProxyReverseMapping(); const Chaos::TArrayCollectionArray & ParticleMapping = Solver->GetParticleIndexReverseMapping(); EXPECT_EQ(SolverMapping.Num(), 11); EXPECT_EQ(ParticleMapping.Num(), 11); EXPECT_EQ(ParticleMapping[0], INDEX_NONE); EXPECT_EQ(ParticleMapping[1], 0); EXPECT_EQ(SolverMapping[0].PhysicsProxy, nullptr); EXPECT_EQ(SolverMapping[0].Type, EPhysicsProxyType::NoneType); EXPECT_EQ(SolverMapping[5].PhysicsProxy != nullptr); EXPECT_EQ(SolverMapping[5].Type, EPhysicsProxyType::GeometryCollectionType); const TManagedArray & RigidBodyID = static_cast(SolverMapping[5].PhysicsProxy)-> GetGeometryDynamicCollection_PhysicsThread()->GetAttribute("RigidBodyID", FGeometryCollection::TransformGroup); EXPECT_EQ(RigidBodyID.Num(), 1); EXPECT_EQ(RigidBodyID[0], 5); #endif FChaosSolversModule::GetModule()->DestroySolver(Solver); */ } TSharedPtr CommonInit(int32 NumObjects, bool UseClusters) { TSharedPtr RestCollectionOut; for (int32 i = 0; i < NumObjects; i++) { TSharedPtr RestCollection; if (UseClusters) { RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, -10, 10)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, 10, 10)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, 0, 0)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(0, 0, 10)), FVector(1.0))); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); RestCollection->Transform[4] = FTransform3f(FQuat4f::MakeFromEuler(FVector3f(90.f, 0, 0.)), FVector3f(0, 0, 40)); } else { RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(i * 3, 0, 5)), FVector(1.0)); } if (i == 0) { RestCollectionOut = RestCollection; } else { RestCollectionOut->AppendGeometry(*RestCollection); } } return RestCollectionOut; } class FEventHarvester { public: FEventHarvester(Chaos::FPBDRigidsSolver* Solver) { Solver->GetEventManager()->template RegisterHandler(Chaos::EEventType::Collision, this, &FEventHarvester::HandleCollisionEvents); Solver->GetEventManager()->template RegisterHandler(Chaos::EEventType::Breaking, this, &FEventHarvester::HandleBreakingEvents); } void HandleCollisionEvents(const Chaos::FCollisionEventData& Events) { CollisionEventData = Events; } void HandleBreakingEvents(const Chaos::FBreakingEventData& Events) { BreakingEventData = Events; } Chaos::FCollisionEventData CollisionEventData; Chaos::FBreakingEventData BreakingEventData; }; GTEST_TEST(AllTraits, GeometryCollection_Solver_CollisionEventFilter) { FReal TestMassThreshold = 7.0; FGeometryCollectionWrapper* Collection[10]; for (int i=0; i<10; i++) { TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0)), FVector(0,0,0)), FVector(1.0)); CreationParameters Params; Params.RestCollection = RestCollection; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Box; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(i * 10.0f, 0.f, 10.f)); Params.Simulating = true; Collection[i] = TNewSimulationObject::Init(Params)->template As(); } RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); FFramework UnitTest; for (int i=0; i<10; i++) { TManagedArray& Mass = Collection[i]->RestCollection->template ModifyAttribute("Mass", FTransformCollection::TransformGroup); Mass[0] = i + 1; UnitTest.AddSimulationObject(Collection[i]); } UnitTest.AddSimulationObject(Floor); UnitTest.Initialize(); // setup collision filter FSolverCollisionFilterSettings CollisionFilterSettings; CollisionFilterSettings.FilterEnabled = true; CollisionFilterSettings.MinImpulse = 0; CollisionFilterSettings.MinMass = TestMassThreshold; CollisionFilterSettings.MinSpeed = 0; UnitTest.Solver->SetGenerateCollisionData(true); UnitTest.Solver->SetCollisionFilterSettings(CollisionFilterSettings); FEventHarvester Events(UnitTest.Solver); bool Impact = false; for(int LoopCount=0; LoopCount<10; LoopCount++) { // Events data on physics thread is appended until the game thread has had a chance to Tick & read it UnitTest.Advance(); const auto& AllCollisionsArray = Events.CollisionEventData.CollisionData.AllCollisionsArray; Impact = (AllCollisionsArray.Num() > 0); if (Impact) { // any objects with a mass of less than 6 are removed from returned collision data EXPECT_LE(AllCollisionsArray.Num(), 4); for (const auto& Collision : AllCollisionsArray) { EXPECT_GE(Collision.Mass1, TestMassThreshold); EXPECT_EQ(Collision.Mass2, 0.0f); EXPECT_LT(Collision.Velocity2.Z, SMALL_NUMBER); } break; } } EXPECT_EQ(Impact, true); } GTEST_TEST(AllTraits, GeometryCollection_Solver_BreakingEventFilter) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_OnePartent_FourBodies(FVector(0)); CreationParameters Params; Params.RootTransform.SetLocation(FVector(0, 0, 20)); Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_LevelSet; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 1.0 }; Params.MaxClusterLevel = 1000; Params.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; Params.ClusterGroupIndex = 0; RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); UnitTest.AddSimulationObject(Floor); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); Collection->PhysObject->SetCollisionParticlesPerObjectFraction(1.0); UnitTest.Initialize(); TArray*> ParticleHandles; FReal TestMass = 7.0; UnitTest.Solver->RegisterSimOneShotCallback([&]() { // setup breaking filter FSolverBreakingFilterSettings BreakingFilterSettings; BreakingFilterSettings.FilterEnabled = true; BreakingFilterSettings.MinMass = TestMass; BreakingFilterSettings.MinSpeed = 0; BreakingFilterSettings.MinVolume = 0; UnitTest.Solver->SetGenerateBreakingData(true); UnitTest.Solver->SetBreakingFilterSettings(BreakingFilterSettings); ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), }; ParticleHandles[0]->SetM(TestMass + 1.0f); ParticleHandles[1]->SetM(TestMass - 1.0f); ParticleHandles[2]->SetM(TestMass - 2.0f); ParticleHandles[3]->SetM(TestMass + 2.0f); }); FEventHarvester Events(UnitTest.Solver); bool Impact = false; for(int Loop=0; Loop<50; Loop++) { // Events data on physics thread is appended until the game thread has had a chance to Tick & read it UnitTest.Advance(); const auto& AllBreakingsArray = Events.BreakingEventData.BreakingData.AllBreakingsArray; Impact = (AllBreakingsArray.Num() > 0); if(Impact) { EXPECT_EQ(ParticleHandles[0]->Disabled(),false); // piece1 active 6 mass EXPECT_EQ(ParticleHandles[1]->Disabled(),false); // piece2 active 0.5 mass EXPECT_EQ(ParticleHandles[2]->Disabled(),false); // piece3 active 0.5 mass EXPECT_EQ(ParticleHandles[3]->Disabled(),false); // cluster active 7 mass EXPECT_EQ(ParticleHandles[4]->Disabled(),true); // cluster // breaking data EXPECT_EQ(AllBreakingsArray.Num(),2); // 2 pieces filtered out of 4 EXPECT_LT(AllBreakingsArray[0].Mass - (TestMass + 2.0f),SMALL_THRESHOLD); EXPECT_LT(AllBreakingsArray[1].Mass - (TestMass + 1.0f),SMALL_THRESHOLD); break; } } } }