/* * Copyright (c) Contributors to the Open 3D Engine Project * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysX { using PhysXJointsTest = PhysX::GenericPhysicsInterfaceTest; template AZStd::unique_ptr AddBodyColliderEntity( AzPhysics::SceneHandle sceneHandle, const AZ::Vector3& position, const AZ::Vector3& initialLinearVelocity, AZStd::shared_ptr jointConfig = nullptr, AZStd::shared_ptr jointGenericProperties = nullptr, AZStd::shared_ptr jointLimitProperties = nullptr) { const char* entityName = "testEntity"; auto entity = AZStd::make_unique(entityName); AZ::TransformConfig transformConfig; transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position); entity->CreateComponent()->SetConfiguration(transformConfig); auto colliderConfiguration = AZStd::make_shared(); auto boxShapeConfiguration = AZStd::make_shared(); auto boxColliderComponent = entity->CreateComponent(); boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfiguration, boxShapeConfiguration) }); AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_initialLinearVelocity = initialLinearVelocity; rigidBodyConfig.m_gravityEnabled = false; // Make lead body very heavy if (!jointConfig) { rigidBodyConfig.m_mass = 9999.0f; } entity->CreateComponent(rigidBodyConfig, sceneHandle); if (jointConfig) { jointConfig->m_followerEntity = entity->GetId(); JointGenericProperties defaultJointGenericProperties; JointLimitProperties defaultJointLimitProperties; entity->CreateComponent( *jointConfig, (jointGenericProperties)? *jointGenericProperties : defaultJointGenericProperties, (jointLimitProperties)? *jointLimitProperties : defaultJointLimitProperties); } entity->Init(); entity->Activate(); return entity; } AZ::Vector3 RunJointTest(AzPhysics::Scene* scene, AZ::EntityId followerEntityId) { AZ::Vector3 followerEndPosition(0.0f, 0.0f, 0.0f); //Run simulation for a while - bounces box once on force volume const float deltaTime = AzPhysics::SystemConfiguration::DefaultFixedTimestep; const AZ::u32 numSteps = 240; PhysX::TestUtils::UpdateScene(scene, deltaTime, numSteps); AZ::TransformBus::EventResult(followerEndPosition, followerEntityId, &AZ::TransformBus::Events::GetWorldTranslation); return followerEndPosition; } TEST_F(PhysXJointsTest, Joints_FixedJoint_BodiesAreConstrainedAndMoveTogether) { // Place lead on the right of follower, tie them together with fixed joint and send the lead moving to the right. // The follower should be pulled along if the fixed joint works. const AZ::Vector3 followerPosition(-1.0f, 0.0f, 0.0f); const AZ::Vector3 followerInitialLinearVelocity(0.0f, 0.0f, 0.0f); const AZ::Vector3 leadPosition(1.0f, 0.0f, 0.0f); const AZ::Vector3 leadInitialLinearVelocity(10.0f, 0.0f, 0.0f); const AZ::Transform jointLocalTransform = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 0.0f, 0.0f)); // Templated joint component type is irrelevant since joint component is not created for this invocation. auto leadEntity = AddBodyColliderEntity(m_testSceneHandle, leadPosition, leadInitialLinearVelocity); auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; auto followerEntity = AddBodyColliderEntity(m_testSceneHandle, followerPosition, followerInitialLinearVelocity, jointConfig); const AZ::Vector3 followerEndPosition = RunJointTest(m_defaultScene, followerEntity->GetId()); EXPECT_TRUE(followerEndPosition.GetX() > followerPosition.GetX()); } TEST_F(PhysXJointsTest, Joint_HingeJoint_FollowerSwingsAroundLead) { // Place lead on the right of follower, tie them together with hinge joint and send the follower moving up. // The follower should swing around the lead. const AZ::Vector3 followerPosition(-1.0f, 0.0f, 0.0f); const AZ::Vector3 followerInitialLinearVelocity(0.0f, 0.0f, 10.0f); const AZ::Vector3 leadPosition(1.0f, 0.0f, 0.0f); const AZ::Vector3 leadInitialLinearVelocity(0.0f, 0.0f, 0.0f); const AZ::Vector3 jointLocalPosition(1.0f, 0.0f, 0.0f); const AZ::Quaternion jointLocalRotation = AZ::Quaternion::CreateRotationZ(90.0f); const AZ::Transform jointLocalTransform = AZ::Transform::CreateFromQuaternionAndTranslation( jointLocalRotation, jointLocalPosition); // Templated joint component type is irrelevant since joint component is not created for this invocation. auto leadEntity = AddBodyColliderEntity(m_testSceneHandle, leadPosition, leadInitialLinearVelocity); auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; auto jointLimits = AZStd::make_shared (); jointLimits->m_isLimited = false; auto followerEntity = AddBodyColliderEntity(m_testSceneHandle, followerPosition, followerInitialLinearVelocity, jointConfig, nullptr, jointLimits); const AZ::Vector3 followerEndPosition = RunJointTest(m_defaultScene, followerEntity->GetId()); EXPECT_TRUE(followerEndPosition.GetX() > followerPosition.GetX()); EXPECT_TRUE(abs(followerEndPosition.GetZ()) > FLT_EPSILON); } TEST_F(PhysXJointsTest, Joint_BallJoint_FollowerSwingsUpAboutLead) { // Place lead on top of follower, tie them together with ball joint and send the follower moving sideways in the X and Y directions. // The follower should swing up about lead. const AZ::Vector3 followerPosition(0.0f, 0.0f, -1.0f); const AZ::Vector3 followerInitialLinearVelocity(10.0f, 10.0f, 0.0f); const AZ::Vector3 leadPosition(0.0f, 0.0f, 1.0f); const AZ::Vector3 leadInitialLinearVelocity(0.0f, 0.0f, 0.0f); const AZ::Vector3 jointLocalPosition(0.0f, 0.0f, 2.0f); const AZ::Quaternion jointLocalRotation = AZ::Quaternion::CreateRotationY(90.0f); const AZ::Transform jointLocalTransform = AZ::Transform::CreateFromQuaternionAndTranslation( jointLocalRotation, jointLocalPosition); // Templated joint component type is irrelevant since joint component is not created for this invocation. auto leadEntity = AddBodyColliderEntity(m_testSceneHandle, leadPosition, leadInitialLinearVelocity); auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; auto jointLimits = AZStd::make_shared (); jointLimits->m_isLimited = false; auto followerEntity = AddBodyColliderEntity(m_testSceneHandle, followerPosition, followerInitialLinearVelocity, jointConfig, nullptr, jointLimits); const AZ::Vector3 followerEndPosition = RunJointTest(m_defaultScene, followerEntity->GetId()); EXPECT_TRUE(followerEndPosition.GetZ() > followerPosition.GetZ()); } // for some reason TYPED_TEST_CASE with the fixture is not working on Android + Linux #ifdef ENABLE_JOINTS_TYPED_TEST_CASE template class PhysXJointsApiTest : public PhysX::GenericPhysicsInterfaceTest { public: void SetUp() override { PhysX::GenericPhysicsInterfaceTest::SetUp(); if (auto* sceneInterface = AZ::Interface::Get()) { AzPhysics::RigidBodyConfiguration parentConfiguration; AzPhysics::RigidBodyConfiguration childConfiguration; auto colliderConfig = AZStd::make_shared(); auto shapeConfiguration = AZStd::make_shared(AZ::Vector3(1.0f, 1.0f, 1.0f)); parentConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration); childConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration); // Put the child body a bit to the lower side of X to avoid it colliding with parent childConfiguration.m_position.SetX(childConfiguration.m_position.GetX() - 2.0f); m_childInitialPos = childConfiguration.m_position; parentConfiguration.m_initialLinearVelocity.SetX(10.0f); m_parentBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &parentConfiguration); m_childBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &childConfiguration); } } void TearDown() override { if (auto* sceneInterface = AZ::Interface::Get()) { sceneInterface->RemoveSimulatedBody(m_testSceneHandle, m_parentBodyHandle); sceneInterface->RemoveSimulatedBody(m_testSceneHandle, m_childBodyHandle); } PhysX::GenericPhysicsInterfaceTest::TearDown(); } AzPhysics::SimulatedBodyHandle m_parentBodyHandle = AzPhysics::InvalidJointHandle; AzPhysics::SimulatedBodyHandle m_childBodyHandle = AzPhysics::InvalidJointHandle; AZ::Vector3 m_childInitialPos; }; using JointTypes = testing::Types< D6JointLimitConfiguration, FixedJointConfiguration, BallJointConfiguration, HingeJointConfiguration>; TYPED_TEST_CASE(PhysXJointsApiTest, JointTypes); TYPED_TEST(PhysXJointsApiTest, Joint_ChildFollowsParent) { TypeParam jointConfiguration; AzPhysics::JointHandle jointHandle = AzPhysics::InvalidJointHandle; if (auto* sceneInterface = AZ::Interface::Get()) { jointHandle = sceneInterface->AddJoint(m_testSceneHandle, &jointConfiguration, m_parentBodyHandle, m_childBodyHandle); } EXPECT_NE(jointHandle, AzPhysics::InvalidJointHandle); // run physics to trigger the the move of parent body TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); AZ::Vector3 childCurrentPos; if (auto* sceneInterface = AZ::Interface::Get()) { auto* childBody = sceneInterface->GetSimulatedBodyFromHandle(m_testSceneHandle, m_childBodyHandle); childCurrentPos = childBody->GetPosition(); } EXPECT_GT(childCurrentPos.GetX(), m_childInitialPos.GetX()); } #endif // ENABLE_JOINTS_TYPED_TEST_CASE }