SimulatedObjectColliderWidget.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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 <AzCore/Component/ComponentApplicationBus.h>
  9. #include <AzCore/Serialization/SerializeContext.h>
  10. #include <AzFramework/Physics/Character.h>
  11. #include <EMotionFX/Source/Actor.h>
  12. #include <EMotionFX/Source/Node.h>
  13. #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
  14. #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  15. #include <Editor/ColliderContainerWidget.h>
  16. #include <Editor/ColliderHelpers.h>
  17. #include <Editor/NotificationWidget.h>
  18. #include <Editor/SimulatedObjectHelpers.h>
  19. #include <Editor/SkeletonModel.h>
  20. #include <Editor/Plugins/ColliderWidgets/SimulatedObjectColliderWidget.h>
  21. #include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerBus.h>
  22. #include <MysticQt/Source/MysticQtManager.h>
  23. #include <QLabel>
  24. #include <QMessageBox>
  25. #include <QHBoxLayout>
  26. #include <QVBoxLayout>
  27. namespace EMotionFX
  28. {
  29. SimulatedObjectColliderWidget::SimulatedObjectColliderWidget(QWidget* parent)
  30. : SkeletonModelJointWidget(parent)
  31. {
  32. }
  33. QWidget* SimulatedObjectColliderWidget::CreateContentWidget(QWidget* parent)
  34. {
  35. QWidget* result = new QWidget(parent);
  36. QVBoxLayout* layout = new QVBoxLayout();
  37. layout->setMargin(0);
  38. layout->setSpacing(ColliderContainerWidget::s_layoutSpacing);
  39. result->setLayout(layout);
  40. // Object ownership label
  41. {
  42. m_ownershipWidget = new QWidget(result);
  43. QHBoxLayout* ownershipLayout = new QHBoxLayout(m_ownershipWidget);
  44. ownershipLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
  45. ownershipLayout->setMargin(0);
  46. ownershipLayout->setSpacing(0);
  47. m_ownershipWidget->setLayout(ownershipLayout);
  48. ownershipLayout->addSpacerItem(new QSpacerItem(s_jointLabelSpacing, 0, QSizePolicy::Fixed));
  49. QLabel* tempLabel = new QLabel("Part of Simulated Objects");
  50. tempLabel->setStyleSheet("font-weight: bold;");
  51. ownershipLayout->addWidget(tempLabel);
  52. ownershipLayout->addSpacerItem(new QSpacerItem(44, 0, QSizePolicy::Fixed));
  53. m_ownershipLabel = new QLabel();
  54. m_ownershipLabel->setWordWrap(true);
  55. ownershipLayout->addWidget(m_ownershipLabel);
  56. ownershipLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Ignored));
  57. layout->addWidget(m_ownershipWidget);
  58. }
  59. // Collide with object label
  60. {
  61. m_collideWithWidget = new QWidget(result);
  62. QHBoxLayout* collideWithLayout = new QHBoxLayout(m_collideWithWidget);
  63. collideWithLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
  64. collideWithLayout->setMargin(0);
  65. collideWithLayout->setSpacing(0);
  66. m_collideWithWidget->setLayout(collideWithLayout);
  67. collideWithLayout->addSpacerItem(new QSpacerItem(s_jointLabelSpacing, 0, QSizePolicy::Fixed));
  68. QLabel* tempLabel = new QLabel("Collide with Simulated Objects");
  69. tempLabel->setStyleSheet("font-weight: bold;");
  70. collideWithLayout->addWidget(tempLabel);
  71. collideWithLayout->addSpacerItem(new QSpacerItem(13, 0, QSizePolicy::Fixed));
  72. m_collideWithLabel = new QLabel();
  73. m_collideWithLabel->setWordWrap(true);
  74. collideWithLayout->addWidget(m_collideWithLabel);
  75. collideWithLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Ignored));
  76. layout->addWidget(m_collideWithWidget);
  77. }
  78. // Collider notification
  79. m_colliderNotif = new NotificationWidget(result, "Currently, this collider doesn't collide against any simulated object. Select the Simulated Object you want to collide with from the Simulated Object Window, and choose this collider in the \"Collide with\" setting.");
  80. layout->addWidget(m_colliderNotif);
  81. m_colliderNotif->hide();
  82. // Colliders widget
  83. m_collidersWidget = new ColliderContainerWidget(QIcon(SkeletonModel::s_simulatedColliderIconPath), result); // use the ragdoll white collider icon because it's generic to all colliders.
  84. m_collidersWidget->setObjectName("EMFX.SimulatedObjectColliderWidget.ColliderContainerWidget");
  85. connect(m_collidersWidget, &ColliderContainerWidget::CopyCollider, this, &SimulatedObjectColliderWidget::OnCopyCollider);
  86. connect(m_collidersWidget, &ColliderContainerWidget::PasteCollider, this, &SimulatedObjectColliderWidget::OnPasteCollider);
  87. connect(m_collidersWidget, &ColliderContainerWidget::RemoveCollider, this, &SimulatedObjectColliderWidget::OnRemoveCollider);
  88. layout->addWidget(m_collidersWidget);
  89. return result;
  90. }
  91. void SimulatedObjectColliderWidget::InternalReinit()
  92. {
  93. m_widgetCount = 0;
  94. const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
  95. if (selectedModelIndices.size() == 1)
  96. {
  97. Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
  98. if (nodeConfig)
  99. {
  100. AZ::SerializeContext* serializeContext = nullptr;
  101. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  102. AZ_Error("EMotionFX", serializeContext, "Can't get serialize context from component application.");
  103. m_collidersWidget->Update(GetActor(), GetNode(), PhysicsSetup::ColliderConfigType::SimulatedObjectCollider, nodeConfig->m_shapes, serializeContext);
  104. m_content->show();
  105. m_collidersWidget->show();
  106. m_widgetCount = static_cast<int>(1 + nodeConfig->m_shapes.size()); // on Mac unsigned long (size_t) can't silent downcast to int, added static_cast
  107. }
  108. else
  109. {
  110. m_collidersWidget->Reset();
  111. }
  112. }
  113. else
  114. {
  115. m_collidersWidget->Reset();
  116. }
  117. UpdateOwnershipLabel();
  118. UpdateColliderNotification();
  119. emit WidgetCountChanged();
  120. }
  121. int SimulatedObjectColliderWidget::WidgetCount() const
  122. {
  123. return m_widgetCount;
  124. }
  125. void SimulatedObjectColliderWidget::UpdateOwnershipLabel()
  126. {
  127. Actor* actor = GetActor();
  128. if (!actor)
  129. {
  130. return;
  131. }
  132. AZStd::string labelText;
  133. const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
  134. const AZStd::vector<SimulatedObject*>& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
  135. for (const SimulatedObject* obj : simObjs)
  136. {
  137. for (int i = 0; i < selectedModelIndices.size(); ++i)
  138. {
  139. Node* node = selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value<Node*>();
  140. if (obj->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex()))
  141. {
  142. if (!labelText.empty())
  143. {
  144. labelText += ", ";
  145. }
  146. labelText += obj->GetName();
  147. break;
  148. }
  149. }
  150. }
  151. if (labelText.empty())
  152. {
  153. labelText = "N/A";
  154. }
  155. m_ownershipLabel->setText(labelText.c_str());
  156. }
  157. void SimulatedObjectColliderWidget::UpdateColliderNotification()
  158. {
  159. m_colliderNotif->hide();
  160. m_collideWithWidget->hide();
  161. Actor* actor = GetActor();
  162. Node* joint = GetNode();
  163. if (!actor || !joint)
  164. {
  165. return;
  166. }
  167. const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
  168. // Only show the notification when it is single selection.
  169. if (selectedModelIndices.size() != 1)
  170. {
  171. return;
  172. }
  173. Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
  174. if (!nodeConfig)
  175. {
  176. return;
  177. }
  178. m_collideWithWidget->show();
  179. AZStd::string collideWith;
  180. const AZStd::vector<SimulatedObject*>& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
  181. for (const SimulatedObject* obj : simObjs)
  182. {
  183. if (AZStd::find(obj->GetColliderTags().begin(), obj->GetColliderTags().end(), joint->GetName()) != obj->GetColliderTags().end())
  184. {
  185. if (!collideWith.empty())
  186. {
  187. collideWith += ", ";
  188. }
  189. collideWith += obj->GetName();
  190. }
  191. }
  192. if (collideWith.empty())
  193. {
  194. m_colliderNotif->show();
  195. m_collideWithLabel->setText("N/A");
  196. }
  197. else
  198. {
  199. m_colliderNotif->hide();
  200. m_collideWithLabel->setText(collideWith.c_str());
  201. }
  202. }
  203. void SimulatedObjectColliderWidget::OnAddCollider(const AZ::TypeId& colliderType)
  204. {
  205. ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::SimulatedObjectCollider, colliderType);
  206. }
  207. void SimulatedObjectColliderWidget::OnCopyCollider(size_t colliderIndex)
  208. {
  209. ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider);
  210. }
  211. void SimulatedObjectColliderWidget::OnPasteCollider(size_t colliderIndex, bool replace)
  212. {
  213. ColliderHelpers::PasteColliderFromClipboard(
  214. GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace);
  215. }
  216. void SimulatedObjectColliderWidget::OnRemoveCollider(size_t colliderIndex)
  217. {
  218. CommandColliderHelpers::RemoveCollider(GetActor()->GetID(), GetNode()->GetNameString(), PhysicsSetup::SimulatedObjectCollider, colliderIndex);
  219. }
  220. Physics::CharacterColliderNodeConfiguration* SimulatedObjectColliderWidget::GetNodeConfig() const
  221. {
  222. AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single selected");
  223. Actor* actor = GetActor();
  224. Node* joint = GetNode();
  225. if (!actor || !joint)
  226. {
  227. return nullptr;
  228. }
  229. const AZStd::shared_ptr<EMotionFX::PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  230. if (!physicsSetup)
  231. {
  232. return nullptr;
  233. }
  234. const Physics::CharacterColliderConfiguration& simulatedObjectColliderConfig = physicsSetup->GetSimulatedObjectColliderConfig();
  235. return simulatedObjectColliderConfig.FindNodeConfigByName(joint->GetNameString());
  236. }
  237. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  238. AddToSimulatedObjectButton::AddToSimulatedObjectButton(const QString& text, QWidget* parent)
  239. : QPushButton(text, parent)
  240. {
  241. m_actionManager = AZStd::make_unique<EMStudio::SimulatedObjectActionManager>();
  242. setIcon(MysticQt::GetMysticQt()->FindIcon("Images/Icons/ArrowDownGray.png"));
  243. connect(this, &QPushButton::clicked, this, &AddToSimulatedObjectButton::OnCreateContextMenu);
  244. }
  245. void AddToSimulatedObjectButton::OnCreateContextMenu()
  246. {
  247. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  248. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  249. if (!selectedRowIndicesOutcome.IsSuccess())
  250. {
  251. return;
  252. }
  253. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  254. if (selectedRowIndices.empty())
  255. {
  256. return;
  257. }
  258. const Actor* actor = selectedRowIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  259. if (!actor || !actor->GetSimulatedObjectSetup())
  260. {
  261. return;
  262. }
  263. const SimulatedObjectSetup* simObjSetup = actor->GetSimulatedObjectSetup().get();
  264. const size_t simObjCounts = simObjSetup->GetNumSimulatedObjects();
  265. // Find the object we can add the joints to, excluded the one that already contains all the selected joints.
  266. AZStd::vector<bool> flags(simObjCounts, false);
  267. for (const QModelIndex& index : selectedRowIndices)
  268. {
  269. const Node* joint = index.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  270. for (size_t i = 0; i < simObjCounts; ++i)
  271. {
  272. const SimulatedObject* object = simObjSetup->GetSimulatedObject(i);
  273. if (!object->FindSimulatedJointBySkeletonJointIndex(joint->GetNodeIndex()))
  274. {
  275. flags[i] = true;
  276. }
  277. }
  278. }
  279. QMenu* contextMenu = new QMenu(this);
  280. if (simObjCounts == 0)
  281. {
  282. QAction* action = contextMenu->addAction("0 simulated objects created.");
  283. action->setEnabled(false);
  284. contextMenu->addSeparator();
  285. }
  286. // Add all the object that we can add joints to in the menu.
  287. for (size_t i = 0; i < simObjCounts; ++i)
  288. {
  289. if (!flags[i])
  290. {
  291. continue;
  292. }
  293. const SimulatedObject* obj = simObjSetup->GetSimulatedObject(i);
  294. QAction* action = contextMenu->addAction(obj->GetName().c_str());
  295. action->setProperty("simObjName", obj->GetName().c_str());
  296. action->setProperty("simObjIndex", QVariant::fromValue(i));
  297. connect(action, &QAction::triggered, this, &AddToSimulatedObjectButton::OnAddJointsToObjectActionTriggered);
  298. }
  299. contextMenu->addSeparator();
  300. // Add the action to add simulated object, then add the joint to the object.
  301. QAction* addObjectAction = contextMenu->addAction("New simulated object...");
  302. connect(addObjectAction, &QAction::triggered, this, &AddToSimulatedObjectButton::OnCreateObjectAndAddJointsActionTriggered);
  303. contextMenu->setFixedWidth(width());
  304. if (!contextMenu->isEmpty())
  305. {
  306. contextMenu->popup(mapToGlobal(QPoint(0, height())));
  307. }
  308. connect(contextMenu, &QMenu::triggered, contextMenu, &QMenu::deleteLater);
  309. }
  310. void AddToSimulatedObjectButton::OnAddJointsToObjectActionTriggered([[maybe_unused]] bool checked)
  311. {
  312. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  313. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  314. if (!selectedRowIndicesOutcome.IsSuccess())
  315. {
  316. return;
  317. }
  318. QAction* action = static_cast<QAction*>(sender());
  319. size_t objIndex = static_cast<size_t>(action->property("simObjIndex").toInt());
  320. SimulatedObjectHelpers::AddSimulatedJoints(selectedRowIndicesOutcome.GetValue(), objIndex, false);
  321. }
  322. void AddToSimulatedObjectButton::OnCreateObjectAndAddJointsActionTriggered()
  323. {
  324. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  325. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  326. if (!selectedRowIndicesOutcome.IsSuccess())
  327. {
  328. return;
  329. }
  330. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  331. if (selectedRowIndices.empty())
  332. {
  333. return;
  334. }
  335. Actor* actor = selectedRowIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  336. if (!actor || !actor->GetSimulatedObjectSetup())
  337. {
  338. return;
  339. }
  340. const bool addChildren = (QMessageBox::question(this,
  341. "Add children of joints?", "Add all children of selected joints to the simulated object?") == QMessageBox::Yes);
  342. m_actionManager->OnAddNewObjectAndAddJoints(actor, selectedRowIndices, addChildren, this);
  343. }
  344. } // namespace EMotionFX