// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionTestSimulationField.h" #include "GeometryCollection/GeometryCollectionTestUtility.h" #include "GeometryCollection/GeometryCollectionTestFramework.h" #include "GeometryCollection/GeometryCollection.h" #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/GeometryCollectionAlgo.h" #include "GeometryCollection/TransformCollection.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "Field/FieldSystem.h" #include "Field/FieldSystemNodes.h" #include "GeometryCollectionProxyData.h" #include "GeometryCollection/GeometryCollectionSimulationTypes.h" #include "PhysicsProxy/PhysicsProxies.h" #include "Chaos/ErrorReporter.h" #include "Chaos/PBDRigidClustering.h" #include "PBDRigidsSolver.h" #include "ChaosSolversModule.h" #include "HeadlessChaosTestUtility.h" //#include "GeometryCollection/GeometryCollectionAlgo.h" #define SMALL_THRESHOLD 1e-4 // #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; GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_KinematicActivationOnProxyDuringInit) { const FVector Translation0(0, 0, 1); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Kinematic; Params.RootTransform.SetLocation(Translation0); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); Params.RootTransform.SetLocation(FVector(100, 0, 0)); FGeometryCollectionWrapper* CollectionOther = TNewSimulationObject::Init(Params)->template As(); FFramework UnitTest; UnitTest.AddSimulationObject(CollectionOther); UnitTest.AddSimulationObject(Collection); FRadialIntMask* RadialMaskTmp = new FRadialIntMask(); RadialMaskTmp->Position = FVector(0.0, 0.0, 0.0); RadialMaskTmp->Radius = 100.0; RadialMaskTmp->InteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMaskTmp->ExteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic; RadialMaskTmp->SetMaskCondition = ESetMaskConditionType::Field_Set_IFF_NOT_Interior; FName TargetNameTmp = GetFieldPhysicsName(EFieldPhysicsType::Field_DynamicState); Collection->PhysObject->BufferFieldCommand_Internal(UnitTest.Solver, { TargetNameTmp, RadialMaskTmp }); UnitTest.Initialize(); UnitTest.Advance(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { EXPECT_EQ(CollectionOther->DynamicCollection->DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic); EXPECT_EQ(Collection->DynamicCollection->DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic); }); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_KinematicActivationOnProxyDuringUpdate) { const FVector Translation0(0, 0, 1); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Kinematic; Params.RootTransform.SetLocation(Translation0); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); FFramework UnitTest; UnitTest.Dt = 1 / 24.0; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); { UnitTest.Advance(); } TManagedArray& DynamicState = Collection->DynamicCollection->DynamicState; EXPECT_EQ(DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic); // simulated EXPECT_EQ(Collection->DynamicCollection->GetNumTransforms(), 1); const FVector Translation1 = FVector(Collection->DynamicCollection->GetTransform(0).GetTranslation()); EXPECT_NEAR((Translation0 - Translation1).Size(), 0.f, KINDA_SMALL_NUMBER); EXPECT_NEAR(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 1.f, KINDA_SMALL_NUMBER); FRadialIntMask* RadialMask = new FRadialIntMask(); RadialMask->Position = FVector(0.0, 0.0, 0.0); RadialMask->Radius = 100.0; RadialMask->InteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMask->ExteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic; RadialMask->SetMaskCondition = ESetMaskConditionType::Field_Set_IFF_NOT_Interior; FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_DynamicState); Collection->PhysObject->BufferFieldCommand_Internal(UnitTest.Solver, { TargetName, RadialMask }); { UnitTest.Advance(); } EXPECT_EQ(DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic); const FVector Translation2 = FVector(Collection->DynamicCollection->GetTransform(0).GetTranslation()); EXPECT_NE(Translation1, Translation2); EXPECT_LE(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 0.f); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_KinematicActivation) { const FVector Translation0(0, 0, 1); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Kinematic; Params.RootTransform.SetLocation(Translation0); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); FFramework UnitTest; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); for (int i = 0; i < 100; i++) { UnitTest.Advance(); } // simulated EXPECT_EQ(Collection->DynamicCollection->GetNumTransforms(), 1); const FVector Translation1 = FVector(Collection->DynamicCollection->GetTransform(0).GetTranslation()); EXPECT_NEAR((Translation0 - Translation1).Size(), 0.f, KINDA_SMALL_NUMBER); EXPECT_NEAR(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 1.f, KINDA_SMALL_NUMBER); FRadialIntMask* RadialMask = new FRadialIntMask(); RadialMask->Position = FVector(0.0, 0.0, 0.0); RadialMask->Radius = 100.0; RadialMask->InteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMask->ExteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic; RadialMask->SetMaskCondition = ESetMaskConditionType::Field_Set_IFF_NOT_Interior; FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_DynamicState); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, RadialMask }); for (int i = 0; i < 100; i++) { UnitTest.Advance(); } const FVector Translation2 = FVector(Collection->DynamicCollection->GetTransform(0).GetTranslation()); EXPECT_NE(Translation1, Translation2); EXPECT_LE(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 0.f); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_InitialLinearVelocity) { FFramework UnitTest; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Kinematic; Params.RootTransform.SetLocation(FVector(0.0, 0.0, 0.0)); Params.InitialVelocityType = EInitialVelocityTypeEnum::Chaos_Initial_Velocity_User_Defined; Params.InitialLinearVelocity = FVector3f(0.f, 100.f, 0.f); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FRadialIntMask* RadialMask = new FRadialIntMask(); RadialMask->Position = FVector(0.0, 0.0, 0.0); RadialMask->Radius = 5.0f; RadialMask->InteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMask->ExteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMask->SetMaskCondition = ESetMaskConditionType::Field_Set_Always; UnitTest.Initialize(); TManagedArray& DynamicState = Collection->DynamicCollection->DynamicState; FReal PreviousY = 0.f; EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, 0); EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y, 0); for (int Frame = 0; Frame < 10; Frame++) { UnitTest.Advance(); if (Frame == 1) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_DynamicState); Collection->PhysObject->BufferFieldCommand_Internal(UnitTest.Solver, { TargetName, RadialMask }); } if (Frame >= 2) { EXPECT_EQ(DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic); EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, 0); EXPECT_GT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y, PreviousY); EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 0); } else { EXPECT_EQ(DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic); EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, 0); EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y, 0); EXPECT_EQ(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 0); } PreviousY = Collection->DynamicCollection->GetTransform(0).GetTranslation().Y; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_StayDynamic) { FFramework UnitTest; FReal PreviousHeight = 5.0; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Static; Params.RootTransform.SetLocation(FVector(0.f, 0.f, PreviousHeight)); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FRadialIntMask* RadialMask = new FRadialIntMask(); RadialMask->Position = FVector(0.0, 0.0, PreviousHeight); RadialMask->Radius = 5.0; RadialMask->InteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic; RadialMask->ExteriorValue = (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic; RadialMask->SetMaskCondition = ESetMaskConditionType::Field_Set_IFF_NOT_Interior; UnitTest.Initialize(); for (int Frame = 0; Frame < 10; Frame++) { // Set everything inside the r=5.0 sphere to dynamic if (Frame == 5) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_DynamicState); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, RadialMask }); } UnitTest.Advance(); if (Frame < 5) { // Before frame 5 nothing should have moved EXPECT_LT(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z - 5.f), SMALL_THRESHOLD); } else { // Frame 5 and after should be falling EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, PreviousHeight); } // Track current height of the object PreviousHeight = Collection->DynamicCollection->GetTransform(0).GetTranslation().Z; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_LinearForce) { FFramework UnitTest; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(0.f, 0.f, 5.f)); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FUniformVector* UniformVector = new FUniformVector(); UniformVector->Direction = FVector(0.0, 1.0, 0.0); UniformVector->Magnitude = 1000.0; UnitTest.Initialize(); FReal PreviousY = 0.0; for (int Frame = 0; Frame < 10; Frame++) { if (Frame >= 5) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, UniformVector->NewCopy() }); } UnitTest.Advance(); if (Frame < 5) { EXPECT_LT(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y), SMALL_THRESHOLD); } else { EXPECT_GT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y, PreviousY); } PreviousY = Collection->DynamicCollection->GetTransform(0).GetTranslation().Y; } delete UniformVector; } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_Torque) { FFramework UnitTest; // Physics Object Setup FVector Scale = FVector(10.0); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(0.f, 0.f, 5.f)); Params.GeomTransform.SetScale3D(Scale); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FUniformVector* UniformVector = new FUniformVector(); UniformVector->Direction = FVector(0.0, 1.0, 0.0); UniformVector->Magnitude = 100.0; UnitTest.Initialize(); FReal PreviousY = 0.0; for (int Frame = 0; Frame < 10; Frame++) { if (Frame >= 5) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_AngularTorque); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, UniformVector->NewCopy() }); } UnitTest.Advance(); auto& Particles = UnitTest.Solver->GetParticles().GetGeometryCollectionParticles(); if (Frame < 5) { EXPECT_LT(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetRotation().Euler().Y), SMALL_THRESHOLD); } else { EXPECT_NE(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetRotation().Euler().Y), SMALL_THRESHOLD); EXPECT_GT(Particles.GetW(0).Y, PreviousY); } PreviousY = Particles.GetW(0).Y; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_Kill) { FFramework UnitTest; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(0.f, 0.f, 20.f)); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FPlaneFalloff* FalloffField = new FPlaneFalloff(); FalloffField->Magnitude = 1.0; FalloffField->Distance = 10.0f; FalloffField->Position = FVector(0.0, 0.0, 5.0); FalloffField->Normal = FVector(0.0, 0.0, 1.0); FalloffField->Falloff = EFieldFalloffType::Field_Falloff_Linear; UnitTest.Initialize(); TManagedArray& Active = Collection->DynamicCollection->Active; auto& Particles = UnitTest.Solver->GetParticles().GetGeometryCollectionParticles(); for (int Frame = 0; Frame < 20; Frame++) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_Kill); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); if (Particles.Disabled(0)) { break; } } EXPECT_EQ(Particles.Disabled(0), true); // hasn't fallen any further than this due to being disabled EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, 5.f); EXPECT_GT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, -5.0f); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_LinearVelocity) { FFramework UnitTest; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(0.f, 0.f, 20.f)); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Field setup FUniformVector* VectorField = new FUniformVector(); VectorField->Magnitude = 100.0; VectorField->Direction = FVector(1.0, 0.0, 0.0); UnitTest.Initialize(); FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_LinearVelocity); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, VectorField->NewCopy() }); UnitTest.Advance(); FReal PreviousX = 0.0; for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearVelocity), VectorField->NewCopy() }); UnitTest.Advance(); EXPECT_GT(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, PreviousX); PreviousX = Collection->DynamicCollection->GetTransform(0).GetTranslation().X; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_CollisionGroup) { /** * Create a stack of boxes on the ground and verify that we we change their collision * group, they drop through the ground. */ FFramework UnitTest; UnitTest.Dt = 1 / 24.0; RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); UnitTest.AddSimulationObject(Floor); // Generate Geometry - a stack of boxes. // The bottom box is on the ground, and the others are dropped into it. CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; FVector Scale = FVector(100); Params.GeomTransform.SetScale3D(Scale); FGeometryCollectionWrapper* Collection[4]; for (int n = 0; n < 3; n++) { Params.RootTransform.SetLocation(FVector(0.f, 0.f, n * 200.0f + 100.0f)); Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Collection[n + 1] = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection[n + 1]); } // Field setup FRadialIntMask* RadialMask = new FRadialIntMask(); RadialMask->Position = FVector(0.0, 0.0, 0.0); RadialMask->Radius = 0; RadialMask->InteriorValue = -1; RadialMask->ExteriorValue = -1; RadialMask->SetMaskCondition = ESetMaskConditionType::Field_Set_Always; UnitTest.Initialize(); for (int Frame = 0; Frame < 60; Frame++) { UnitTest.Advance(); auto& Particles = UnitTest.Solver->GetParticles().GetGeometryCollectionParticles(); if (Frame == 30) { // The boxes should have landed on each other and settled by now EXPECT_NEAR(Collection[1]->DynamicCollection->GetTransform(0).GetTranslation().Z, (FReal)100, (FReal)20); EXPECT_NEAR(Collection[2]->DynamicCollection->GetTransform(0).GetTranslation().Z, (FReal)300, (FReal)20); EXPECT_NEAR(Collection[3]->DynamicCollection->GetTransform(0).GetTranslation().Z, (FReal)500, (FReal)20); } if (Frame == 31) { EFieldPhysicsType PhysicsType = GetGeometryCollectionPhysicsType(EGeometryCollectionPhysicsTypeEnum::Chaos_CollisionGroup); Collection[1]->PhysObject->BufferFieldCommand_Internal(UnitTest.Solver, { PhysicsType, RadialMask }); } } // The bottom boxes should have fallen below the ground level, box 2 now on the ground with box 3 on top auto& Particles = UnitTest.Solver->GetParticles().GetGeometryCollectionParticles(); EXPECT_LT(Collection[1]->DynamicCollection->GetTransform(0).GetTranslation().Z, 0); EXPECT_TRUE(FMath::IsNearlyEqual((FReal)Collection[2]->DynamicCollection->GetTransform(0).GetTranslation().Z, (FReal)100, (FReal)20)); EXPECT_TRUE(FMath::IsNearlyEqual((FReal)Collection[3]->DynamicCollection->GetTransform(0).GetTranslation().Z, (FReal)300, (FReal)20)); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_ClusterBreak_StrainModel_Test1) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_TwoByTwo_ThreeTransform(FVector(0)); 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 = { 1.0 }; Params.MaxClusterLevel = 1000; Params.ClusterConnectionMethod = Chaos::FClusterCreationParameters::EConnectionMethod::DelaunayTriangulation; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = 1.5; FalloffField->Radius = 100.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); UnitTest.Advance(); { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_ExternalClusterStrain); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); EXPECT_EQ(ClusterMap.Num(), 3); EXPECT_EQ(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Num(), 2); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Contains(Collection->PhysObject->GetParticle_Internal(0))); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Contains(Collection->PhysObject->GetParticle_Internal(1))); EXPECT_EQ(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Num(), 2); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Contains(Collection->PhysObject->GetParticle_Internal(2))); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Contains(Collection->PhysObject->GetParticle_Internal(3))); EXPECT_EQ(ClusterMap[Collection->PhysObject->GetParticle_Internal(6)].Num(), 2); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(6)].Contains(Collection->PhysObject->GetParticle_Internal(5))); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(6)].Contains(Collection->PhysObject->GetParticle_Internal(4))); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(0)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(1)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(2)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(3)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(4)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(5)->Disabled()); EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(6)->Disabled()); UnitTest.Advance(); // todo: indices here might seem odd, particles 4 & 5 are swapped EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_EQ(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Num(), 2); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Contains(Collection->PhysObject->GetParticle_Internal(0))); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(4)].Contains(Collection->PhysObject->GetParticle_Internal(1))); EXPECT_EQ(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Num(), 2); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Contains(Collection->PhysObject->GetParticle_Internal(2))); EXPECT_TRUE(ClusterMap[Collection->PhysObject->GetParticle_Internal(5)].Contains(Collection->PhysObject->GetParticle_Internal(3))); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(0)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(1)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(2)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(3)->Disabled()); EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(4)->Disabled()); EXPECT_FALSE(Collection->PhysObject->GetParticle_Internal(5)->Disabled()); EXPECT_TRUE(Collection->PhysObject->GetParticle_Internal(6)->Disabled()); } delete FalloffField; } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_ClusterBreak_StrainModel_Test2) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_ThreeByTwo_ThreeTransform(FVector(0)); 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 = { 1.0 }; Params.MaxClusterLevel = 1000; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = 1.5; FalloffField->Radius = 200.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); UnitTest.Advance(); auto ParticleHandles = [&Collection](int32 Index) -> Chaos::TPBDRigidClusteredParticleHandle* { return Collection->PhysObject->GetParticle_Internal(Index); }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_ExternalClusterStrain); FFieldSystemCommand Command(TargetName, FalloffField->NewCopy()); FFieldSystemMetaDataProcessingResolution* ResolutionData = new FFieldSystemMetaDataProcessingResolution(EFieldResolutionType::Field_Resolution_Maximum); Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution, TUniquePtr< FFieldSystemMetaDataProcessingResolution >(ResolutionData)); UnitTest.Solver->GetPerSolverField().AddTransientCommand(Command); EXPECT_EQ(ClusterMap.Num(), 3); 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(), 2); EXPECT_TRUE(ClusterMap[ParticleHandles(8)].Contains(ParticleHandles(7))); EXPECT_TRUE(ClusterMap[ParticleHandles(8)].Contains(ParticleHandles(6))); 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()); UnitTest.Advance(); UnitTest.Solver->GetPerSolverField().AddTransientCommand(Command); UnitTest.Advance(); EXPECT_EQ(ClusterMap.Num(), 1); 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_FALSE(ParticleHandles(0)->Disabled()); EXPECT_FALSE(ParticleHandles(1)->Disabled()); EXPECT_FALSE(ParticleHandles(2)->Disabled()); EXPECT_TRUE(ParticleHandles(3)->Disabled()); EXPECT_TRUE(ParticleHandles(4)->Disabled()); EXPECT_TRUE(ParticleHandles(5)->Disabled()); EXPECT_TRUE(ParticleHandles(6)->Disabled()); EXPECT_FALSE(ParticleHandles(7)->Disabled()); EXPECT_TRUE(ParticleHandles(8)->Disabled()); } delete FalloffField; } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_ClusterBreak_StrainModel_Test3) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_ThreeByTwo_ThreeTransform(FVector(0)); 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 = { 1.0 }; Params.MaxClusterLevel = 1000; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = 1.1; FalloffField->Radius = 200.0; FalloffField->Position = FVector(350.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); UnitTest.Advance(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); auto ParticleHandles = [&Collection](int32 Index) -> Chaos::TPBDRigidClusteredParticleHandle* { return Collection->PhysObject->GetParticle_Internal(Index); }; UnitTest.Advance(); { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_ExternalClusterStrain); FFieldSystemCommand Command(TargetName, FalloffField->NewCopy()); FFieldSystemMetaDataProcessingResolution* ResolutionData = new FFieldSystemMetaDataProcessingResolution(EFieldResolutionType::Field_Resolution_Maximum); Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution, TUniquePtr< FFieldSystemMetaDataProcessingResolution >(ResolutionData)); UnitTest.Solver->GetPerSolverField().AddTransientCommand(Command); 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()); UnitTest.Advance(); // todo: indices here might be off but the test crashes before this so we can't validate yet EXPECT_EQ(ClusterMap.Num(), 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(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_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_FALSE(ParticleHandles(6)->Disabled()); EXPECT_FALSE(ParticleHandles(7)->Disabled()); EXPECT_TRUE(ParticleHandles(8)->Disabled()); } delete FalloffField; } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_ClusterBreak_StrainModel_Test4) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_TwoByTwo_ThreeTransform(FVector(0)); 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 = { 1.0 }; Params.MaxClusterLevel = 1000; Params.ClusterGroupIndex = 0; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = 1.5; FalloffField->Radius = 100.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); UnitTest.Advance(); auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); 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), }; { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_ExternalClusterStrain); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); 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_FALSE(ParticleHandles[6]->Disabled()); UnitTest.Advance(); EXPECT_EQ(ClusterMap.Num(), 2); EXPECT_EQ(ClusterMap[ParticleHandles[4]].Num(), 2); EXPECT_TRUE(ClusterMap[ParticleHandles[4]].Contains(ParticleHandles[0])); EXPECT_TRUE(ClusterMap[ParticleHandles[4]].Contains(ParticleHandles[1])); EXPECT_EQ(ClusterMap[ParticleHandles[5]].Num(), 2); EXPECT_TRUE(ClusterMap[ParticleHandles[5]].Contains(ParticleHandles[2])); EXPECT_TRUE(ClusterMap[ParticleHandles[5]].Contains(ParticleHandles[3])); EXPECT_TRUE(ParticleHandles[0]->Disabled()); EXPECT_TRUE(ParticleHandles[1]->Disabled()); EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_TRUE(ParticleHandles[3]->Disabled()); EXPECT_FALSE(ParticleHandles[4]->Disabled()); EXPECT_FALSE(ParticleHandles[5]->Disabled()); EXPECT_TRUE(ParticleHandles[6]->Disabled()); } delete FalloffField; } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_ClusterBreak_StrainModel_TestResolutionMinimal) { FFramework UnitTest; TSharedPtr RestCollection = CreateClusteredBody_ThreeByTwo_ThreeTransform(FVector(0)); 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 = { 101.0, 103.0}; Params.MaxClusterLevel = 10; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); // field resolution is minimal by default FalloffField->Magnitude = -100.0; FalloffField->Radius = 10000.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); 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*> ClusteredParticleHandles = { 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), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); UnitTest.Solver->RegisterSimOneShotCallback([&]() { 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), }; ClusteredParticleHandles = { 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), }; EXPECT_EQ(ClusterMap.Num(), 3); EXPECT_EQ(ClusteredParticleHandles[1]->GetInternalStrains(), 101); }); FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_InternalClusterStrain); FalloffField->Magnitude = 0.0; UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); { // { 101.0, 103.0 } 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()); FalloffField->Magnitude = -100; UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); // { 1.0, 3.0 } 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()); // Parent still clustered w/ threshold > 0 EXPECT_EQ(ClusterMap.Num(), 3); 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(), 2); EXPECT_TRUE(ClusterMap[ParticleHandles[8]].Contains(ParticleHandles[6])); EXPECT_TRUE(ClusterMap[ParticleHandles[8]].Contains(ParticleHandles[7])); FalloffField->Magnitude = -1.0; UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); // { 0.0, 2.0 } broken EXPECT_FALSE(ParticleHandles[6]->Disabled()); EXPECT_FALSE(ParticleHandles[7]->Disabled()); EXPECT_TRUE(ParticleHandles[8]->Disabled()); FalloffField->Magnitude = -103; UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, FalloffField->NewCopy() }); UnitTest.Advance(); // { 0.0, 0.0 } EXPECT_FALSE(ParticleHandles[0]->Disabled()); EXPECT_FALSE(ParticleHandles[1]->Disabled()); EXPECT_FALSE(ParticleHandles[2]->Disabled()); EXPECT_FALSE(ParticleHandles[3]->Disabled()); EXPECT_FALSE(ParticleHandles[4]->Disabled()); EXPECT_FALSE(ParticleHandles[5]->Disabled()); EXPECT_TRUE(ParticleHandles[6]->Disabled()); EXPECT_TRUE(ParticleHandles[7]->Disabled()); EXPECT_TRUE(ParticleHandles[8]->Disabled()); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_Algebra1) { FFramework UnitTest; FVector ExpectedLocation = FVector(0.0f, 0.0f, 0.0f); FReal LastZ = ExpectedLocation.Z; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(ExpectedLocation); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Opposing Fields setup (velocity) FUniformVector* VectorFieldRight = new FUniformVector(); VectorFieldRight->Magnitude = 100.0; VectorFieldRight->Direction = FVector(1.0, 0.0, 0.0); FUniformVector* VectorFieldLeft = new FUniformVector(); VectorFieldLeft->Magnitude = 100.0; VectorFieldLeft->Direction = FVector(-1.0f, 0.0, 0.0); // Opposing Fields setup (force) FUniformVector* VectorFieldForward = new FUniformVector(); VectorFieldRight->Magnitude = 100.0; VectorFieldRight->Direction = FVector(0.0, 100.0, 0.0); FUniformVector* VectorFieldBackward = new FUniformVector(); VectorFieldLeft->Magnitude = 100.0; VectorFieldLeft->Direction = FVector(0.0f, -100.0, 0.0); UnitTest.Initialize(); FName TargetNameVel = GetFieldPhysicsName(EFieldPhysicsType::Field_LinearVelocity); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetNameVel, VectorFieldRight->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetNameVel, VectorFieldLeft->NewCopy() }); FName TargetNameForce = GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetNameForce, VectorFieldForward->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetNameForce, VectorFieldBackward->NewCopy() }); UnitTest.Advance(); for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearVelocity), VectorFieldRight->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearVelocity), VectorFieldLeft->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce), VectorFieldForward->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce), VectorFieldBackward->NewCopy() }); UnitTest.Advance(); EXPECT_NEAR(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, ExpectedLocation.X, KINDA_SMALL_NUMBER); EXPECT_NEAR(Collection->DynamicCollection->GetTransform(0).GetTranslation().Y, ExpectedLocation.Y, KINDA_SMALL_NUMBER); EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, ExpectedLocation.Z); EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, LastZ); LastZ = Collection->DynamicCollection->GetTransform(0).GetTranslation().Z; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_Algebra2) { FFramework UnitTest; FVector ExpectedLocation = FVector(0.0f, 0.0f, 0.0f); FVector LastLocation = ExpectedLocation; // Physics Object Setup CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.InitialVelocityType = EInitialVelocityTypeEnum::Chaos_Initial_Velocity_User_Defined; Params.InitialLinearVelocity = FVector3f(100.0, 0.0, 0.0); Params.RootTransform.SetLocation(ExpectedLocation); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Opposing Fields setup (force) FUniformVector* VectorFieldRightForce = new FUniformVector(); VectorFieldRightForce->Magnitude = 100.0; VectorFieldRightForce->Direction = FVector(100.0, 0.0, 0.0); FUniformVector* VectorFieldLeftForce = new FUniformVector(); VectorFieldLeftForce->Magnitude = 100.0; VectorFieldLeftForce->Direction = FVector(-100.0f, 0.0, 0.0); UnitTest.Initialize(); UnitTest.Advance(); TArray*> ParticleHandles = { Collection->PhysObject->GetParticle_Internal(0), }; Chaos::TVector CurrV = ParticleHandles[0]->GetV(); for (int Frame = 1; Frame < 10; Frame++) { UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce), VectorFieldRightForce->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ GetFieldPhysicsName(EFieldPhysicsType::Field_LinearForce), VectorFieldLeftForce->NewCopy() }); UnitTest.Advance(); CurrV = ParticleHandles[0]->GetV(); EXPECT_NEAR(CurrV.X, Params.InitialLinearVelocity.X, KINDA_SMALL_NUMBER); // Velocity in +x EXPECT_GT(Collection->DynamicCollection->GetTransform(0).GetTranslation().X, LastLocation.X); // Pos in +x // Still falling? EXPECT_LT(Collection->DynamicCollection->GetTransform(0).GetTranslation().Z, LastLocation.Z); LastLocation = FVector(Collection->DynamicCollection->GetTransform(0).GetTranslation()); } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_Algebra3) { FFramework UnitTest; // Physics Object Setup FVector Scale = FVector(10.0); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.RootTransform.SetLocation(FVector(0.f, 0.f, 5.f)); Params.GeomTransform.SetScale3D(Scale); FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); // Fields setup... when both our applied, we expect Y rotation to cancel, while X rotation > 0. FUniformVector* UniformVector1 = new FUniformVector(); UniformVector1->Direction = FVector(2.0, 1.0, 0.0); UniformVector1->Magnitude = 100.0; FUniformVector* UniformVector2 = new FUniformVector(); UniformVector2->Direction = FVector(-1.0, -1.0, 0.0); UniformVector2->Magnitude = 100.0; UnitTest.Initialize(); FReal PreviousHeight = Collection->DynamicCollection->GetTransform(0).GetTranslation().Z; FReal PreviousX = Collection->DynamicCollection->GetTransform(0).GetRotation().Euler().X; for (int Frame = 0; Frame < 10; Frame++) { FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_AngularTorque); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, UniformVector1->NewCopy() }); UnitTest.Solver->GetPerSolverField().AddTransientCommand({ TargetName, UniformVector2->NewCopy() }); UnitTest.Advance(); Chaos::TPBDGeometryCollectionParticles& Particles = UnitTest.Solver->GetParticles().GetGeometryCollectionParticles(); EXPECT_NE(FMath::Abs(Collection->DynamicCollection->GetTransform(0).GetRotation().Euler().Y), SMALL_THRESHOLD); // not rotating in Y? EXPECT_GT(Particles.GetW(0).X, PreviousX); // rotating in X? EXPECT_LT(Particles.GetX(0).Z, PreviousHeight); // still falling? PreviousHeight = Particles.GetX(0).Z; PreviousX = Particles.GetW(0).X; } } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_FieldResolutionMinimal) { FFramework UnitTest; // Construct Rest Collection... TSharedPtr 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->Transform[0].SetTranslation(FVector3f(0.0f)); RestCollection->SimulationType[0] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[1] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[2] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[3] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[4] = FGeometryCollection::ESimulationTypes::FST_Rigid; GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 0, { 1 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 1, { 2 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 2, { 3 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 3, { 4 }); 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 = { 91.0f, 92.0f, 93.0f, 94.0f }; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = -100; FalloffField->Radius = 1000.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); 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), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); Chaos::TArrayCollectionArray& StrainArray = UnitTest.Solver->GetEvolution()->GetRigidClustering().GetStrainArray(); EXPECT_FALSE(ParticleHandles[0]->Disabled()); EXPECT_TRUE(ParticleHandles[1]->Disabled()); EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_TRUE(ParticleHandles[3]->Disabled()); EXPECT_TRUE(ParticleHandles[4]->Disabled()); FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_InternalClusterStrain); FFieldSystemCommand Command(TargetName, FalloffField->NewCopy()); FFieldSystemMetaDataProcessingResolution* ResolutionData = new FFieldSystemMetaDataProcessingResolution(EFieldResolutionType::Field_Resolution_Minimal); Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution, TUniquePtr< FFieldSystemMetaDataProcessingResolution >(ResolutionData)); UnitTest.Solver->GetPerSolverField().AddTransientCommand(Command); UnitTest.Advance(); // We expect only active clusters and their children to be affected by the field with Field_Resolution_Minimal EXPECT_GT(StrainArray[0], 0.0f); EXPECT_GT(StrainArray[1], 0.0f); EXPECT_GT(0.0f, StrainArray[2]); EXPECT_GT(0.0f, StrainArray[3]); } GTEST_TEST(AllTraits, GeometryCollection_RigidBodies_Field_FieldResolutionMaximum) { FFramework UnitTest; // Construct Rest Collection... TSharedPtr 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->Transform[0].SetTranslation(FVector3f(0.0f)); RestCollection->SimulationType[0] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[1] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[2] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[3] = FGeometryCollection::ESimulationTypes::FST_Clustered; RestCollection->SimulationType[4] = FGeometryCollection::ESimulationTypes::FST_Rigid; GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 0, { 1 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 1, { 2 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 2, { 3 }); GeometryCollectionAlgo::ParentTransforms(RestCollection.Get(), 3, { 4 }); 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 = { 91.0f, 92.0f, 93.0f, 94.0f }; FGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As(); UnitTest.AddSimulationObject(Collection); FRadialFalloff* FalloffField = new FRadialFalloff(); FalloffField->Magnitude = -100; FalloffField->Radius = 1000.0; FalloffField->Position = FVector(0.0, 0.0, 0.0); FalloffField->Falloff = EFieldFalloffType::Field_FallOff_None; UnitTest.Initialize(); 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), }; auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); Chaos::TArrayCollectionArray& StrainArray = UnitTest.Solver->GetEvolution()->GetRigidClustering().GetStrainArray(); EXPECT_FALSE(ParticleHandles[0]->Disabled()); EXPECT_TRUE(ParticleHandles[1]->Disabled()); EXPECT_TRUE(ParticleHandles[2]->Disabled()); EXPECT_TRUE(ParticleHandles[3]->Disabled()); EXPECT_TRUE(ParticleHandles[4]->Disabled()); FName TargetName = GetFieldPhysicsName(EFieldPhysicsType::Field_InternalClusterStrain); FFieldSystemCommand Command(TargetName, FalloffField->NewCopy()); FFieldSystemMetaDataProcessingResolution* ResolutionData = new FFieldSystemMetaDataProcessingResolution(EFieldResolutionType::Field_Resolution_Maximum); Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution, TUniquePtr< FFieldSystemMetaDataProcessingResolution >(ResolutionData)); UnitTest.Solver->GetPerSolverField().AddTransientCommand(Command); UnitTest.Advance(); UnitTest.Advance(); // We expect all clusters to be affected by the field with Field_Resolution_Maximum EXPECT_GT(0.0f, StrainArray[0]); EXPECT_GT(0.0f, StrainArray[1]); EXPECT_GT(0.0f, StrainArray[2]); EXPECT_GT(0.0f, StrainArray[3]); } }