// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionTestClustering.h" #include "GeometryCollection/GeometryCollectionTestFramework.h" #include "GeometryCollection/GeometryCollectionTestUtility.h" #include "GeometryCollection/GeometryCollection.h" #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/GeometryCollectionAlgo.h" #include "GeometryCollection/GeometryCollectionClusteringUtility.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 "PhysicsProxy/PhysicsProxies.h" #include "Chaos/ErrorReporter.h" #include "ChaosSolversModule.h" #include "PhysicsSolver.h" #include "Chaos/PBDRigidClustering.h" #include "HAL/IConsoleManager.h" #include "HeadlessChaosTestUtility.h" DEFINE_LOG_CATEGORY_STATIC(GCTCL_Log, Verbose, All); // #TODO Lots of duplication in here, anyone making solver or object changes // has to go and fix up so many callsites here and they're all pretty much // Identical. The similar code should be pulled out namespace GeometryCollectionTest { using namespace ChaosTest; bool ClusterMapContains(const Chaos::FRigidClustering::FClusterMap& ClusterMap, const FPBDRigidParticleHandle* InKey, TArray Elements) { if (ClusterMap.Num()) { if (const Chaos::TPBDRigidClusteredParticleHandle* Key = InKey->CastToClustered()) { if (ClusterMap.Contains(Key)) { if (ClusterMap[Key].Num() == Elements.Num()) { for (FPBDRigidParticleHandle* Element : Elements) { if (!ClusterMap[Key].Contains(Element)) { return false; } } return true; } } } } return false; } GTEST_TEST(AllTraits,GeometryCollection_RigidBodies_ClusterTest_SingleLevelNonBreaking) { 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.0, 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(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; TManagedArray& Active = Collection->DynamicCollection->Active; EXPECT_FALSE(Active[0]); EXPECT_FALSE(Active[1]); EXPECT_TRUE(Active[2]); // only the root cluster should be active when using clustering UnitTest.Advance(); EXPECT_FALSE(Active[0]); EXPECT_FALSE(Active[1]); EXPECT_TRUE(Active[2]); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto & ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetSolverClusterHandle_Internal(0), { Collection->PhysObject->GetParticle_Internal(0),Collection->PhysObject->GetParticle_Internal(1) })); FReal InitialZ = Collection->RestCollection->Transform[2].GetTranslation().Z; for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Advance(); EXPECT_FALSE(Active[0]); EXPECT_FALSE(Active[1]); EXPECT_TRUE(Active[2]); CurrentRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); EXPECT_LT(FMath::Abs(CurrentRigidDistance - StartingRigidDistance), SMALL_NUMBER); // two bodies under cluster maintain distance EXPECT_LT(Collection->DynamicCollection->GetTransform(2).GetTranslation().Z, InitialZ); // body should be falling and decreasing in Z } EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetSolverClusterHandle_Internal(0), { Collection->PhysObject->GetParticle_Internal(0),Collection->PhysObject->GetParticle_Internal(1) })); } GTEST_TEST(AllTraits, GeometryCollection_DynamicCollection_ChildrenAccess) { 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.0, 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(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; TManagedArray& Active = Collection->DynamicCollection->Active; EXPECT_FALSE(Active[0]); EXPECT_FALSE(Active[1]); EXPECT_TRUE(Active[2]); // only the root cluster should be active when using clustering UnitTest.Advance(); EXPECT_FALSE(Active[0]); EXPECT_FALSE(Active[1]); EXPECT_TRUE(Active[2]); int32 ChildCount[3] = {0, 0, 0}; for (int32 Index = 0; Index < 3; ++Index) { Collection->DynamicCollection->IterateThroughChildren(Index, [&](int32 ChildIndex) { ChildCount[Index]++; return true; }); } EXPECT_EQ(ChildCount[0], 0); EXPECT_EQ(ChildCount[1], 0); EXPECT_EQ(ChildCount[2], 2); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_DeactivateClusterParticle) { FFramework UnitTest; // 5 cube leaf nodes TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(20.f)),FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(30.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(40.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(50.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(60.f)), FVector(1.0))); // 4 mid-level cluster parents FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 5, { 4,3 }, true, false); // just validate at end of construction FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 6, { 5,2 }, true, false); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 7, { 6,1 }, true, false); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); FGeometryCollectionClusteringUtility::ValidateResults(RestCollection.Get()); //GeometryCollectionAlgo::PrintParentHierarchy(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 50.0, 50.0, 50.0, FLT_MAX }; Params.MaxClusterLevel = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto & ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4], ParticleHandles[3] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5], ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6], ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[7], ParticleHandles[0] })); TArray Conditions = { false,false }; TArray DisabledFlags; for (int Frame = 1; Frame < 4; Frame++) { UnitTest.Advance(); if (Frame == 2) { Clustering.DeactivateClusterParticle(ParticleHandles[8]); } DisabledFlags.Reset(); for (const auto* Handle : ParticleHandles) { DisabledFlags.Add(Handle->Disabled()); } if (Frame == 1) { if (DisabledFlags[0] == true && DisabledFlags[1] == true && DisabledFlags[2] == true && DisabledFlags[3] == true && DisabledFlags[4] == true && DisabledFlags[5] == true && DisabledFlags[6] == true && DisabledFlags[7] == true && DisabledFlags[8] == false) { Conditions[0] = true; } } else if (Frame == 2 || Frame == 3) { if (Conditions[0] == true) { if (DisabledFlags[0] == false && DisabledFlags[1] == true && DisabledFlags[2] == true && DisabledFlags[3] == true && DisabledFlags[4] == true && DisabledFlags[5] == true && DisabledFlags[6] == true && DisabledFlags[7] == false && DisabledFlags[8] == true) { Conditions[1] = true; EXPECT_TRUE(!ClusterMap.Contains(ParticleHandles[8])); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6], ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5], ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4], ParticleHandles[3] })); } } } } for (int i = 0; i < Conditions.Num(); i++) { EXPECT_TRUE(Conditions[i]); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_BreakClusterParticle) { FFramework UnitTest; // 5 cube leaf nodes TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(20.f)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(30.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(40.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(50.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(60.f)), FVector(1.0))); // 4 mid-level cluster parents FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 5, { 4,3 }, true, false); // just validate at end of construction FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 6, { 5,2 }, true, false); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 7, { 6,1 }, true, false); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); FGeometryCollectionClusteringUtility::ValidateResults(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 50.0, 50.0, 50.0, FLT_MAX }; Params.MaxClusterLevel = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4], ParticleHandles[3] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5], ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6], ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[7], ParticleHandles[0] })); TArray Conditions = { false,false }; TArray DisabledFlags; for (int Frame = 1; Frame < 4; Frame++) { UnitTest.Advance(); if (Frame == 2) { Clustering.SetExternalStrain(ParticleHandles[0], 50.0f); Clustering.BreakingModel(); } DisabledFlags.Reset(); for (const auto* Handle : ParticleHandles) { DisabledFlags.Add(Handle->Disabled()); } //UE_LOG(GCTCL_Log, Verbose, TEXT("FRAME : %d"), Frame); //for (int32 rdx = 0; rdx < (int32)Particles.Size(); rdx++) //{ // UE_LOG(GCTCL_Log, Verbose, TEXT("... ... ...Disabled[%d] : %d"), rdx, Particles.Disabled(rdx)); // UE_LOG(GCTCL_Log, Verbose, TEXT("... ... ... InvM[%d] : %f"), rdx, Particles.InvM(rdx)); //} if (Frame == 1) { if (DisabledFlags[0] == true && DisabledFlags[1] == true && DisabledFlags[2] == true && DisabledFlags[3] == true && DisabledFlags[4] == true && DisabledFlags[5] == true && DisabledFlags[6] == true && DisabledFlags[7] == true && DisabledFlags[8] == false) { Conditions[0] = true; } } else if (Frame == 2 || Frame == 3) { if (Conditions[0] == true) { if (DisabledFlags[0] == false && DisabledFlags[1] == true && DisabledFlags[2] == true && DisabledFlags[3] == true && DisabledFlags[4] == true && DisabledFlags[5] == true && DisabledFlags[6] == true && DisabledFlags[7] == false && DisabledFlags[8] == true) { Conditions[1] = true; EXPECT_TRUE(!ClusterMap.Contains(ParticleHandles[8])); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6], ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5], ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4], ParticleHandles[3] })); } } } } for (int i = 0; i < Conditions.Num(); i++) { EXPECT_TRUE(Conditions[i]); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_SingleLevelBreaking) { // // Test overview: // Create two 1cm cubes in a cluster arranged vertically and 20cm apart. // Position the cluster above the ground. // Wait until the cluster hits the ground. // Ensure that the cluster breaks and that the children have the correct states from then on. // FFramework UnitTest; UnitTest.AddSimulationObject(TNewSimulationObject::Init()->template As()); TSharedPtr RestCollection = CreateClusteredBody(FVector::ZeroVector); RestCollection->Transform[2] = FTransform3f(FQuat4f::MakeFromEuler(FVector3f(0., 90.f, 0.)), FVector3f(0, 0, 17)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 0.1f }; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); Collection->PhysObject->SetCollisionParticlesPerObjectFraction(1.0); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; UnitTest.Advance(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetSolverClusterHandle_Internal(0), { Collection->PhysObject->GetParticle_Internal(0),Collection->PhysObject->GetParticle_Internal(1) })); // Particles array contains the following: // 0: Box1 (top) // 1: Box2 (bottom) int32 BrokenFrame = INDEX_NONE; // 2: Box1+Box2 Cluster for (int Frame = 1; Frame < 20; Frame++) { UnitTest.Advance(); CurrentRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); if ((BrokenFrame == INDEX_NONE) && !Collection->PhysObject->GetSolverClusterHandle_Internal(2)->Disabled()) { // The two boxes are dropping to the ground as a cluster EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(0)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(1)->Disabled()); // The boxes are still separated by StartingRigidDistance EXPECT_LT(FMath::Abs(CurrentRigidDistance - StartingRigidDistance), 1e-4); } if ((BrokenFrame == INDEX_NONE) && Collection->PhysObject->GetParticle_Internal(2)->Disabled()) { // The cluster has just hit the ground and should have broken. EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(0)->Disabled()); EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(1)->Disabled()); EXPECT_EQ(ClusterMap.Num(), 0); BrokenFrame = Frame; } if ((BrokenFrame != INDEX_NONE) && (Frame > BrokenFrame + 1)) // +1 so that the boxes have a bit of time to move away from each other { // The boxes are now moving independently - the bottom one is on the ground and should be stopped. // The top one is still falling, so they should be closer together EXPECT_GT(FMath::Abs(CurrentRigidDistance - StartingRigidDistance), 1e-4); } } // Make sure it actually broke EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(0)->Disabled()); EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(1)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(2)->Disabled()); EXPECT_TRUE(BrokenFrame != INDEX_NONE); EXPECT_GT(FMath::Abs(CurrentRigidDistance - StartingRigidDistance), 1e-4); } // Wrap two boxes in a cluster (as a sphere), and then wrap that cluster in another cluster (as a sphere). // Drop the cluster onto the ground. The outer cluster will break, activating the inner cluster. // Then the inner cluster will break, activating the boxes. // Note: the inner cluster has a damage threshold of 0, so it breaks even though it is resting on // the ground with no velocity after the outer cluster breaks. GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_NestedCluster) { 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)); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 3, { 2 }, true); EXPECT_EQ(RestCollection->Transform.Num(), 4); RestCollection->Transform[3] = FTransform3f(FQuat4f::MakeFromEuler(FVector3f(0.f, 0, 0.)), FVector4f(0, 0, 10)); //GeometryCollectionAlgo::PrintParentHierarchy(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 0.1f, 0.0f }; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; UnitTest.Advance(); // Particle Handles array contains // [0]: GeometryCollection Sphere0 at 0,10,60 // [1]: GeometryCollection Sphere1 at 0,10,40 // [2]: GeometryCollection Cluster0 of Sphere0 and Sphere1 at 0,10,50 (root rotated 90deg about X) // [3]: GeometryCollection Cluster1 of Cluster0 at 0,10,50 auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetParticle_Internal(2), { Collection->PhysObject->GetParticle_Internal(0),Collection->PhysObject->GetParticle_Internal(1) })); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetParticle_Internal(3), { Collection->PhysObject->GetParticle_Internal(2) })); TArray Conditions = {false,false,false}; for (int Frame = 1; Frame < 100; Frame++) { UnitTest.Advance(); CurrentRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); if (Conditions[0]==false) { if ( Collection->PhysObject->GetParticle_Internal(0)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(1)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(2)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(3)->Disabled() == false) { // Only the outer Cluster1 is active. // This is the initial condition Conditions[0] = true; } } else if (Conditions[0]==true && Conditions[1] == false) { if ( Collection->PhysObject->GetParticle_Internal(0)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(1)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(2)->Disabled() == false && Collection->PhysObject->GetParticle_Internal(3)->Disabled() == true) { // Cluster1 is now disabled, and Cluster0 was activated. // This happens when Cluster1 collides with the floor Conditions[1] = true; EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection->PhysObject->GetParticle_Internal(2), { Collection->PhysObject->GetParticle_Internal(0),Collection->PhysObject->GetParticle_Internal(1) })); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(!ClusterMap.Contains(Collection->PhysObject->GetParticle_Internal(3))); } } else if (Conditions[1] == true && Conditions[2] == false) { if ( Collection->PhysObject->GetParticle_Internal(0)->Disabled() == false && Collection->PhysObject->GetParticle_Internal(1)->Disabled() == false && Collection->PhysObject->GetParticle_Internal(2)->Disabled() == true && Collection->PhysObject->GetParticle_Internal(3)->Disabled() == true) { // Cluster0 is now disabled because it had a damage threshold of 0 // and the boxes should now be active. Conditions[2] = true; EXPECT_EQ(ClusterMap.Num(), 0); } } } for (int i = 0; i < Conditions.Num(); i++) { EXPECT_TRUE(Conditions[i]); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_NestedCluster_NonIdentityMassToLocal) { // Advance and release each cluster, everything is kinematic, so the output transforms should never change. // This tests the transforms in BufferPhysicsResults, validating that MassToLocal, ChildToParent, and X,P // will properly map back into the GeometryCollections animation transform hierarchy. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_TwoParents_TwoBodiesB(FVector(0, 0, 20)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.MaxClusterLevel = 1; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection1 = TNewSimulationObject::Init(Params)->template As(); TSharedPtr DynamicCollection1 = Collection1->DynamicCollection; DynamicCollection1->ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; DynamicCollection1->ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[0] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; UnitTest.AddSimulationObject(Collection1); UnitTest.Initialize(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); TArray Collection1_InitialTM; GeometryCollectionAlgo::GlobalMatrices(Collection1->RestCollection->Transform, Collection1->RestCollection->Parent, Collection1_InitialTM); const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { EXPECT_EQ(SovlerParticleHandles.Size(),4); EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1->PhysObject->GetParticle_Internal(2), { Collection1->PhysObject->GetParticle_Internal(1),Collection1->PhysObject->GetParticle_Internal(0) })); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1->PhysObject->GetParticle_Internal(3), { Collection1->PhysObject->GetParticle_Internal(2) })); }); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1->PhysObject->GetParticle_Internal(2), { Collection1->PhysObject->GetParticle_Internal(1),Collection1->PhysObject->GetParticle_Internal(0) })); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1->PhysObject->GetParticle_Internal(3), { Collection1->PhysObject->GetParticle_Internal(2) })); TArray Collection1_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1.Get(), Collection1_PreReleaseTM); for (int Idx = 0; Idx < Collection1_PreReleaseTM.Num(); Idx++) { EXPECT_TRUE( (Collection1_PreReleaseTM[Idx].GetTranslation()-Collection1_InitialTM[Idx].GetTranslation()).Size()GetEvolution()->GetRigidClustering().DeactivateClusterParticle(Collection1->PhysObject->GetParticle_Internal(3)); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1->PhysObject->GetParticle_Internal(2), { Collection1->PhysObject->GetParticle_Internal(1),Collection1->PhysObject->GetParticle_Internal(0) })); TArray Collection1_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1.Get(), Collection1_PostReleaseTM); for (int Idx = 0; Idx < Collection1_PostReleaseTM.Num(); Idx++) { EXPECT_TRUE((Collection1_PostReleaseTM[Idx].GetTranslation() - Collection1_InitialTM[Idx].GetTranslation()).Size() < KINDA_SMALL_NUMBER); } UnitTest.Solver->GetEvolution()->GetRigidClustering().DeactivateClusterParticle(Collection1->PhysObject->GetParticle_Internal(2)); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 0); TArray Collection1_PostRelease2TM; GeometryCollectionAlgo::Private::GlobalMatrices( *DynamicCollection1, Collection1_PostRelease2TM); for (int Idx = 0; Idx < Collection1_PostRelease2TM.Num(); Idx++) { EXPECT_TRUE((Collection1_PostRelease2TM[Idx].GetTranslation() - Collection1_InitialTM[Idx].GetTranslation()).Size() < KINDA_SMALL_NUMBER); } } GTEST_TEST(AllTraits, DISASBLED_GeometryCollection_RigidBodies_ClusterTest_NestedCluster_MultiStrain) { FFramework UnitTest; RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); UnitTest.AddSimulationObject(Floor); TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(20.f)),FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(30.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(40.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(50.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(60.f)), FVector(1.0))); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 4, { 2, 3 }, true, true); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 2, { 1, 0 }, true, true); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 1, { 3, 0 }, true, true); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 30.0, 30.0, 30, FLT_MAX }; // basically a stand-in for a 'component' FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(), CurrentRigidDistance = 0.f; TArray Conditions = { false,false,false,false }; UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[6],ParticleHandles[4] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[7],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[8],ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[3],ParticleHandles[0] })); for (int Frame = 1; Frame < 40; Frame++) { UnitTest.Advance(); CurrentRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); if (Conditions[0] == false) { if (ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == false && // root ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { Conditions[0] = true; } } // Root cluster broken, check for activated children else if (Conditions[0] == true && Conditions[1] == false) { if ( ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == false && ParticleHandles[5]->Disabled() == true && // root, now disabled ParticleHandles[6]->Disabled() == false && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { Conditions[1] = true; EXPECT_EQ(ClusterMap.Num(), 3); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[3],ParticleHandles[0] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[1],ParticleHandles[8] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[7],ParticleHandles[2] })); } } else if (Conditions[1] == true && Conditions[2] == false) { if ( ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == false && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == false && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == false && ParticleHandles[8]->Disabled() == true) { Conditions[2] = true; EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[3],ParticleHandles[0] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[1],ParticleHandles[8] })); } } else if (Conditions[2] == true && Conditions[3] == false) { if ( ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == false && ParticleHandles[2]->Disabled() == false && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == false && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == false) { Conditions[3] = true; EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[3],ParticleHandles[0] })); } } else if (Conditions[3] == true) { // fLT_MAX strain so last cluster should never break. EXPECT_TRUE(ParticleHandles[0]->Disabled() == true); EXPECT_TRUE(ParticleHandles[1]->Disabled() == false); EXPECT_TRUE(ParticleHandles[2]->Disabled() == false); EXPECT_TRUE(ParticleHandles[3]->Disabled() == true); EXPECT_TRUE(ParticleHandles[4]->Disabled() == false); EXPECT_TRUE(ParticleHandles[5]->Disabled() == true); EXPECT_TRUE(ParticleHandles[6]->Disabled() == true); EXPECT_TRUE(ParticleHandles[7]->Disabled() == true); EXPECT_TRUE(ParticleHandles[8]->Disabled() == false); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[3],ParticleHandles[0] })); } } for (int i = 0; i < Conditions.Num(); i++) { //EXPECT_TRUE(Conditions[i]); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_KinematicAnchor) { // Test : Set one element kinematic. When the cluster breaks the elements that do not contain the kinematic // rigid body should be dynamic, while the clusters that contain the kinematic body should remain // kinematic. FFramework UnitTest; TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(20.f)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(30.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(40.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(50.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(60.f)), FVector(1.0))); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 5, { 4,3 }, true, false); // just validate at end of construction FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 6, { 5,2 }, true, false); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 7, { 6,1 }, true, false); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); FGeometryCollectionClusteringUtility::ValidateResults(RestCollection.Get()); //GeometryCollectionAlgo::PrintParentHierarchy(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 50.0, 50.0, 50.0, FLT_MAX }; Params.MaxClusterLevel = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); Collection->DynamicCollection->template ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); FReal CurrentRigidDistance = 0; // Staged conditions // Initial state should set up the hierarchy correctly, leaving correct disabled flags on frame 1 bool bValidInitialState = false; // After releasing particle 8, the states should be updated on frame 2 bool bParticle8SucessfulRelease = false; // After releasing particle 8, the states should be updated on frame 4 bool bParticle7SucessfulRelease = false; // After simulating post-release the states should match frame 4 bool bValidFinalActiveState = false; // Tick once to fush commands UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), }; TArray*> ClusterHandles = { Collection->PhysObject->GetSolverClusterHandle_Internal(0), Collection->PhysObject->GetSolverClusterHandle_Internal(1), Collection->PhysObject->GetSolverClusterHandle_Internal(2), Collection->PhysObject->GetSolverClusterHandle_Internal(3), Collection->PhysObject->GetSolverClusterHandle_Internal(4), Collection->PhysObject->GetSolverClusterHandle_Internal(5), Collection->PhysObject->GetSolverClusterHandle_Internal(6), Collection->PhysObject->GetSolverClusterHandle_Internal(7), Collection->PhysObject->GetSolverClusterHandle_Internal(8), }; FRigidClustering& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const FRigidClustering::FClusterMap& ClusterMap = Clustering.GetChildrenMap(); // Verify that the parent-child relationship is reflected in the clustering hierarchy // Tree should be: // // 8 // / \ // 7 0 // / \ // 6 1 // / \ // 5 2 // / \ // 4 3 // // Entire cluster is kinematic due to particle 1 // EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6],ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[7],ParticleHandles[0] })); // Storage for positions for particles 0, 1, 6 for testing assumptions FVector Ref0(0.0); FVector Ref1(0.0); FVector Ref6(0.0); for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Advance(); // On frames 2 and 4, deactivate particles 8 and 7, releasing their children (7,0 then 6,1) if (Frame == 2) { UnitTest.Solver->GetEvolution()->GetRigidClustering().DeactivateClusterParticle(ParticleHandles[8]); } if (Frame == 4) { UnitTest.Solver->GetEvolution()->GetRigidClustering().DeactivateClusterParticle(ParticleHandles[7]); } // Verify that the kinematic particle remains kinematic (InvMass == 0.0) // and the the dynamic particles have a non-zero inv mass EXPECT_NE(ParticleHandles[0]->InvM(), 0); // dynamic rigid EXPECT_EQ(ParticleHandles[1]->InvM(), 0); // kinematic rigid EXPECT_NE(ParticleHandles[2]->InvM(), 0); // dynamic rigid EXPECT_NE(ParticleHandles[3]->InvM(), 0); // dynamic rigid EXPECT_NE(ParticleHandles[4]->InvM(), 0); // dynamic rigid EXPECT_NE(ParticleHandles[5]->InvM(), 0); // dynamic rigid EXPECT_NE(ParticleHandles[6]->InvM(), 0); // dynamic cluster if (!bValidInitialState && Frame == 1) { if (ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == false) { bValidInitialState = true; Ref0 = ParticleHandles[0]->GetX(); Ref1 = ParticleHandles[1]->GetX(); Ref6 = ParticleHandles[6]->GetX(); // Test kinematic particles have valid (0.0) inverse mass and have the kinematic object state set EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster } } else if (bValidInitialState && !bParticle8SucessfulRelease && Frame == 2) { if (ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == false && ParticleHandles[8]->Disabled() == true) { bParticle8SucessfulRelease = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); FVector X00 = Ref0; FVector X11 = Ref1; FVector X66 = Ref6; check(!X0.ContainsNaN()); check(!Ref0.ContainsNaN()); check(FMath::IsFinite(X0.Size())); check(FMath::IsFinite(Ref0.Size())); check(FMath::IsFinite(X0.Size() - Ref0.Size())); check(FMath::IsFinite(FMath::Abs(X0.Size() - X00.Size()))); EXPECT_NEAR(FMath::Abs(X0.Size() - Ref0.Size()), 0, KINDA_SMALL_NUMBER);// << *FString("Kinematic body1 moved"); check(FMath::IsFinite(FMath::Abs(X1.Size() - X11.Size()))); EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER);// << *FString("Kinematic body2 moved"); check(FMath::IsFinite(FMath::Abs(X6.Size() - X66.Size()))); EXPECT_NEAR(FMath::Abs(X6.Size() - Ref6.Size()), 0, KINDA_SMALL_NUMBER);// << *FString("Kinematic body7 moved"); // Test kinematic particles have valid (0.0) inverse mass and have the kinematic object state set EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster // Test that after declustering the new cluster hierarchy is what we expect // Tree should be: // // 7 Removed: 8 (Disabled) // / \ \ // 6 1 0 (Now unclustered) // / \ // 5 2 // / \ // 4 3 // // 8 has been removed, zero is dynamic and the remaining tree is kinematic due to particle 1 // EXPECT_EQ(ClusterMap.Num(), 3); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6],ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } else if (bParticle8SucessfulRelease && !bParticle7SucessfulRelease && Frame == 4) { if (ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == false && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == false && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { bParticle7SucessfulRelease = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); // 0 is a dynamic unclustered body (was owned by cluster 8), check that it's moved since declustering EXPECT_GT(FMath::Abs(X0.Size() - Ref0.Size()), KINDA_SMALL_NUMBER); // 1 is a kinematic unclustered body (was owned by cluster 7), check that it's stayed in place EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER); // 6 is a dynamic cluster (was owned by cluster 7). Now that 1 is not a part of the cluster // however it's just been declustered so make sure it's still near the starting location EXPECT_NEAR(FMath::Abs(X6.Size() - Ref6.Size()), 0, KINDA_SMALL_NUMBER); // Check the newly disabled 7 is still kinematic EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster // Test that after declustering the new cluster hierarchy is what we expect // Tree should be: // // 6 Removed: 7 (disabled) // / \ \ // 5 2 1 (declustered, but kinematic) // / \ // 4 3 // // 7 has been removed, 1 is kinematic and the rest of the tree is dynamic as the kinematic element is // no longer in the cluster // EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } else if (bParticle7SucessfulRelease && !bValidFinalActiveState && Frame == 6) { if (ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == false && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == false && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { bValidFinalActiveState = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); // 0 is a dynamic unclustered body (was owned by cluster 8), check that it's moved since declustering EXPECT_GT(FMath::Abs(X0.Size() - Ref0.Size()), KINDA_SMALL_NUMBER); // 1 is a kinematic unclustered body (was owned by cluster 7), check that it's stayed in place EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER); // 6 is a dynamic cluster (was owned by cluster 7). Now that 1 is not a part of the cluster // it is dynamic, check that it has moved since declustering EXPECT_GT(FMath::Abs(X6.Size() - Ref6.Size()), KINDA_SMALL_NUMBER); // Check the previously declustered 7 is still kinematic EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Kinematic); // kinematic cluster // Test that the tree is still the same after the final decluster operation. // Tree should be: // // 6 // / \ // 5 2 // / \ // 4 3 // EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } } // Test our staged conditions // Initial state should set up the heirachy correctly, leaving correct disabled flags on frame 1 EXPECT_TRUE(bValidInitialState); // After releasing particle 8, the states should be updated on frame 2 EXPECT_TRUE(bParticle8SucessfulRelease); // After releasing particle 8, the states should be updated on frame 4 EXPECT_TRUE(bParticle7SucessfulRelease); // After simulating post-release the states should match frame 4 EXPECT_TRUE(bValidFinalActiveState); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_StaticAnchor) { // Test : Set one element static. When the cluster breaks the elements that do not contain the static // rigid body should be dynamic, while the clusters that contain the static body should remain // static. FFramework UnitTest; TSharedPtr RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(20.f)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(30.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(40.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(50.f)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(60.f)), FVector(1.0))); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 5, { 4,3 }, true, false); // just validate at end of construction FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 6, { 5,2 }, true, false); FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(RestCollection.Get(), 7, { 6,1 }, true, false); FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(RestCollection.Get()); FGeometryCollectionClusteringUtility::ValidateResults(RestCollection.Get()); //GeometryCollectionAlgo::PrintParentHierarchy(RestCollection.Get()); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 50.0, 50.0, 50.0, FLT_MAX }; Params.MaxClusterLevel = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); Collection->DynamicCollection->template ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Static; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); FReal StartingRigidDistance = (Collection->DynamicCollection->GetTransform(1).GetTranslation() - Collection->DynamicCollection->GetTransform(0).GetTranslation()).Size(); FReal CurrentRigidDistance = 0.f; // Staged conditions // Initial state should set up the hierachy correctly, leaving correct disabled flags on frame 1 bool bValidInitialState = false; // After releasing particle 8, the states should be updated on frame 2 bool bParticle8SucessfulRelease = false; // After releasing particle 8, the states should be updated on frame 4 bool bParticle7SucessfulRelease = false; // After simulating post-release the states should match frame 4 bool bValidFinalActiveState = false; // Tick once to fush commands UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), }; TArray*> ClusterHandles = { Collection->PhysObject->GetSolverClusterHandle_Internal(0), Collection->PhysObject->GetSolverClusterHandle_Internal(1), Collection->PhysObject->GetSolverClusterHandle_Internal(2), Collection->PhysObject->GetSolverClusterHandle_Internal(3), Collection->PhysObject->GetSolverClusterHandle_Internal(4), Collection->PhysObject->GetSolverClusterHandle_Internal(5), Collection->PhysObject->GetSolverClusterHandle_Internal(6), Collection->PhysObject->GetSolverClusterHandle_Internal(7), Collection->PhysObject->GetSolverClusterHandle_Internal(8), }; FRigidClustering& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const FRigidClustering::FClusterMap& ClusterMap = Clustering.GetChildrenMap(); // Verify that the parent-child relationship is reflected in the clustering hierarchy // Tree should be: // // 8 // / \ // 7 0 // / \ // 6 1 // / \ // 5 2 // / \ // 4 3 // // Entire cluster is kinematic due to particle 1 // EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6],ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[8], { ParticleHandles[7],ParticleHandles[0] })); // Storage for positions for particles 0, 1, 6 for testing assumptions FVector Ref0; FVector Ref1; FVector Ref6; for(int Frame = 1; Frame < 10; Frame++) { UnitTest.Advance(); // On frames 2 and 4, deactivate particles 8 and 7, releasing their children (7,0 then 6,1) if(Frame == 2) { UnitTest.Solver->GetEvolution()->GetRigidClustering().DeactivateClusterParticle(ParticleHandles[8]); } if(Frame == 4) { UnitTest.Solver->GetEvolution()->GetRigidClustering().DeactivateClusterParticle(ParticleHandles[7]); } // Verify that the kinematic particle remains kinematic (InvMass == 0.0) // and the the dynamic particles have a non-zero inv mass EXPECT_NE(ParticleHandles[0]->InvM(), 0.f); // dynamic rigid EXPECT_EQ(ParticleHandles[1]->InvM(), 0.f); // kinematic rigid EXPECT_NE(ParticleHandles[2]->InvM(), 0.f); // dynamic rigid EXPECT_NE(ParticleHandles[3]->InvM(), 0.f); // dynamic rigid EXPECT_NE(ParticleHandles[4]->InvM(), 0.f); // dynamic rigid EXPECT_NE(ParticleHandles[5]->InvM(), 0.f); // dynamic rigid EXPECT_NE(ParticleHandles[6]->InvM(), 0.f); // dynamic cluster if(!bValidInitialState && Frame == 1) { if(ParticleHandles[0]->Disabled() == true && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == false) { bValidInitialState = true; Ref0 = ParticleHandles[0]->GetX(); Ref1 = ParticleHandles[1]->GetX(); Ref6 = ParticleHandles[6]->GetX(); // Test static particles have valid (0.0) inverse mass and have the static object state set EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster EXPECT_EQ(ParticleHandles[8]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster } } else if(bValidInitialState && !bParticle8SucessfulRelease && Frame == 2) { if(ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == true && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == true && ParticleHandles[7]->Disabled() == false && ParticleHandles[8]->Disabled() == true) { bParticle8SucessfulRelease = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); EXPECT_NEAR(FMath::Abs(X0.Size() - Ref0.Size()), 0, KINDA_SMALL_NUMBER); EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER); EXPECT_NEAR(FMath::Abs(X6.Size() - Ref6.Size()), 0, KINDA_SMALL_NUMBER); // Test static particles have valid (0.0) inverse mass and have the static object state set EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster EXPECT_EQ(ParticleHandles[8]->InvM(), 0.f); // kinematic cluster EXPECT_EQ(ParticleHandles[8]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster // Test that after declustering the new cluster hierarchy is what we expect // Tree should be: // // 7 Removed: 8 (Disabled) // / \ \ // 6 1 0 (Now unclustered) // / \ // 5 2 // / \ // 4 3 // // 8 has been removed, zero is dynamic and the remaining tree is static due to particle 1 // EXPECT_EQ(ClusterMap.Num(), 3); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[7], { ParticleHandles[6],ParticleHandles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } else if(bParticle8SucessfulRelease && !bParticle7SucessfulRelease && Frame == 4) { if(ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == false && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == false && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { bParticle7SucessfulRelease = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); // 0 is a dynamic unclustered body (was owned by cluster 8), check that it's moved since declustering EXPECT_GT(FMath::Abs(X0.Size() - Ref0.Size()), KINDA_SMALL_NUMBER); // 1 is a static unclustered body (was owned by cluster 7), check that it's stayed in place EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER); // 6 is a dynamic cluster (was owned by cluster 7) but it has just been declustered // Test that it's still near the starting position EXPECT_NEAR(FMath::Abs(X6.Size() - Ref6.Size()), 0, KINDA_SMALL_NUMBER); // Check the newly disabled 7 is still static EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // Static cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster // Test that after declustering the new cluster hierarchy is what we expect // Tree should be: // // 6 Removed: 7 (disabled) // / \ \ // 5 2 1 (declustered, but Static) // / \ // 4 3 // // 7 has been removed, 1 is static and the rest of the tree is dynamic as the static element is // no longer in the cluster // EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } else if(bParticle7SucessfulRelease && !bValidFinalActiveState && Frame == 6) { if(ParticleHandles[0]->Disabled() == false && ParticleHandles[1]->Disabled() == false && ParticleHandles[2]->Disabled() == true && ParticleHandles[3]->Disabled() == true && ParticleHandles[4]->Disabled() == true && ParticleHandles[5]->Disabled() == true && ParticleHandles[6]->Disabled() == false && ParticleHandles[7]->Disabled() == true && ParticleHandles[8]->Disabled() == true) { bValidFinalActiveState = true; FVector X0 = ParticleHandles[0]->GetX(); FVector X1 = ParticleHandles[1]->GetX(); FVector X6 = ParticleHandles[6]->GetX(); // 0 is a dynamic unclustered body (was owned by cluster 8), check that it's moved since declustering EXPECT_GT(FMath::Abs(X0.Size() - Ref0.Size()), KINDA_SMALL_NUMBER); // 1 is a static unclustered body (was owned by cluster 7), check that it's stayed in place EXPECT_NEAR(FMath::Abs(X1.Size() - Ref1.Size()), 0, KINDA_SMALL_NUMBER); // 6 is a dynamic cluster (was owned by cluster 7). Now that 1 is not a part of the cluster // it is dynamic, check that it has moved since declustering EXPECT_GT(FMath::Abs(X6.Size() - Ref6.Size()), KINDA_SMALL_NUMBER); // Check the previously declustered 7 is still static EXPECT_EQ(ParticleHandles[7]->InvM(), 0.f); // Static cluster EXPECT_EQ(ParticleHandles[7]->ObjectState(), Chaos::EObjectStateType::Static); // Static cluster // Test that the tree is still the same after the final decluster operation. // Tree should be: // // 6 // / \ // 5 2 // / \ // 4 3 // EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[6], { ParticleHandles[5],ParticleHandles[2] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[5], { ParticleHandles[4],ParticleHandles[3] })); } } } // Test our staged conditions // Initial state should set up the heirachy correctly, leaving correct disabled flags on frame 1 EXPECT_TRUE(bValidInitialState); // After releasing particle 8, the states should be updated on frame 2 EXPECT_TRUE(bParticle8SucessfulRelease); // After releasing particle 8, the states should be updated on frame 4 EXPECT_TRUE(bParticle7SucessfulRelease); // After simulating post-release the states should match frame 4 EXPECT_TRUE(bValidFinalActiveState); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_UnionClusters) { // Test : Joining collections using the ClusterGroupIndex by a particle dynamically created within the solver. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody(FVector(-2, 0, 3)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); TSharedPtr RestCollection2 = CreateClusteredBody(FVector(2, 0, 3)); CreationParameters Params2; Params2.RestCollection = RestCollection2; Params2.DynamicState = EObjectStateTypeEnum::Chaos_Object_Kinematic; Params2.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params2.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params2.Simulating = true; Params2.EnableClustering = true; Params2.DamageThreshold = { FLT_MAX }; Params2.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection2 = TNewSimulationObject::Init(Params2)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.AddSimulationObject(Collection2); UnitTest.Initialize(); TSharedPtr DynamicCollection = Collection->DynamicCollection; TSharedPtr DynamicCollection2 = Collection2->DynamicCollection; TArray Distances; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); const auto& ParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(2)->CastToRigidParticle(),{ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle()})); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(5)->CastToRigidParticle(),{ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle()})); }); for (int Frame = 0; Frame < 100; Frame++) { UnitTest.Advance(); if (Frame == 0) { TArray GlobalTransform; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, GlobalTransform); TArray GlobalTransform2; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, GlobalTransform2); // build relative transforms distances for (int32 i = 0; i < (int32)GlobalTransform.Num()-1; i++) { for (int j = 0; j < (int32)GlobalTransform2.Num()-1; j++) { Distances.Add((GlobalTransform[i].GetTranslation() - GlobalTransform2[j].GetTranslation()).Size()); } } EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(6)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle(),ParticleHandles.Handle(4)->CastToRigidParticle() })); } } TArray GlobalTransform; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, GlobalTransform); TArray GlobalTransform2; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, GlobalTransform2); // build relative transforms distances TArray Distances2; for (int32 i = 0; i < (int32)GlobalTransform.Num() - 1; i++) { for (int j = 0; j < (int32)GlobalTransform2.Num() - 1; j++) { Distances2.Add((GlobalTransform[i].GetTranslation() - GlobalTransform2[j].GetTranslation()).Size()); } } for (int i = 0; i < Distances.Num()/2.0; i++) { EXPECT_LT( FMath::Abs(Distances[i] - Distances2[i]), 0.1 ); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_UnionClustersFalling) { // Test : Joining collections using the ClusterGroupIndex by a particle dynamically created within the solver. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody(FVector(-2, 0, 3)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); TSharedPtr RestCollection2 = CreateClusteredBody(FVector(2, 0, 3)); CreationParameters Params2; Params2.RestCollection = RestCollection2; Params2.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params2.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params2.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params2.Simulating = true; Params2.EnableClustering = true; Params2.DamageThreshold = { FLT_MAX }; Params2.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection2 = TNewSimulationObject::Init(Params2)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.AddSimulationObject(Collection2); UnitTest.Initialize(); TSharedPtr DynamicCollection = Collection->DynamicCollection; TSharedPtr DynamicCollection2 = Collection2->DynamicCollection; TArray Distances; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); const auto& ParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&Clustering,&ClusterMap,&ParticleHandles]() { EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(2)->CastToRigidParticle(),{ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle()})); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(5)->CastToRigidParticle(),{ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle()})); }); TArray PrevGlobalTransform; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, PrevGlobalTransform); TArray PrevGlobalTransform2; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, PrevGlobalTransform2); for (int Frame = 0; Frame < 100; Frame++) { UnitTest.Advance(); TArray GlobalTransform; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, GlobalTransform); TArray GlobalTransform2; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, GlobalTransform2); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(6)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle(),ParticleHandles.Handle(4)->CastToRigidParticle() })); EXPECT_TRUE(DynamicCollection->GetParent(0) == INDEX_NONE); EXPECT_TRUE(DynamicCollection->GetParent(1) == INDEX_NONE); EXPECT_TRUE(DynamicCollection->GetParent(2) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(0) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(1) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(2) == INDEX_NONE); EXPECT_TRUE(GlobalTransform[0].GetTranslation().X == PrevGlobalTransform[0].GetTranslation().X); EXPECT_TRUE(GlobalTransform[1].GetTranslation().X == PrevGlobalTransform[1].GetTranslation().X); EXPECT_TRUE(GlobalTransform[0].GetTranslation().Y == PrevGlobalTransform[0].GetTranslation().Y); EXPECT_TRUE(GlobalTransform[1].GetTranslation().Y == PrevGlobalTransform[1].GetTranslation().Y); EXPECT_TRUE(GlobalTransform[0].GetTranslation().Z < PrevGlobalTransform[0].GetTranslation().Z); EXPECT_TRUE(GlobalTransform[1].GetTranslation().Z < PrevGlobalTransform[1].GetTranslation().Z); EXPECT_TRUE(GlobalTransform2[0].GetTranslation().X == PrevGlobalTransform2[0].GetTranslation().X); EXPECT_TRUE(GlobalTransform2[1].GetTranslation().X == PrevGlobalTransform2[1].GetTranslation().X); EXPECT_TRUE(GlobalTransform2[0].GetTranslation().Y == PrevGlobalTransform2[0].GetTranslation().Y); EXPECT_TRUE(GlobalTransform2[1].GetTranslation().Y == PrevGlobalTransform2[1].GetTranslation().Y); EXPECT_TRUE(GlobalTransform2[0].GetTranslation().Z < PrevGlobalTransform2[0].GetTranslation().Z); EXPECT_TRUE(GlobalTransform2[1].GetTranslation().Z < PrevGlobalTransform2[1].GetTranslation().Z); PrevGlobalTransform = GlobalTransform; PrevGlobalTransform2 = GlobalTransform2; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_UnionClusterCollisions) { // Test : Joining collections using the ClusterGroupIndex by a particle dynamically created within the solver. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody(FVector(-2, 0, 3)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_LevelSet; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); TSharedPtr RestCollection2 = CreateClusteredBody(FVector(2, 0, 3)); CreationParameters Params2; Params2.RestCollection = RestCollection2; Params2.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params2.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_LevelSet; Params2.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params2.Simulating = true; Params2.EnableClustering = true; Params2.DamageThreshold = { FLT_MAX }; Params2.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection2 = TNewSimulationObject::Init(Params2)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.AddSimulationObject(Collection2); UnitTest.AddSimulationObject(TNewSimulationObject::Init()->template As()); //make newsimobject set a full block filter on all shapes! UnitTest.Initialize(); TSharedPtr DynamicCollection = Collection->DynamicCollection; TSharedPtr DynamicCollection2 = Collection2->DynamicCollection; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); const auto& ParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); const TArrayCollectionArray& ChildToParent = Clustering.GetChildToParentMap(); FVector InitialRootPosition; FVector RelativeChildOffsets[4]; UnitTest.Solver->RegisterSimOneShotCallback([&]() { EXPECT_EQ(ClusterMap.Num(),2); TArray*> CollectionParticles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2) }; EXPECT_EQ(CollectionParticles.Num(),3); EXPECT_EQ(Collection->PhysObject->GetNumTransforms(), 3); EXPECT_TRUE(ClusterMapContains(ClusterMap,CollectionParticles[2],{CollectionParticles[1],CollectionParticles[0]})); TArray*> CollectionParticles2 = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2) }; EXPECT_EQ(CollectionParticles2.Num(),3); EXPECT_EQ(Collection2->PhysObject->GetNumTransforms(), 3); EXPECT_TRUE(ClusterMapContains(ClusterMap,CollectionParticles2[2],{CollectionParticles2[1],CollectionParticles2[0]})); }); for (int Frame = 0; Frame < 50; Frame++) { UnitTest.Advance(); TArray*> CollectionParticles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), }; EXPECT_EQ(CollectionParticles.Num(),3); EXPECT_EQ(Collection->PhysObject->GetNumTransforms(), 3); TArray*> CollectionParticles2 = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2), }; const auto* Root = CollectionParticles[0]->ClusterIds().Id; EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, Root, { CollectionParticles[0],CollectionParticles[1], CollectionParticles2[0], CollectionParticles2[1] })); // // TEST // Validate that the relative translations stay the same // for the union clustered children, and that the root actually // moves. // if (Frame == 0) { InitialRootPosition = Root->GetX(); RelativeChildOffsets[0] = CollectionParticles[0]->GetX() - Root->GetX(); RelativeChildOffsets[1] = CollectionParticles[1]->GetX() - Root->GetX(); RelativeChildOffsets[2] = CollectionParticles2[0]->GetX() - Root->GetX(); RelativeChildOffsets[3] = CollectionParticles2[1]->GetX() - Root->GetX(); } else { FTransform RootTransform(Root->GetR(), Root->GetX()); TArray GlobalTransform1; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, GlobalTransform1); TArray GlobalTransform2; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, GlobalTransform2); EXPECT_TRUE(!InitialRootPosition.Equals(Root->GetX())); // root moves EXPECT_TRUE(RelativeChildOffsets[0].Equals(GlobalTransform1[0].GetRelativeTransform(RootTransform).GetTranslation())); EXPECT_TRUE(RelativeChildOffsets[1].Equals(GlobalTransform1[1].GetRelativeTransform(RootTransform).GetTranslation())); EXPECT_TRUE(RelativeChildOffsets[2].Equals(GlobalTransform2[0].GetRelativeTransform(RootTransform).GetTranslation())); EXPECT_TRUE(RelativeChildOffsets[3].Equals(GlobalTransform2[1].GetRelativeTransform(RootTransform).GetTranslation())); } // // TEST // Validate that the children have been removed from the // parenting hierarchy. // EXPECT_TRUE(DynamicCollection->GetParent(0) == INDEX_NONE); EXPECT_TRUE(DynamicCollection->GetParent(1) == INDEX_NONE); EXPECT_TRUE(DynamicCollection->GetParent(2) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(0) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(1) == INDEX_NONE); EXPECT_TRUE(DynamicCollection2->GetParent(2) == INDEX_NONE); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_ReleaseClusterParticle_ClusteredNode) { // Test : Build to geometry collections, cluster them together, release the sub bodies of the first collection. // ... should create a internal cluster with property transform mappings. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody(FVector(0, 0, 100)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.ClusterGroupIndex = 1; Params.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; FGeometryCollectionWrapper* Collection1 = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection1); TSharedPtr RestCollection2 = CreateClusteredBody(FVector(0, 0, 200)); CreationParameters Params2; Params2.RestCollection = RestCollection2; Params2.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params2.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params2.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params2.Simulating = true; Params2.EnableClustering = true; Params2.DamageThreshold = { FLT_MAX }; Params2.ClusterGroupIndex = 1; Params2.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; FGeometryCollectionWrapper* Collection2 = TNewSimulationObject::Init(Params2)->template As(); UnitTest.AddSimulationObject(Collection2); UnitTest.Initialize(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); Chaos::FClusterUnionManager& ClusterUnionManager = Clustering.GetClusterUnionManager(); const auto& ClusterMap = Clustering.GetChildrenMap(); TSharedPtr DynamicCollection1 = Collection1->DynamicCollection; TSharedPtr DynamicCollection2 = Collection2->DynamicCollection; TArray Collection1_InitialTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_InitialTM); TArray Collection2_InitialTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_InitialTM); TArray*> Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; TArray*> Collection2Handles = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2), }; const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; Collection2Handles = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2), }; EXPECT_EQ(SovlerParticleHandles.Size(), 6); EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection2Handles[2],{Collection2Handles[1],Collection2Handles[0]})); }); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, SovlerParticleHandles.Handle(6)->CastToRigidParticle(), { Collection1Handles[1],Collection1Handles[0], Collection2Handles[1],Collection2Handles[0] })); EXPECT_TRUE(ClusterUnionManager.FindClusterUnionFromExplicitIndex(1) != nullptr); Chaos::FClusterUnion& ClusterUnion = *ClusterUnionManager.FindClusterUnionFromExplicitIndex(1); EXPECT_EQ(ClusterUnion.InternalCluster, SovlerParticleHandles.Handle(6).Get()); // A bit of an assumption that the root particle is the 2nd index in these arrays. EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection1Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection1Handles[1])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[1])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 4); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection1Handles[0], Collection1Handles[1], Collection2Handles[0], Collection2Handles[1] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 4); TArray Collection1_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PreReleaseTM); TArray Collection2_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_PreReleaseTM); for (int Idx = 0; Idx < Collection1_PreReleaseTM.Num() - 1; Idx++) { EXPECT_LT(Collection1_PreReleaseTM[Idx].GetTranslation().Z, Collection1_InitialTM[Idx].GetTranslation().Z); EXPECT_LT(Collection2_PreReleaseTM[Idx].GetTranslation().Z, Collection2_InitialTM[Idx].GetTranslation().Z); } UnitTest.Solver->GetEvolution()->GetRigidClustering().ReleaseClusterParticles({ Collection1Handles[0],Collection1Handles[1] }); ClusterUnionManager.HandleDeferredClusterUnionUpdateProperties(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); EXPECT_FALSE(ClusterUnion.ChildParticles.Contains(Collection1Handles[0])); EXPECT_FALSE(ClusterUnion.ChildParticles.Contains(Collection1Handles[1])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[1])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 2); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_FALSE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection1Handles[0], Collection1Handles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection2Handles[0], Collection2Handles[1] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 2); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); TArray Collection1_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PostReleaseTM); TArray Collection2_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_PostReleaseTM); for (int Idx = 0; Idx < Collection1_PostReleaseTM.Num() - 1; Idx++) { EXPECT_LT(Collection1_PostReleaseTM[Idx].GetTranslation().Z, Collection1_PreReleaseTM[Idx].GetTranslation().Z); EXPECT_LT(Collection2_PostReleaseTM[Idx].GetTranslation().Z, Collection2_PreReleaseTM[Idx].GetTranslation().Z); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_ReleaseClusterParticle_ClusteredKinematicNode) { // Test : Build to geometry collections, cluster them together, release the sub bodies of the first collection. // this should create a internal cluster with property transform mappings. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody(FVector(0, 0, 100)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.ClusterGroupIndex = 1; Params.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; FGeometryCollectionWrapper* Collection1 = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection1); TSharedPtr RestCollection2 = CreateClusteredBody(FVector(0, 0, 200)); CreationParameters Params2; Params2.RestCollection = RestCollection2; Params2.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params2.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params2.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params2.Simulating = true; Params2.EnableClustering = true; Params2.DamageThreshold = { FLT_MAX }; Params2.ClusterGroupIndex = 1; Params2.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; FGeometryCollectionWrapper* Collection2 = TNewSimulationObject::Init(Params2)->template As(); UnitTest.AddSimulationObject(Collection2); TSharedPtr DynamicCollection1 = Collection1->DynamicCollection; TSharedPtr DynamicCollection2 = Collection2->DynamicCollection; DynamicCollection1->ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; UnitTest.Initialize(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); Chaos::FClusterUnionManager& ClusterUnionManager = Clustering.GetClusterUnionManager(); const auto& ClusterMap = Clustering.GetChildrenMap(); TArray Collection1_InitialTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_InitialTM); TArray Collection2_InitialTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_InitialTM); TArray*> Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; TArray*> Collection2Handles = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2), }; const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; Collection2Handles = { Collection2->PhysObject->GetParticle_Internal(0), Collection2->PhysObject->GetParticle_Internal(1), Collection2->PhysObject->GetParticle_Internal(2), }; EXPECT_EQ(SovlerParticleHandles.Size(),6); EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection2Handles[2],{Collection2Handles[1],Collection2Handles[0]})); }); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); EXPECT_EQ(ClusterMap.Num(), 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, SovlerParticleHandles.Handle(6)->CastToRigidParticle(), { Collection1Handles[1],Collection1Handles[0], Collection2Handles[1],Collection2Handles[0] })); EXPECT_TRUE(ClusterUnionManager.FindClusterUnionFromExplicitIndex(1) != nullptr); Chaos::FClusterUnion& ClusterUnion = *ClusterUnionManager.FindClusterUnionFromExplicitIndex(1); EXPECT_EQ(ClusterUnion.InternalCluster, SovlerParticleHandles.Handle(6).Get()); // A bit of an assumption that the root particle is the 2nd index in these arrays. EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection1Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection1Handles[1])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[1])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 4); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection1Handles[0], Collection1Handles[1], Collection2Handles[0], Collection2Handles[1] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 4); TArray Collection1_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PreReleaseTM); TArray Collection2_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_PreReleaseTM); for (int Idx = 0; Idx < Collection1_PreReleaseTM.Num() - 1; Idx++) { EXPECT_EQ(Collection1_PreReleaseTM[Idx].GetTranslation().Z, Collection1_InitialTM[Idx].GetTranslation().Z); EXPECT_EQ(Collection2_PreReleaseTM[Idx].GetTranslation().Z, Collection2_InitialTM[Idx].GetTranslation().Z); } UnitTest.Solver->GetEvolution()->GetRigidClustering().ReleaseClusterParticles({ Collection1Handles[0],Collection1Handles[1] }); ClusterUnionManager.HandleDeferredClusterUnionUpdateProperties(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); EXPECT_FALSE(ClusterUnion.ChildParticles.Contains(Collection1Handles[0])); EXPECT_FALSE(ClusterUnion.ChildParticles.Contains(Collection1Handles[1])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[0])); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(Collection2Handles[1])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 2); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_FALSE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection1Handles[0], Collection1Handles[1] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { Collection2Handles[0], Collection2Handles[1] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 2); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 7); // validate that DynamicCollection2 became dynamic and fell from the cluster. TArray Collection1_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PostReleaseTM); TArray Collection2_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection2, Collection2_PostReleaseTM); for (int Idx = 0; Idx < Collection1_PostReleaseTM.Num() - 1; Idx++) { if(Idx == 1) { EXPECT_EQ(Collection1_PostReleaseTM[Idx].GetTranslation().Z, Collection1_PreReleaseTM[Idx].GetTranslation().Z); // the original kinematic should be frozen } else { EXPECT_LT(Collection1_PostReleaseTM[Idx].GetTranslation().Z, Collection1_PreReleaseTM[Idx].GetTranslation().Z); } EXPECT_LT(Collection2_PostReleaseTM[Idx].GetTranslation().Z, Collection2_PreReleaseTM[Idx].GetTranslation().Z); } } // Create two boxes and wrap them in a clusterm and wrap that in a second cluster. // Release the two boxes, and the boxes should fall. Both clusters should be empty and therefore disabled. // // @todo(chaos): this test is disabled. The outer cluster is not being disabled when the leafs are released. // Fix when this functionality is supported again... GTEST_TEST(AllTraits, DISABLED_GeometryCollection_RigidBodies_ClusterTest_ReleaseClusterParticles_AllLeafNodes) { // Release the leaf nodes of a cluster. This test exercises the clusters ability to deactivate from the bottom up. FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_TwoParents_TwoBodies(FVector(0, 0, 100)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.MaxClusterLevel = 1; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection1 = TNewSimulationObject::Init(Params)->template As(); TSharedPtr DynamicCollection1 = Collection1->DynamicCollection; DynamicCollection1->ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; UnitTest.AddSimulationObject(Collection1); UnitTest.Initialize(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); TArray Collection1_InitialTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_InitialTM); TArray*> Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { Collection1Handles = { Collection1->PhysObject->GetParticle_Internal(0), Collection1->PhysObject->GetParticle_Internal(1), Collection1->PhysObject->GetParticle_Internal(2), }; EXPECT_EQ(SovlerParticleHandles.Size(),4); EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[3],{Collection1Handles[2]})); }); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[2], { Collection1Handles[1],Collection1Handles[0] })); EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[3], { Collection1Handles[2] })); TArray Collection1_PreReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PreReleaseTM); for (int Idx = 0; Idx < Collection1_PreReleaseTM.Num() - 1; Idx++) { EXPECT_EQ(Collection1_PreReleaseTM[Idx].GetTranslation().Z, Collection1_InitialTM[Idx].GetTranslation().Z); } UnitTest.Solver->GetEvolution()->GetRigidClustering().ReleaseClusterParticles({ Collection1Handles[0],Collection1Handles[1] }); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 1); UnitTest.Advance(); EXPECT_EQ(SovlerParticleHandles.Size(), 4); EXPECT_EQ(ClusterMap.Num(), 1); // validate that DynamicCollection1 BODY 2 became dynamic and fell from the cluster. TArray Collection1_PostReleaseTM; GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection1, Collection1_PostReleaseTM); EXPECT_NEAR(Collection1_PostReleaseTM[1].GetTranslation().Z, Collection1_PreReleaseTM[1].GetTranslation().Z, KINDA_SMALL_NUMBER); // the original kinematic should be frozen EXPECT_LT(Collection1_PostReleaseTM[0].GetTranslation().Z, Collection1_PreReleaseTM[0].GetTranslation().Z); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_ClusterTest_ReleaseClusterParticles_ClusterNodeAndSubClusterNode) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_TwoParents_TwoBodies(FVector(0, 0, 100)); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { FLT_MAX }; Params.MaxClusterLevel = 1; Params.ClusterGroupIndex = 1; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); TSharedPtr DynamicCollection = Collection->DynamicCollection; DynamicCollection->ModifyAttribute("DynamicState", FGeometryCollection::TransformGroup)[1] = (uint8)EObjectStateTypeEnum::Chaos_Object_Kinematic; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); // The tests below require a list of all the current particles which are abstracted away a little // inside the solver particles handler. This helper just lets us auto cast to rigids as we know // that's all that exists in the solver. struct FRigidParticleWrapper { FRigidParticleWrapper(TGeometryParticleHandles& InParticlesRef) : Particles(InParticlesRef) {} TGeometryParticleHandles& Particles; TPBDRigidParticleHandle* operator[](int32 InIndex) { return Particles.Handle(InIndex)->CastToRigidParticle(); } }; FRigidParticleWrapper ParticleHandles(UnitTest.Solver->GetParticles().GetParticleHandles()); UnitTest.Advance(); FRigidClustering& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); Chaos::FClusterUnionManager& ClusterUnionManager = Clustering.GetClusterUnionManager(); const FRigidClustering::FClusterMap& ClusterMap = Clustering.GetChildrenMap(); const Chaos::TArrayCollectionArray& ClusterIdsArray = Clustering.GetClusterIdsArray(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { EXPECT_EQ(ClusterMap.Num(),2); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles[2],{ParticleHandles[0],ParticleHandles[1]})); EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles[4],{ParticleHandles[2]})); }); EXPECT_TRUE(ClusterUnionManager.FindClusterUnionFromExplicitIndex(1) != nullptr); Chaos::FClusterUnion& ClusterUnion = *ClusterUnionManager.FindClusterUnionFromExplicitIndex(1); // Test releasing a specific unioned cluster // We end up with the following cluster tree // 4 // | // 2 // / \ // 1 0 // On frame 5 we tell particle 4 as a cluster parent to release its children (only 2) and verify the result for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Advance(); if (Frame == 5) { UnitTest.Solver->GetEvolution()->GetRigidClustering().ReleaseClusterParticles(ParticleHandles[4]->CastToClustered(), true); } if (Frame < 5) { EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_NE(ClusterIdsArray[0].Id, nullptr); EXPECT_EQ(ClusterIdsArray[1].Id, nullptr); EXPECT_EQ(ClusterIdsArray[2].Id, nullptr); EXPECT_TRUE(ClusterUnion.ChildParticles.Contains(ParticleHandles[2])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 1); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_TRUE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { ParticleHandles[2] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 1); EXPECT_EQ(ClusterMap.Num(), 2); } else { EXPECT_TRUE(!ParticleHandles[2]->Disabled()); EXPECT_EQ(ClusterIdsArray[0].Id, nullptr); EXPECT_EQ(ClusterIdsArray[1].Id, nullptr); EXPECT_EQ(ClusterIdsArray[2].Id, nullptr); EXPECT_FALSE(ClusterUnion.ChildParticles.Contains(ParticleHandles[2])); EXPECT_EQ(ClusterUnion.ChildParticles.Num(), 0); EXPECT_EQ(ClusterUnion.ExplicitIndex, 1); EXPECT_FALSE(ClusterMapContains(ClusterMap, ClusterUnion.InternalCluster, { ParticleHandles[2] })); EXPECT_EQ(ClusterMap[ClusterUnion.InternalCluster].Num(), 0); EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[2], { ParticleHandles[0], ParticleHandles[1] })); } } } GTEST_TEST(AllTraits, DISABLED_GeometryCollection_RigidBodiess_ClusterTest_MaxClusterLevel_1) { FFramework UnitTest; // Create hierarchy TSharedPtr RestCollection; RestCollection = GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(0, 0, 0)), FVector(1.0)); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(100, 0, 0)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(200, 0, 0)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(300, 0, 0)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(400, 0, 0)), FVector(1.0))); RestCollection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0.f)), FVector(500, 0, 0)), FVector(1.0))); RestCollection->AddElements(7, FGeometryCollection::TransformGroup); RestCollection->Transform[10].SetTranslation(FVector3f(0,0,0)); RestCollection->SimulationType[0] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[1] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[2] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[3] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[4] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[5] = FGeometryCollection::ESimulationTypes::FST_Rigid; RestCollection->SimulationType[6] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[7] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[8] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[9] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[10] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[11] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[12] = FGeometryCollection::ESimulationTypes::FST_Clustered; GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 6, { 11 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 7, { 3,4,5 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 8, { 6 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 9, { 7 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 10, { 9,8 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 11, { 12 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 12, { 0,1,2 }); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.Simulating = true; Params.EnableClustering = true; Params.DamageThreshold = { 100.0f, 4.0f, 2.0f }; Params.MaxClusterLevel = 3; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); UnitTest.Advance(); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = 104.0; FalloffField->Radius = 10000.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), Collection->PhysObject->GetParticle_Internal(9), Collection->PhysObject->GetParticle_Internal(10), }; Chaos::FRigidClustering& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); auto& x = UnitTest.Solver->GetEvolution()->GetRigidClustering().GetStrainArray(); { EXPECT_EQ(ClusterMap.Num(), 5); EXPECT_EQ(ClusterMap[ParticleHandles[6]].Num(), 3); EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[0])); EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[1])); EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[2])); EXPECT_EQ(ClusterMap[ParticleHandles[7]].Num(), 3); EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[3])); EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[4])); EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[5])); EXPECT_EQ(ClusterMap[ParticleHandles[8]].Num(), 1); EXPECT_TRUE(ClusterMap[ParticleHandles[8]].Contains(ParticleHandles[6])); EXPECT_EQ(ClusterMap[ParticleHandles[9]].Num(), 1); EXPECT_TRUE(ClusterMap[ParticleHandles[9]].Contains(ParticleHandles[7])); EXPECT_EQ(ClusterMap[ParticleHandles[10]].Num(), 2); EXPECT_TRUE(ClusterMap[ParticleHandles[10]].Contains(ParticleHandles[8])); EXPECT_TRUE(ClusterMap[ParticleHandles[10]].Contains(ParticleHandles[9])); } EXPECT_TRUE(ParticleHandles[6]->Disabled()); EXPECT_TRUE(ParticleHandles[7]->Disabled()); EXPECT_TRUE(ParticleHandles[8]->Disabled()); EXPECT_TRUE(ParticleHandles[9]->Disabled()); EXPECT_FALSE(ParticleHandles[10]->Disabled()); // {100, 4, 2} -> {4, 2} FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_ExternalClusterStrain); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); EXPECT_TRUE(ParticleHandles[6]->Disabled()); EXPECT_TRUE(ParticleHandles[7]->Disabled()); EXPECT_FALSE(ParticleHandles[8]->Disabled()); EXPECT_FALSE(ParticleHandles[9]->Disabled()); EXPECT_TRUE(ParticleHandles[10]->Disabled()); // {4, 2} -> {4, 2} (beyond max cluster level) UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); EXPECT_TRUE(ParticleHandles[0]->Disabled()); EXPECT_TRUE(ParticleHandles[1]->Disabled()); EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_TRUE(ParticleHandles[3]->Disabled()); EXPECT_TRUE(ParticleHandles[4]->Disabled()); EXPECT_TRUE(ParticleHandles[5]->Disabled()); EXPECT_TRUE(ParticleHandles[6]->Disabled()); EXPECT_TRUE(ParticleHandles[7]->Disabled()); EXPECT_FALSE(ParticleHandles[8]->Disabled()); EXPECT_FALSE(ParticleHandles[9]->Disabled()); EXPECT_TRUE(ParticleHandles[10]->Disabled()); // {4, 2} -> {4, 2} (beyond max cluster level) // but this time, force internal reordering via internal strain. TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_InternalClusterStrain); // Note: now internal strain FalloffField->Magnitude = -3.0; UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); EXPECT_TRUE(ParticleHandles[0]->Disabled()); EXPECT_TRUE(ParticleHandles[1]->Disabled()); EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_TRUE(ParticleHandles[3]->Disabled()); EXPECT_TRUE(ParticleHandles[4]->Disabled()); EXPECT_TRUE(ParticleHandles[5]->Disabled()); EXPECT_TRUE(ParticleHandles[6]->Disabled()); EXPECT_TRUE(ParticleHandles[7]->Disabled()); EXPECT_FALSE(ParticleHandles[8]->Disabled()); EXPECT_FALSE(ParticleHandles[9]->Disabled()); EXPECT_TRUE(ParticleHandles[10]->Disabled()); { // Check if level 3 rigids (children) are still clustered despite internal strain breaking level 2 clusters (parent) //EXPECT_EQ(ClusterMap[ParticleHandles[6]].Num(), 3); //EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[0])); //EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[1])); //EXPECT_TRUE(ClusterMap[ParticleHandles[6]].Contains(ParticleHandles[2])); //EXPECT_EQ(ClusterMap[ParticleHandles[7]].Num(), 3); //EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[3])); //EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[4])); //EXPECT_TRUE(ClusterMap[ParticleHandles[7]].Contains(ParticleHandles[5])); } } GTEST_TEST(AllTraits, DISABLED_GeometryCollection_RigidBodiess_ClusterTest_ParticleImplicitCollisionGeometry) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_FracturedGeometry(); CreationParameters Params; Params.RestCollection = RestCollection; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_LevelSet; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.Simulating = true; Params.EnableClustering = true; Params.CollisionGroup = -1; Params.CollisionParticleFraction = 0.70f; Params.MinLevelSetResolution = 15; Params.MaxLevelSetResolution = 20; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); // We can get the size of an original CollisionParticles by storing the Simplicials before any internal resizing. typedef TUniquePtr< FCollisionStructureManager::FSimplicial > FSimplicialPointer; const TManagedArray & Simplicials = RestCollection->template GetAttribute(FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup); UnitTest.Advance(); // this call triggers the array resize based on the fraction // Test non-clustered bodies TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), Collection->PhysObject->GetParticle_Internal(1), Collection->PhysObject->GetParticle_Internal(2), Collection->PhysObject->GetParticle_Internal(3), Collection->PhysObject->GetParticle_Internal(4), Collection->PhysObject->GetParticle_Internal(5), Collection->PhysObject->GetParticle_Internal(6), Collection->PhysObject->GetParticle_Internal(7), Collection->PhysObject->GetParticle_Internal(8), Collection->PhysObject->GetParticle_Internal(9), Collection->PhysObject->GetParticle_Internal(10), }; int32 NumCollisionParticles, ExpectedNumCollisionParticles; for (int i = 0; i < Collection->PhysObject->GetNumTransforms(); i++) { if (Collection->RestCollection->SimulationType[i] == FGeometryCollection::ESimulationTypes::FST_Rigid) { NumCollisionParticles = ParticleHandles[i]->CollisionParticlesSize(); // resized ExpectedNumCollisionParticles = (int32)Simplicials[i]->Size() * Params.CollisionParticleFraction; //EXPECT_EQ(ExpectedNumCollisionParticles, NumCollisionParticles); //EXPECT_FALSE(NumCollisionParticles == 0.0f); //EXPECT_FALSE(Params.CollisionParticleFraction == 1.0f); // not defaulted } } } }