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

604 lines
24 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;
// 1 Kinematic Body with 4 Dynamic bodies hanging from it by a breakable constraint.
// Verify that the force in each joint settles to about (Sum of Child Mass)xG
template <typename TEvolution>
void JointForces_Linear()
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
const bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 10;
const FReal Gravity = 980;
const FReal Dt = 0.01f;
const int32 NumSteps = 100;
const int32 NumBodies = 5;
FJointChainTest<TEvolution> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
Test.Create();
// Run the sim
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
}
for (int32 JointIndex = 0; JointIndex < Test.Evolution.GetJointCombinedConstraints().LinearConstraints.NumConstraints(); ++JointIndex)
{
FReal ChildMass = 0.0f;
for (int32 ChildBodyIndex = JointIndex + 1; ChildBodyIndex < Test.ParticleMasses.Num(); ++ChildBodyIndex)
{
ChildMass += Test.ParticleMasses[ChildBodyIndex];
}
FVec3 ExpectedLinearImpulse = FVec3(0.0f, 0.0f, -ChildMass * Gravity * Dt);
FVec3 LinearImpulse = Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(JointIndex);
EXPECT_NEAR(LinearImpulse.X, ExpectedLinearImpulse.X, ExpectedLinearImpulse.Size() / 100.0f);
EXPECT_NEAR(LinearImpulse.Y, ExpectedLinearImpulse.Y, ExpectedLinearImpulse.Size() / 100.0f);
EXPECT_NEAR(LinearImpulse.Z, ExpectedLinearImpulse.Z, ExpectedLinearImpulse.Size() / 100.0f);
}
}
}
GTEST_TEST(AllEvolutions, JointForcesTests_TestLinear)
{
JointForces_Linear<FPBDRigidsEvolutionGBF>();
}
// 1 Kinematic Body with 1 Dynamic bodies arranged horizontally.
// Verify that the force in the joint settles to about MxG
template <typename TEvolution>
void JointForces_Linear2()
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
const bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 40;
const FReal Gravity = 980;
const FReal Dt = 0.01f;
const int32 NumSteps = 100;
const int32 NumBodies = 3;
FJointChainTest<TEvolution> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(1, 0, 0));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
// Set all joints to fixed angular
for (int32 JointIndex = 0; JointIndex < Test.JointSettings.Num(); ++JointIndex)
{
Test.JointSettings[JointIndex].AngularMotionTypes = { EJointMotionType::Locked, EJointMotionType::Locked, EJointMotionType::Locked };
Test.JointSettings[JointIndex].bProjectionEnabled = false;
}
Test.Create();
// Run the sim
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
}
FReal L = Test.ParticlePositions[1].X - Test.ParticlePositions[0].X;
for (int32 JointIndex = 0; JointIndex < Test.Evolution.GetJointCombinedConstraints().NumConstraints(); ++JointIndex)
{
FReal ChildMass = 0.0f;
FReal ChildMoment = 0.0f;
for (int32 ChildBodyIndex = JointIndex + 1; ChildBodyIndex < Test.ParticleMasses.Num(); ++ChildBodyIndex)
{
ChildMass += Test.ParticleMasses[ChildBodyIndex];
FReal ChildL = (ChildBodyIndex - JointIndex) * L;
ChildMoment += Test.ParticleMasses[ChildBodyIndex] * ChildL;
}
FVec3 ExpectedLinearImpulse = FVec3(0.0f, 0.0f, -ChildMass * Gravity * Dt);
FVec3 LinearImpulse = Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(JointIndex);
EXPECT_NEAR(LinearImpulse.X, ExpectedLinearImpulse.X, ExpectedLinearImpulse.Size() / 100.0f);
EXPECT_NEAR(LinearImpulse.Y, ExpectedLinearImpulse.Y, ExpectedLinearImpulse.Size() / 100.0f);
EXPECT_NEAR(LinearImpulse.Z, ExpectedLinearImpulse.Z, ExpectedLinearImpulse.Size() / 100.0f);
FVec3 ExpectedAngularImpulse = FVec3(0.0f, ChildMoment * Gravity * Dt, 0.0f);
FVec3 AngularImpulse = Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintAngularImpulse(JointIndex);
EXPECT_NEAR(AngularImpulse.X, ExpectedAngularImpulse.X, ExpectedAngularImpulse.Size() / 100.0f);
EXPECT_NEAR(AngularImpulse.Y, ExpectedAngularImpulse.Y, ExpectedAngularImpulse.Size() / 100.0f);
EXPECT_NEAR(AngularImpulse.Z, ExpectedAngularImpulse.Z, ExpectedAngularImpulse.Size() / 100.0f);
}
}
}
GTEST_TEST(AllEvolutions, JointForcesTests_TestLinear2)
{
JointForces_Linear2<FPBDRigidsEvolutionGBF>();
}
// 1 Kinematic Body with 1 Dynamic bodies arranged vertically.
// Apply a torque to the body and verify that the joint torque is the same.
template <typename TEvolution>
void JointForces_Angular()
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 0;
const FReal Dt = 0.01f;
const int32 NumSteps = 100;
const int32 NumBodies = 2;
const FVec3 Torque = FVec3(10000, 0, 0);
FJointChainTest<TEvolution> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(1, 0, 0));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
// Set all joints to fixed angular and disable projection
for (int32 JointIndex = 0; JointIndex < Test.JointSettings.Num(); ++JointIndex)
{
Test.JointSettings[JointIndex].AngularMotionTypes = { EJointMotionType::Locked, EJointMotionType::Locked, EJointMotionType::Locked };
Test.JointSettings[JointIndex].bProjectionEnabled = false;
}
Test.Create();
// Run the sim
for (int32 i = 0; i < NumSteps; ++i)
{
Test.GetParticle(1)->CastToRigidParticle()->SetTorque(Torque);
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
}
for (int32 JointIndex = 0; JointIndex < Test.Evolution.GetJointCombinedConstraints().NumConstraints(); ++JointIndex)
{
FReal ChildMass = 0.0f;
for (int32 ChildBodyIndex = JointIndex + 1; ChildBodyIndex < Test.ParticleMasses.Num(); ++ChildBodyIndex)
{
ChildMass += Test.ParticleMasses[ChildBodyIndex];
}
FVec3 ExpectedAngularImpulse = Torque * Dt;
FVec3 AngularImpulse = Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintAngularImpulse(JointIndex);
EXPECT_NEAR(AngularImpulse.X, ExpectedAngularImpulse.X, ExpectedAngularImpulse.Size() / 100.0f);
EXPECT_NEAR(AngularImpulse.Y, ExpectedAngularImpulse.Y, ExpectedAngularImpulse.Size() / 100.0f);
EXPECT_NEAR(AngularImpulse.Z, ExpectedAngularImpulse.Z, ExpectedAngularImpulse.Size() / 100.0f);
}
}
}
GTEST_TEST(AllEvolutions, JointForcesTests_Angular)
{
JointForces_Angular<FPBDRigidsEvolutionGBF>();
}
// Check that a joint drive linear stiffness calculates the correct force F=-K.X assuming implicit integration
// NOTE: Uses Joint Force mode which isn't currently selectable from the editor
//
GTEST_TEST(JointForceTests, TestLinearDriveForceMode_Force)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 0;
const FReal Dt = 0.01;
const int32 NumBodies = 2;
const FReal Extension = 10;
const FReal Stiffness = 10000;
const FReal Damping = 0;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive in force mode
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, true };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, false };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Force;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.Create();
FGenericParticleHandle P1 = Test.GetParticle(1);
// Reposition the particle to have some extension in the spring
P1->InitTransform(P1->GetX() + FVec3(0, 0, -Extension), P1->GetR());
// Run the sim
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
// Calculate expected force from F = -K.X with implicit integration
const FReal M = Test.ParticleMasses[1];
FReal ExpectedForceZ = 0;
FReal DP = 0;
for (int32 It = 0; It < 10; ++It)
{
const FReal X = -Extension + DP;
const FReal F = -Stiffness * X;
const FReal DV = ((F - ExpectedForceZ) / M) * Dt;
DP += DV * Dt;
ExpectedForceZ = F;
}
// Check the joint forces agree
const FReal ForceZ = -Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(0).Z / Dt;
EXPECT_NEAR(ForceZ, ExpectedForceZ, 0.01);
}
}
// Check that a joint drive linear damping calculates the correct force F=-D.V assuming implicit integration
// NOTE: Uses Joint Force mode which isn't currently selectable from the editor
//
GTEST_TEST(JointForceTests, TestLinearDriveForceMode_Damping)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 0;
const FReal Dt = 0.01;
const int32 NumBodies = 2;
const FReal Velocity = 100;
const FReal Stiffness = 0;
const FReal Damping = 2000;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive in force mode
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, false };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, true };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Force;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.Create();
FGenericParticleHandle P1 = Test.GetParticle(1);
// Give the particle velocity so that joint damping has some work to do
P1->SetV(FVec3(0, 0, -Velocity));
// Run the sim
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
// Calculate expected force from F = -D.V with implicit integration
const FReal M = Test.ParticleMasses[1];
FReal ExpectedForceZ = 0;
FReal DP = 0;
for (int32 It = 0; It < NumSolverIterations; ++It)
{
const FReal V = -Velocity + DP / Dt;
const FReal F = -Damping * V;
const FReal DV = ((F - ExpectedForceZ) / M) * Dt;
DP += DV * Dt;
ExpectedForceZ = F;
}
// Check the joint forces agree
const FReal ForceZ = -Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(0).Z / Dt;
EXPECT_NEAR(ForceZ, ExpectedForceZ, 1);
}
}
GTEST_TEST(JointForceTests, TestAngularDriveForceMode_Damping)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 0;
const FReal Dt = 0.01;
const int32 NumBodies = 2;
const FReal AngularVelocity = 3;
const FReal Stiffness = 0;
const FReal Damping = 200;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive in force mode
Test.JointSettings[0].bAngularSLerpVelocityDriveEnabled = true;
Test.JointSettings[0].AngularDriveForceMode = EJointForceMode::Force;
Test.JointSettings[0].AngularDriveStiffness = FVec3(Stiffness, Stiffness, Stiffness);
Test.JointSettings[0].AngularDriveDamping = FVec3(Damping, Damping, Damping);
Test.Create();
FGenericParticleHandle P1 = Test.GetParticle(1);
// Give the particle angular velocity so that joint damping has some work to do
P1->SetW(FVec3(0, 0, -AngularVelocity));
// Run the sim
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
// Calculate expected Trque from T = -D.W with implicit integration
const FReal I = FConstGenericParticleHandle(Test.GetParticle(1))->I().Z;
FReal ExpectedTorqueZ = 0;
FReal DQ = 0;
for (int32 It = 0; It < NumSolverIterations; ++It)
{
const FReal W = -AngularVelocity + DQ / Dt;
const FReal T = -Damping * W;
const FReal DW = ((T - ExpectedTorqueZ) / I) * Dt;
DQ += DW * Dt;
ExpectedTorqueZ = T;
}
// Check the joint forces agree
const FReal TorqueZ = -Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintAngularImpulse(0).Z / Dt;
EXPECT_NEAR(TorqueZ, ExpectedTorqueZ, 0.1);
}
}
// Check that a hanging mass on a joint drive reaches the correct extension with the correct spring force when using Force mode.
// This is just a pre-test for TestLinearDriveForceMode_MaxForce to verify that everything works in the absense of a max force
//
GTEST_TEST(JointForceTests, TestLinearDriveForceMode_MaxForcePreTest)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 1000;
const FReal Dt = 0.01;
const int32 NumSteps = 100;
const int32 NumBodies = 2;
const FReal Extension = 10;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive without a force limit
const FReal Stiffness = Test.ParticleMasses[1] * Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness * Test.ParticleMasses[1]);
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, true };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, true };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Force;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.Create();
FConstGenericParticleHandle P1 = Test.GetParticle(1);
// Run the sim - the dangling box should reach a steady state
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
}
// We should be stationary at the desired extension
const FReal ExpectedVZ = 0;
const FReal ExpectedZ = Test.ParticlePositions[1].Z - Extension;
EXPECT_NEAR(P1->V().Z, ExpectedVZ, 1);
EXPECT_NEAR(P1->GetX().Z, ExpectedZ, 1);
}
}
// Check that the maximum drive force setting honored for linear drives.
// We repeat TestLinearDriveForceMode_MaxForcePreTest but with a max force which is less than K.X at the rest extension
//
GTEST_TEST(JointForceTests, TestLinearDriveForceMode_MaxForce)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 1000;
const FReal Dt = 0.01;
const int32 NumSteps = 100;
const int32 NumBodies = 2;
const FReal Extension = 10;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive with a force limit
const FReal Stiffness = Test.ParticleMasses[1] * Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness * Test.ParticleMasses[1]);
Test.JointSettings[0].bLinearPositionDriveEnabled = { false, false, true };
Test.JointSettings[0].bLinearVelocityDriveEnabled = { false, false, true };
Test.JointSettings[0].LinearDriveForceMode = EJointForceMode::Force;
Test.JointSettings[0].LinearDriveStiffness = FVec3(0, 0, Stiffness);
Test.JointSettings[0].LinearDriveDamping = FVec3(0, 0, Damping);
Test.JointSettings[0].LinearDriveMaxForce = FVec3(0, 0, 0.5 * Stiffness * Extension);
Test.Create();
FConstGenericParticleHandle P1 = Test.GetParticle(1);
// Run the sim - the dangling box should reach a steady state
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
const FReal ForceZ = -Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(0).Z / Dt;
EXPECT_LT(ForceZ, Test.JointSettings[0].LinearDriveMaxForce.Z + 0.1);
}
}
}
// Check that a handing mass on a joint drive reaches the correct extension with the correct spring force when using Acceleration mode.
// This is just a pre-test for TestLinearDriveForceMode_MaxForce to verify that everything works in the absense of a max force
// NOTE: Using Accleration mode on the drive but with adjusted stiffness and damping.
// Should yield same results as TestLinearDriveForceMode_MaxForcePreTest.
//
GTEST_TEST(JointForceTests, TestLinearDriveAccMode_MaxForcePreTest)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 1000;
const FReal Dt = 0.01;
const int32 NumSteps = 100;
const int32 NumBodies = 2;
const FReal Extension = 10;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive without a force limit
// NOTE: Acceleration mode - no masses in expressions
const FReal Stiffness = Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness);
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();
FConstGenericParticleHandle P1 = Test.GetParticle(1);
// Run the sim - the dangling box should reach a steady state
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
}
// We should be stationary at the desired extension
const FReal ExpectedVZ = 0;
const FReal ExpectedZ = Test.ParticlePositions[1].Z - Extension;
EXPECT_NEAR(P1->V().Z, ExpectedVZ, 1);
EXPECT_NEAR(P1->GetX().Z, ExpectedZ, 1);
}
}
// Check that the maximum drive force setting honored for linear drives.
// We repeat TestLinearDriveForceMode_MaxForcePreTest but with a max force which is less than K.X at the rest extension
// NOTE: Using Acceleration mode on the drive but with adjusted stiffness and damping.
//
GTEST_TEST(JointForceTests, TestLinearDriveAccMode_MaxForce)
{
for (int32 IndexSimd = 0; IndexSimd < 2; IndexSimd++)
{
bool bUseSimd = IndexSimd == 0;
const int32 NumSolverIterations = 20;
const FReal Gravity = 1000;
const FReal Dt = 0.01;
const int32 NumSteps = 100;
const int32 NumBodies = 2;
const FReal Extension = 10;
FJointChainTest<FPBDRigidsEvolutionGBF> Test(NumSolverIterations, Gravity);
Test.InitChain(NumBodies, FVec3(0, 0, -1));
Test.Evolution.GetJointCombinedConstraints().LinearConstraints.SetUseSimd(bUseSimd);
if (!Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetSettings().bUsePositionBasedDrives)
{
Test.Evolution.SetNumVelocityIterations(NumSolverIterations);
}
// Disable all limits
Test.JointSettings[0].LinearMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
Test.JointSettings[0].AngularMotionTypes = { EJointMotionType::Free, EJointMotionType::Free, EJointMotionType::Free };
// Set up the drive with a force limit
// NOTE: Acceleration mode - no masses in expressions
const FReal Stiffness = Gravity / Extension;
const FReal Damping = 2 * FMath::Sqrt(Stiffness);
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.JointSettings[0].LinearDriveMaxForce = FVec3(0, 0, 0.5 * Stiffness * Extension);
Test.Create();
FConstGenericParticleHandle P1 = Test.GetParticle(1);
// Run the sim - the dangling box should reach a steady state
for (int32 i = 0; i < NumSteps; ++i)
{
Test.Evolution.AdvanceOneTimeStep(Dt);
Test.Evolution.EndFrame(Dt);
const FReal ForceZ = -Test.Evolution.GetJointCombinedConstraints().LinearConstraints.GetConstraintLinearImpulse(0).Z / Dt;
EXPECT_LT(ForceZ, Test.JointSettings[0].LinearDriveMaxForce.Z * Test.ParticleMasses[1] + 0.1);
}
}
}
}