CanAddToSimulatedObject.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <gmock/gmock.h>
  9. #include <gtest/gtest.h>
  10. #include <QPushButton>
  11. #include <QAction>
  12. #include <QtTest>
  13. #include <AzCore/std/algorithm.h>
  14. #include <AzFramework/Physics/Character.h>
  15. #include <AzFramework/Physics/ShapeConfiguration.h>
  16. #include <Editor/ColliderContainerWidget.h>
  17. #include <Editor/Plugins/ColliderWidgets/RagdollOutlinerNotificationHandler.h>
  18. #include <Editor/Plugins/ColliderWidgets/SimulatedObjectColliderWidget.h>
  19. #include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.h>
  20. #include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
  21. #include <Editor/ReselectingTreeView.h>
  22. #include <EMotionStudio/EMStudioSDK/Source/PluginManager.h>
  23. #include <Tests/TestAssetCode/SimpleActors.h>
  24. #include <Tests/TestAssetCode/ActorFactory.h>
  25. #include <Tests/TestAssetCode/TestActorAssets.h>
  26. #include <Tests/UI/ModalPopupHandler.h>
  27. #include <Tests/UI/UIFixture.h>
  28. #include <Tests/D6JointLimitConfiguration.h>
  29. #include <Tests/Mocks/PhysicsSystem.h>
  30. namespace EMotionFX
  31. {
  32. class CanAddToSimulatedObjectFixture
  33. : public UIFixture
  34. {
  35. protected:
  36. virtual bool ShouldReflectPhysicSystem() override { return true; }
  37. };
  38. TEST_F(CanAddToSimulatedObjectFixture, CanAddExistingJointsAndUnaddedChildren)
  39. {
  40. RecordProperty("test_case_id", "C14603914");
  41. AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}");
  42. AZ::Data::Asset<Integration::ActorAsset> actorAsset =
  43. TestActorAssets::CreateActorAssetAndRegister<SimpleJointChainActor>(actorAssetId, 7, "CanAddToSimulatedObjectActor");
  44. Actor* actor = actorAsset->GetActor();
  45. ActorInstance* actorInstance = ActorInstance::Create(actor);
  46. // Change the Editor mode to Simulated Objects
  47. EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects");
  48. // Select the newly created actor instance
  49. AZStd::string result;
  50. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(AZStd::string{ "Select -actorInstanceID " } + AZStd::to_string(actorInstance->GetID()), result)) << result.c_str();
  51. EMotionFX::SkeletonOutlinerPlugin* skeletonOutliner = static_cast<EMotionFX::SkeletonOutlinerPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMotionFX::SkeletonOutlinerPlugin::CLASS_ID));
  52. EXPECT_TRUE(skeletonOutliner);
  53. ReselectingTreeView* skeletonTreeView = skeletonOutliner->GetDockWidget()->findChild<ReselectingTreeView*>("EMFX.SkeletonOutlinerPlugin.SkeletonOutlinerTreeView");
  54. EXPECT_TRUE(skeletonTreeView);
  55. const QAbstractItemModel* skeletonModel = skeletonTreeView->model();
  56. QModelIndexList indexList;
  57. skeletonTreeView->RecursiveGetAllChildren(skeletonModel->index(0, 0, skeletonModel->index(0, 0)), indexList);
  58. EXPECT_EQ(indexList.size(), 7);
  59. SelectIndexes(indexList, skeletonTreeView, 2, 4);
  60. // Bring up the contextMenu
  61. const QRect rect = skeletonTreeView->visualRect(indexList[3]);
  62. EXPECT_TRUE(rect.isValid());
  63. BringUpContextMenu(skeletonTreeView, rect);
  64. QMenu* contextMenu = skeletonOutliner->GetDockWidget()->findChild<QMenu*>("EMFX.SkeletonOutlinerPlugin.ContextMenu");
  65. EXPECT_TRUE(contextMenu);
  66. QAction* addSelectedJointAction;
  67. EXPECT_TRUE(UIFixture::GetActionFromContextMenu(addSelectedJointAction, contextMenu, "Add to simulated object"));
  68. QMenu* addSelectedJointMenu = addSelectedJointAction->menu();
  69. EXPECT_TRUE(addSelectedJointMenu);
  70. QAction* newSimulatedObjectAction;
  71. EXPECT_TRUE(UIFixture::GetActionFromContextMenu(newSimulatedObjectAction, addSelectedJointMenu, "New simulated object..."));
  72. // Handle the add children dialog box.
  73. ModalPopupHandler messageBoxPopupHandler;
  74. messageBoxPopupHandler.WaitForPopupPressDialogButton<QMessageBox*>(QDialogButtonBox::No);
  75. newSimulatedObjectAction->trigger();
  76. EMStudio::InputDialogValidatable* inputDialog = qobject_cast<EMStudio::InputDialogValidatable*>(FindTopLevelWidget("EMFX.SimulatedObjectActionManager.SimulatedObjectDialog"));
  77. ASSERT_NE(inputDialog, nullptr) << "Cannot find input dialog.";
  78. inputDialog->SetText("TestObj");
  79. inputDialog->accept();
  80. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  81. ASSERT_EQ(actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 1);
  82. const auto simulatedObject = actor->GetSimulatedObjectSetup()->GetSimulatedObject(0);
  83. EXPECT_STREQ(simulatedObject->GetName().c_str(), "TestObj");
  84. EXPECT_EQ(simulatedObject->GetNumSimulatedJoints(), 3) << "There aren't 3 joints in the object";
  85. // Select 1 extra joint this time but still have the original 3
  86. SelectIndexes(indexList, skeletonTreeView, 2, 5);
  87. {
  88. // Bring up the contextMenu
  89. const QRect rect2 = skeletonTreeView->visualRect(indexList[4]);
  90. EXPECT_TRUE(rect2.isValid());
  91. BringUpContextMenu(skeletonTreeView, rect2);
  92. QMenu* contextMenu2 = skeletonOutliner->GetDockWidget()->findChild<QMenu*>("EMFX.SkeletonOutlinerPlugin.ContextMenu");
  93. EXPECT_TRUE(contextMenu2);
  94. QAction* addSelectedJointAction2;
  95. EXPECT_TRUE(UIFixture::GetActionFromContextMenu(addSelectedJointAction2, contextMenu2, "Add to simulated object"));
  96. QMenu* addSelectedJointMenu2 = addSelectedJointAction2->menu();
  97. EXPECT_TRUE(addSelectedJointMenu2);
  98. QAction* newSimulatedObjectAction2;
  99. ASSERT_TRUE(UIFixture::GetActionFromContextMenu(newSimulatedObjectAction2, addSelectedJointMenu2, "TestObj")) << "Can't find named simulated object in menu";
  100. // Handle the add children dialog box.
  101. ModalPopupHandler messageBoxPopupHandler2;
  102. messageBoxPopupHandler2.WaitForPopupPressDialogButton<QMessageBox*>(QDialogButtonBox::No);
  103. newSimulatedObjectAction2->trigger();
  104. }
  105. EXPECT_EQ(simulatedObject->GetNumSimulatedRootJoints(), 1);
  106. EXPECT_EQ(simulatedObject->GetNumSimulatedJoints(), 4) << "More than 1 extra object added";
  107. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  108. actorInstance->Destroy();
  109. }
  110. TEST_F(CanAddToSimulatedObjectFixture, CanAddCollidersfromRagdoll)
  111. {
  112. using ::testing::_;
  113. Physics::MockJointHelpersInterface jointHelpers;
  114. EXPECT_CALL(jointHelpers, GetSupportedJointTypeIds)
  115. .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
  116. EXPECT_CALL(jointHelpers, GetSupportedJointTypeId(_))
  117. .WillRepeatedly(
  118. [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
  119. {
  120. if (jointType == AzPhysics::JointType::D6Joint)
  121. {
  122. return azrtti_typeid<D6JointLimitConfiguration>();
  123. }
  124. return AZStd::nullopt;
  125. });
  126. EXPECT_CALL(jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
  127. .WillRepeatedly(
  128. []([[maybe_unused]] const AZ::TypeId& jointLimitTypeId, [[maybe_unused]] const AZ::Quaternion& parentWorldRotation,
  129. [[maybe_unused]] const AZ::Quaternion& childWorldRotation, [[maybe_unused]] const AZ::Vector3& axis,
  130. [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
  131. {
  132. return AZStd::make_unique<D6JointLimitConfiguration>();
  133. });
  134. RecordProperty("test_case_id", "C13291807");
  135. AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}");
  136. AZ::Data::Asset<Integration::ActorAsset> actorAsset =
  137. TestActorAssets::CreateActorAssetAndRegister<SimpleJointChainActor>(actorAssetId, 7, "CanAddToSimulatedObjectActor");
  138. Actor* actor = actorAsset->GetActor();
  139. ActorInstance* actorInstance = ActorInstance::Create(actor);
  140. // Change the Editor mode to Physics
  141. EMStudio::GetMainWindow()->ApplicationModeChanged("Physics");
  142. // Select the newly created actor instance
  143. AZStd::string result;
  144. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(AZStd::string{ "Select -actorInstanceID " } + AZStd::to_string(actorInstance->GetID()), result)) << result.c_str();
  145. EMotionFX::SkeletonOutlinerPlugin* skeletonOutliner = static_cast<EMotionFX::SkeletonOutlinerPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMotionFX::SkeletonOutlinerPlugin::CLASS_ID));
  146. EXPECT_TRUE(skeletonOutliner);
  147. ReselectingTreeView* skeletonTreeView = skeletonOutliner->GetDockWidget()->findChild<ReselectingTreeView*>("EMFX.SkeletonOutlinerPlugin.SkeletonOutlinerTreeView");
  148. EXPECT_TRUE(skeletonTreeView);
  149. const QAbstractItemModel* skeletonModel = skeletonTreeView->model();
  150. QModelIndexList indexList;
  151. skeletonTreeView->RecursiveGetAllChildren(skeletonModel->index(0, 0, skeletonModel->index(0, 0)), indexList);
  152. EXPECT_EQ(indexList.size(), 7);
  153. SelectIndexes(indexList, skeletonTreeView, 2, 4);
  154. // Bring up the contextMenu to add the collider to the Ragdoll.
  155. const QRect rect = skeletonTreeView->visualRect(indexList[3]);
  156. EXPECT_TRUE(rect.isValid());
  157. BringUpContextMenu(skeletonTreeView, rect);
  158. QMenu* contextMenu = skeletonOutliner->GetDockWidget()->findChild<QMenu*>("EMFX.SkeletonOutlinerPlugin.ContextMenu");
  159. EXPECT_TRUE(contextMenu);
  160. QAction* ragdollAction;
  161. EXPECT_TRUE(UIFixture::GetActionFromContextMenu(ragdollAction, contextMenu, "Ragdoll"));
  162. QMenu* ragdollMenu = ragdollAction->menu();
  163. EXPECT_TRUE(ragdollMenu);
  164. QAction* addToRagdollAction;
  165. EXPECT_TRUE(UIFixture::GetActionFromContextMenu(addToRagdollAction, ragdollMenu, "Add to ragdoll"));
  166. addToRagdollAction->trigger();
  167. // Change the Editor mode to SimulatedObjects
  168. EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects");
  169. SelectIndexes(indexList, skeletonTreeView, 2, 4);
  170. // Copy the ragdoll collider setup to simulated object colliders
  171. QDockWidget* simulatedObjectInspectorDock = EMStudio::GetMainWindow()->findChild<QDockWidget*>("EMFX.SimulatedObjectWidget.SimulatedObjectInspectorDock");
  172. ASSERT_TRUE(simulatedObjectInspectorDock);
  173. QPushButton* addColliderButton = EMStudio::GetPluginManager()->FindActivePlugin<SimulatedObjectWidget>()->GetDockWidget()->findChild<QPushButton*>("EMFX.SimulatedObjectColliderWidget.AddColliderButton");
  174. ASSERT_TRUE(addColliderButton);
  175. QTest::mouseClick(addColliderButton, Qt::LeftButton);
  176. QMenu* addColliderMenu = addColliderButton->findChild<QMenu*>("EMFX.AddColliderButton.ContextMenu");
  177. EXPECT_TRUE(addColliderMenu);
  178. QAction* copyRagdollAction;
  179. ASSERT_TRUE(UIFixture::GetActionFromContextMenu(copyRagdollAction, addColliderMenu, "Copy from Ragdoll"));
  180. copyRagdollAction->trigger();
  181. SimulatedObjectColliderWidget* colliderWidget = simulatedObjectInspectorDock->findChild<SimulatedObjectColliderWidget*>(
  182. "EMFX.SimulatedJointWidget.SimulatedObjectColliderWidget");
  183. ASSERT_TRUE(colliderWidget);
  184. ColliderContainerWidget* containerWidget =
  185. colliderWidget->findChild<ColliderContainerWidget*>("EMFX.SimulatedObjectColliderWidget.ColliderContainerWidget");
  186. ASSERT_TRUE(containerWidget);
  187. EXPECT_EQ(containerWidget->ColliderType(), PhysicsSetup::ColliderConfigType::Unknown) << "Collider type not Unknown";
  188. for (int i = 2; i <= 4; ++i)
  189. {
  190. skeletonTreeView->selectionModel()->clearSelection();
  191. SelectIndexes(indexList, skeletonTreeView, i, i);
  192. EXPECT_EQ(containerWidget->ColliderType(), PhysicsSetup::ColliderConfigType::SimulatedObjectCollider)
  193. << "Simulated Collider type not found";
  194. }
  195. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  196. Physics::CharacterColliderConfiguration* colliderConfig = physicsSetup->GetColliderConfigByType(PhysicsSetup::ColliderConfigType::SimulatedObjectCollider);
  197. EXPECT_TRUE(colliderConfig) << "Can't find Simulated Object Colliders";
  198. AZStd::vector<Physics::CharacterColliderNodeConfiguration> nodes = colliderConfig->m_nodes;
  199. const AZStd::vector<AZStd::string> gotNodeNamesInColliderConfig = [&nodes]()
  200. {
  201. AZStd::vector<AZStd::string> nodeNames(nodes.size());
  202. AZStd::transform(begin(nodes), end(nodes), begin(nodeNames), [](const auto& nodeConfig) { return nodeConfig.m_name; });
  203. return nodeNames;
  204. }();
  205. const AZStd::vector<Physics::ShapeType> gotShapeTypes = [&nodes]()
  206. {
  207. AZStd::vector<Physics::ShapeType> shapeTypes(nodes.size());
  208. AZStd::transform(begin(nodes), end(nodes), begin(shapeTypes), [](const Physics::CharacterColliderNodeConfiguration& nodeConfig)
  209. {
  210. return nodeConfig.m_shapes.empty() ? Physics::ShapeType(0xff) : nodeConfig.m_shapes[0].second->GetShapeType();
  211. });
  212. return shapeTypes;
  213. }();
  214. EXPECT_THAT(gotNodeNamesInColliderConfig, testing::Pointwise(StrEq(), AZStd::vector<AZStd::string> {"joint2", "joint3", "joint4"}));
  215. EXPECT_THAT(gotShapeTypes, testing::Pointwise(testing::Eq(), AZStd::vector<Physics::ShapeType> {Physics::ShapeType::Capsule, Physics::ShapeType::Capsule, Physics::ShapeType::Capsule}));
  216. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  217. actorInstance->Destroy();
  218. }
  219. } // namespace EMotionFX