RagdollOutlinerNotificationHandler.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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 <AzFramework/Entity/EntityDebugDisplayBus.h>
  9. #include <AzFramework/Physics/SystemBus.h>
  10. #include <AzQtComponents/Components/ToastNotification.h>
  11. #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
  12. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  13. #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
  14. #include <EMotionFX/Source/ActorManager.h>
  15. #include <EMotionFX/Source/RagdollInstance.h>
  16. #include <EMotionFX/Source/TransformData.h>
  17. #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  18. #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h>
  19. #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
  20. #include <Editor/ColliderContainerWidget.h>
  21. #include <Editor/ColliderHelpers.h>
  22. #include <Editor/Plugins/ColliderWidgets/RagdollNodeWidget.h>
  23. #include <Editor/Plugins/ColliderWidgets/RagdollOutlinerNotificationHandler.h>
  24. #include <Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h>
  25. #include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.h>
  26. #include <Editor/SkeletonModel.h>
  27. #include <Integration/Rendering/RenderActorSettings.h>
  28. #include <MCore/Source/AzCoreConversions.h>
  29. #include <QScrollArea>
  30. #include <UI/Notifications/ToastBus.h>
  31. namespace EMotionFX
  32. {
  33. RagdollOutlinerNotificationHandler::RagdollOutlinerNotificationHandler(RagdollNodeWidget* nodeWidget)
  34. : m_nodeWidget(nodeWidget)
  35. {
  36. if (!IsPhysXGemAvailable() || !ColliderHelpers::AreCollidersReflected())
  37. {
  38. m_nodeWidget->ErrorNotification(
  39. "PhysX disabled", "Ragdoll editor depends on the PhysX gem. Please enable it in the Project Manager.");
  40. return;
  41. }
  42. EMotionFX::SkeletonOutlinerNotificationBus::Handler::BusConnect();
  43. }
  44. RagdollOutlinerNotificationHandler::~RagdollOutlinerNotificationHandler()
  45. {
  46. EMotionFX::SkeletonOutlinerNotificationBus::Handler::BusDisconnect();
  47. }
  48. bool RagdollOutlinerNotificationHandler::IsPhysXGemAvailable() const
  49. {
  50. AZ::SerializeContext* serializeContext = nullptr;
  51. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  52. // TypeId of PhysX::SystemComponent
  53. const char* typeIDPhysXSystem = "{85F90819-4D9A-4A77-AB89-68035201F34B}";
  54. return serializeContext && serializeContext->FindClassData(AZ::TypeId::CreateString(typeIDPhysXSystem));
  55. }
  56. void RagdollOutlinerNotificationHandler::OnContextMenu(QMenu* menu, const QModelIndexList& selectedRowIndices)
  57. {
  58. if (selectedRowIndices.empty())
  59. {
  60. return;
  61. }
  62. if (SkeletonModel::IndicesContainRootNode(selectedRowIndices) && selectedRowIndices.size() == 1)
  63. {
  64. return;
  65. }
  66. const int numSelectedNodes = selectedRowIndices.count();
  67. int ragdollNodeCount = 0;
  68. for (const QModelIndex& modelIndex : selectedRowIndices)
  69. {
  70. const bool partOfRagdoll = modelIndex.data(SkeletonModel::ROLE_RAGDOLL).toBool();
  71. if (partOfRagdoll)
  72. {
  73. ragdollNodeCount++;
  74. }
  75. }
  76. QMenu* contextMenu = menu->addMenu("Ragdoll");
  77. if (ragdollNodeCount < numSelectedNodes)
  78. {
  79. QAction* addToRagdollAction = contextMenu->addAction("Add to ragdoll");
  80. connect(addToRagdollAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnAddToRagdoll);
  81. }
  82. if (ragdollNodeCount == numSelectedNodes)
  83. {
  84. QMenu* addColliderMenu = contextMenu->addMenu("Add collider");
  85. QAction* addBoxAction = addColliderMenu->addAction("Add box");
  86. addBoxAction->setProperty("typeId", azrtti_typeid<Physics::BoxShapeConfiguration>().ToString<AZStd::string>().c_str());
  87. connect(addBoxAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnAddCollider);
  88. QAction* addCapsuleAction = addColliderMenu->addAction("Add capsule");
  89. addCapsuleAction->setProperty("typeId", azrtti_typeid<Physics::CapsuleShapeConfiguration>().ToString<AZStd::string>().c_str());
  90. connect(addCapsuleAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnAddCollider);
  91. QAction* addSphereAction = addColliderMenu->addAction("Add sphere");
  92. addSphereAction->setProperty("typeId", azrtti_typeid<Physics::SphereShapeConfiguration>().ToString<AZStd::string>().c_str());
  93. connect(addSphereAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnAddCollider);
  94. }
  95. ColliderHelpers::AddCopyFromMenu(
  96. this,
  97. contextMenu,
  98. PhysicsSetup::ColliderConfigType::Ragdoll,
  99. selectedRowIndices,
  100. [=](PhysicsSetup::ColliderConfigType copyFrom, [[maybe_unused]] PhysicsSetup::ColliderConfigType copyTo)
  101. {
  102. CopyColliders(selectedRowIndices, copyFrom);
  103. });
  104. if (ragdollNodeCount > 0)
  105. {
  106. QAction* removeCollidersAction = contextMenu->addAction("Remove colliders");
  107. connect(removeCollidersAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnClearColliders);
  108. QAction* removeToRagdollAction = contextMenu->addAction("Remove from ragdoll");
  109. connect(removeToRagdollAction, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnRemoveFromRagdoll);
  110. QAction* pasteJointLimits = contextMenu->addAction("Paste joint limits");
  111. pasteJointLimits->setObjectName("EMFX.RagdollNodeInspectorPlugin.PasteJointLimitsAction");
  112. connect(pasteJointLimits, &QAction::triggered, this, &RagdollOutlinerNotificationHandler::OnPasteJointLimits);
  113. }
  114. }
  115. bool RagdollOutlinerNotificationHandler::IsNodeInRagdoll(const QModelIndex& index)
  116. {
  117. const Actor* actor = index.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  118. const Node* joint = index.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  119. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  120. const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
  121. return ragdollConfig.FindNodeConfigByName(joint->GetNameString()) != nullptr;
  122. }
  123. void RagdollOutlinerNotificationHandler::AddCollider(const QModelIndexList& modelIndices, const AZ::TypeId& colliderType)
  124. {
  125. if (modelIndices.empty())
  126. {
  127. return;
  128. }
  129. const AZStd::string groupName = AZStd::string::format("Add collider%s to ragdoll", modelIndices.size() > 1 ? "s" : "");
  130. MCore::CommandGroup commandGroup(groupName);
  131. for (const QModelIndex& selectedIndex : modelIndices)
  132. {
  133. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  134. {
  135. continue;
  136. }
  137. const Actor* actor = selectedIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  138. const Node* selectedJoint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  139. CommandColliderHelpers::AddCollider(
  140. actor->GetID(), selectedJoint->GetNameString(), PhysicsSetup::Ragdoll, colliderType, &commandGroup);
  141. }
  142. AZStd::string result;
  143. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  144. {
  145. AZ_Error("EMotionFX", false, result.c_str());
  146. }
  147. }
  148. void RagdollOutlinerNotificationHandler::CopyColliders(const QModelIndexList& modelIndices, PhysicsSetup::ColliderConfigType copyFrom)
  149. {
  150. if (modelIndices.empty())
  151. {
  152. return;
  153. }
  154. const AZStd::string groupName = AZStd::string::format(
  155. "Copy %s collider%s to ragdoll", PhysicsSetup::GetStringForColliderConfigType(copyFrom), modelIndices.size() > 1 ? "s" : "");
  156. MCore::CommandGroup commandGroup(groupName);
  157. AZStd::vector<AZStd::string> jointNamesToAdd;
  158. AZStd::vector<const Node*> jointsToAdd;
  159. const Actor* actor = modelIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  160. for (const QModelIndex& selectedIndex : modelIndices)
  161. {
  162. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  163. {
  164. continue;
  165. }
  166. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  167. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  168. const Physics::CharacterColliderConfiguration* copyFromColliderConfig = physicsSetup->GetColliderConfigByType(copyFrom);
  169. if (!copyFromColliderConfig)
  170. {
  171. continue;
  172. }
  173. Physics::CharacterColliderNodeConfiguration* copyFromNodeConfig =
  174. copyFromColliderConfig->FindNodeConfigByName(joint->GetNameString());
  175. if (!copyFromNodeConfig || copyFromNodeConfig->m_shapes.empty())
  176. {
  177. continue;
  178. }
  179. jointNamesToAdd.emplace_back(joint->GetNameString());
  180. jointsToAdd.emplace_back(joint);
  181. }
  182. CommandRagdollHelpers::AddJointsToRagdoll(actor->GetID(), jointNamesToAdd, &commandGroup);
  183. for (const Node* joint : jointsToAdd)
  184. {
  185. // 2. Remove the auto-added capsule and former colliders.
  186. CommandColliderHelpers::ClearColliders(actor->GetID(), joint->GetNameString(), PhysicsSetup::Ragdoll, &commandGroup);
  187. // 3. Copy colliders
  188. ColliderHelpers::AddCopyColliderCommandToGroup(actor, joint, copyFrom, PhysicsSetup::Ragdoll, commandGroup);
  189. }
  190. AZStd::string result;
  191. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  192. {
  193. AZ_Error("EMotionFX", false, result.c_str());
  194. }
  195. }
  196. void RagdollOutlinerNotificationHandler::OnAddToRagdoll()
  197. {
  198. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  199. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  200. if (!selectedRowIndicesOutcome.IsSuccess())
  201. {
  202. return;
  203. }
  204. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  205. if (selectedRowIndices.empty())
  206. {
  207. return;
  208. }
  209. ColliderHelpers::AddToRagdoll(selectedRowIndices);
  210. }
  211. void RagdollOutlinerNotificationHandler::OnAddCollider()
  212. {
  213. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  214. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  215. if (!selectedRowIndicesOutcome.IsSuccess())
  216. {
  217. return;
  218. }
  219. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  220. if (selectedRowIndices.empty())
  221. {
  222. return;
  223. }
  224. QAction* action = static_cast<QAction*>(sender());
  225. const QByteArray typeString = action->property("typeId").toString().toUtf8();
  226. const AZ::TypeId& colliderType = AZ::TypeId::CreateString(typeString.data(), typeString.size());
  227. AddCollider(selectedRowIndices, colliderType);
  228. }
  229. void RagdollOutlinerNotificationHandler::OnRemoveFromRagdoll()
  230. {
  231. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  232. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  233. if (!selectedRowIndicesOutcome.IsSuccess())
  234. {
  235. return;
  236. }
  237. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  238. if (selectedRowIndices.empty())
  239. {
  240. return;
  241. }
  242. ColliderHelpers::RemoveFromRagdoll(selectedRowIndices);
  243. }
  244. void RagdollOutlinerNotificationHandler::OnClearColliders()
  245. {
  246. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  247. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  248. if (!selectedRowIndicesOutcome.IsSuccess())
  249. {
  250. return;
  251. }
  252. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  253. if (selectedRowIndices.empty())
  254. {
  255. return;
  256. }
  257. ColliderHelpers::ClearColliders(selectedRowIndices, PhysicsSetup::Ragdoll);
  258. }
  259. void RagdollOutlinerNotificationHandler::OnPasteJointLimits()
  260. {
  261. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  262. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  263. if (!selectedRowIndicesOutcome.IsSuccess())
  264. {
  265. return;
  266. }
  267. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  268. if (selectedRowIndices.empty())
  269. {
  270. return;
  271. }
  272. MCore::CommandGroup group("Paste joint limits");
  273. for (const QModelIndex& index : selectedRowIndices)
  274. {
  275. const Actor* actor = index.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  276. const Node* joint = index.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  277. auto command = aznew CommandAdjustRagdollJoint(actor->GetID(), joint->GetName(), m_nodeWidget->GetCopiedJointLimits());
  278. group.AddCommand(command);
  279. }
  280. AZStd::string result;
  281. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(group, result))
  282. {
  283. AZ_Error("EMotionFX", false, result.c_str());
  284. }
  285. }
  286. } // namespace EMotionFX