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

135 lines
5.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HeadlessChaos.h"
#include "HeadlessChaosTestJoint.h"
#include "Modules/ModuleManager.h"
#include "Chaos/ParticleHandle.h"
namespace ChaosTest {
using namespace Chaos;
// Create a joint between a kinematic and dynamic body set up as a linear spring (using the drive)
// and wait for it to sleep. Then move the kinematic and verify that the dynamic gets woken.
GTEST_TEST(JointTests, TestWakeOnKinematicMove)
{
const int32 NumSolverIterations = 10;
const FReal Gravity = 1000;
const FReal Dt = 0.01f;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.EnableSleeping();
Test.InitChain(2, FVec3(0, 0, -1));
// Set up the joint as a critically damped spring with a known resting extension under gravity
const FReal Extension = 10;
const FReal Stiffness = Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness);
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, true };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, true };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Acceleration;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.Create();
FGenericParticleHandle P0 = Test.GetParticle(0);
FConstGenericParticleHandle P1 = Test.GetParticle(1);
FPBDJointConstraintHandle* J01 = Test.GetJoint(0);
// Run the sim till the dynamic sleeps (should be less than 1s of sim time)
const int32 MaxSteps = 200;
for (int32 Step = 0; Step < MaxSteps; ++Step)
{
Test.Advance(Dt);
if (P1->IsSleeping())
{
break;
}
}
EXPECT_TRUE(P1->IsSleeping());
if (P1->IsSleeping())
{
// Verify that the joint we created is still in the constraint graph
// This was the source of a wake bug in the past
EXPECT_TRUE(J01->IsInConstraintGraph());
// Move the kinematic and tick
const FKinematicTarget KinematicTarget = FKinematicTarget::MakePositionTarget(FRigidTransform3(FVec3(50, 0, 0), FRotation3::FromIdentity()));
Test.Evolution.SetParticleKinematicTarget(P0->CastToKinematicParticle(), KinematicTarget);
Test.Advance(Dt);
EXPECT_FALSE(P1->IsSleeping());
}
}
// Verify that a joint between 2 kinematics is removed from the graph anmd re-added
// if one of the particles becomes dynamic again.
GTEST_TEST(JointTests, TestKinematicKinematicGraph)
{
const int32 NumSolverIterations = 10;
const FReal Gravity = 1000;
const FReal Dt = 0.01f;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.EnableSleeping();
Test.InitChain(2, FVec3(0, 0, -1));
// Set up the joint as a critically damped spring with a known resting extension under gravity
const FReal Extension = 10;
const FReal Stiffness = Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness);
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, true };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, true };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Acceleration;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.Create();
FGenericParticleHandle P0 = Test.GetParticle(0);
FGenericParticleHandle P1 = Test.GetParticle(1);
FPBDJointConstraintHandle* J01 = Test.GetJoint(0);
// Run the sim till the dynamic sleeps (should be less than 1s of sim time)
const int32 MaxSteps = 200;
for (int32 Step = 0; Step < MaxSteps; ++Step)
{
Test.Advance(Dt);
if (P1->IsSleeping())
{
break;
}
}
// Turn the dynamic into a kinematic and tick.
// The joint should still be removed from the graph.
// NOTE: This is different to the behaviour of collisions where we keep kinematic-kinematic collisions
// in the graph. That is because collisions are transient, but we want the collision to remain allocated
// for when the particle becomes dynamic again. We don't need this for Joints because the joints are
// persistent and we can just re-add them when the particle becomes dynamic again.
Test.Evolution.SetParticleObjectState(P1->CastToRigidParticle(), EObjectStateType::Kinematic);
Test.Advance(Dt);
EXPECT_TRUE(P1->IsKinematic());
EXPECT_FALSE(J01->IsInConstraintGraph());
// Now make the particle dynamic again.
// The joint should get re-added to the graph.
Test.Evolution.SetParticleObjectState(P1->CastToRigidParticle(), EObjectStateType::Dynamic);
Test.Advance(Dt);
EXPECT_TRUE(P1->IsDynamic());
EXPECT_FALSE(P1->IsSleeping());
EXPECT_TRUE(J01->IsInConstraintGraph());
}
}