ColliderContainerWidget.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  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 <AzCore/std/smart_ptr/make_shared.h>
  11. #include <AzFramework/Physics/ShapeConfiguration.h>
  12. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  13. #include <AzQtComponents/Components/Widgets/CardHeader.h>
  14. #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
  15. #include <EMotionFX/CommandSystem/Source/SimulatedObjectCommands.h>
  16. #include <EMotionFX/Source/Actor.h>
  17. #include <EMotionFX/Source/ActorInstance.h>
  18. #include <EMotionFX/Source/ActorManager.h>
  19. #include <EMotionFX/Source/Node.h>
  20. #include <EMotionFX/Source/TransformData.h>
  21. #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  22. #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
  23. #include <Editor/ColliderContainerWidget.h>
  24. #include <Editor/ColliderHelpers.h>
  25. #include <Editor/ObjectEditor.h>
  26. #include <Editor/Plugins/Ragdoll/PhysicsSetupManipulatorBus.h>
  27. #include <Editor/SkeletonModel.h>
  28. #include <MCore/Source/AzCoreConversions.h>
  29. #include <MysticQt/Source/MysticQtManager.h>
  30. #include <QClipboard>
  31. #include <QContextMenuEvent>
  32. #include <QEvent>
  33. #include <QGuiApplication>
  34. #include <QMenu>
  35. #include <QMimeData>
  36. #include <QPushButton>
  37. #include <QVBoxLayout>
  38. #include <Qt>
  39. namespace EMotionFX
  40. {
  41. ColliderPropertyNotify::ColliderPropertyNotify(ColliderWidget* colliderWidget)
  42. : m_colliderWidget(colliderWidget)
  43. {
  44. }
  45. void ColliderPropertyNotify::BeforePropertyModified(AzToolsFramework::InstanceDataNode* node)
  46. {
  47. if (!m_commandGroup.IsEmpty())
  48. {
  49. return;
  50. }
  51. const AzToolsFramework::InstanceDataNode* parentDataNode = node->GetParent();
  52. if (!parentDataNode)
  53. {
  54. return;
  55. }
  56. const AZ::SerializeContext* serializeContext = parentDataNode->GetSerializeContext();
  57. const AZ::SerializeContext::ClassData* classData = parentDataNode->GetClassMetadata();
  58. const AZ::SerializeContext::ClassElement* elementData = node->GetElementMetadata();
  59. const Actor* actor = m_colliderWidget->GetActor();
  60. const Node* joint = m_colliderWidget->GetJoint();
  61. if (!actor || !joint)
  62. {
  63. return;
  64. }
  65. const AZ::u32 actorId = actor->GetID();
  66. const AZStd::string& jointName = joint->GetNameString();
  67. const PhysicsSetup::ColliderConfigType colliderType = m_colliderWidget->GetColliderType();
  68. const size_t colliderIndex = m_colliderWidget->GetColliderIndex();
  69. const size_t instanceCount = parentDataNode->GetNumInstances();
  70. m_commandGroup.SetGroupName(AZStd::string::format("Adjust collider%s", instanceCount > 1 ? "s" : ""));
  71. for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
  72. {
  73. CommandAdjustCollider* command = aznew CommandAdjustCollider(actorId, jointName, colliderType, colliderIndex);
  74. m_commandGroup.AddCommand(command);
  75. // ColliderConfiguration
  76. if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::ColliderConfiguration>(), classData->m_azRtti, nullptr))
  77. {
  78. const Physics::ColliderConfiguration* colliderConfig = static_cast<Physics::ColliderConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  79. if (elementData->m_nameCrc == AZ_CRC("CollisionLayer", 0x39931633))
  80. {
  81. command->SetOldCollisionLayer(colliderConfig->m_collisionLayer);
  82. }
  83. if (elementData->m_nameCrc == AZ_CRC("CollisionGroupId", 0x84fe4bbe))
  84. {
  85. command->SetOldCollisionGroupId(colliderConfig->m_collisionGroupId);
  86. }
  87. if (elementData->m_nameCrc == AZ_CRC("Trigger", 0x1a6b0f5d))
  88. {
  89. command->SetOldIsTrigger(colliderConfig->m_isTrigger);
  90. }
  91. if (elementData->m_nameCrc == AZ_CRC("Position", 0x462ce4f5))
  92. {
  93. command->SetOldPosition(colliderConfig->m_position);
  94. }
  95. if (elementData->m_nameCrc == AZ_CRC("Rotation", 0x297c98f1))
  96. {
  97. command->SetOldRotation(colliderConfig->m_rotation);
  98. }
  99. if (elementData->m_nameCrc == AZ_CRC_CE("MaterialSlots"))
  100. {
  101. command->SetOldMaterialSlots(colliderConfig->m_materialSlots);
  102. }
  103. if (elementData->m_nameCrc == AZ_CRC("ColliderTag", 0x5e2963ad))
  104. {
  105. command->SetOldTag(colliderConfig->m_tag);
  106. }
  107. }
  108. // Box
  109. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::BoxShapeConfiguration>(), classData->m_azRtti, nullptr))
  110. {
  111. const Physics::BoxShapeConfiguration* boxShapeConfig = static_cast<Physics::BoxShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  112. if (elementData->m_nameCrc == AZ_CRC("Configuration", 0xa5e2a5d7))
  113. {
  114. command->SetOldDimensions(boxShapeConfig->m_dimensions);
  115. }
  116. }
  117. // Capsule
  118. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::CapsuleShapeConfiguration>(), classData->m_azRtti, nullptr))
  119. {
  120. const Physics::CapsuleShapeConfiguration* capsuleShapeConfig = static_cast<Physics::CapsuleShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  121. if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a))
  122. {
  123. command->SetOldRadius(capsuleShapeConfig->m_radius);
  124. }
  125. if (elementData->m_nameCrc == AZ_CRC("Height", 0xf54de50f))
  126. {
  127. command->SetOldHeight(capsuleShapeConfig->m_height);
  128. }
  129. }
  130. // Sphere
  131. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::SphereShapeConfiguration>(), classData->m_azRtti, nullptr))
  132. {
  133. const Physics::SphereShapeConfiguration* sphereShapeConfig = static_cast<Physics::SphereShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  134. if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a))
  135. {
  136. command->SetOldRadius(sphereShapeConfig->m_radius);
  137. }
  138. }
  139. }
  140. }
  141. void ColliderPropertyNotify::AfterPropertyModified([[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
  142. {
  143. PhysicsSetupManipulatorRequestBus::Broadcast(&PhysicsSetupManipulatorRequests::OnUnderlyingPropertiesChanged);
  144. }
  145. void ColliderPropertyNotify::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* node)
  146. {
  147. if (m_commandGroup.IsEmpty())
  148. {
  149. return;
  150. }
  151. const AzToolsFramework::InstanceDataNode* parentDataNode = node->GetParent();
  152. if (!parentDataNode)
  153. {
  154. return;
  155. }
  156. const AZ::SerializeContext* serializeContext = parentDataNode->GetSerializeContext();
  157. const AZ::SerializeContext::ClassData* classData = parentDataNode->GetClassMetadata();
  158. const AZ::SerializeContext::ClassElement* elementData = node->GetElementMetadata();
  159. const Actor* actor = m_colliderWidget->GetActor();
  160. const Node* joint = m_colliderWidget->GetJoint();
  161. if (!actor || !joint)
  162. {
  163. return;
  164. }
  165. const PhysicsSetup::ColliderConfigType colliderType = m_colliderWidget->GetColliderType();
  166. const size_t instanceCount = parentDataNode->GetNumInstances();
  167. for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
  168. {
  169. CommandAdjustCollider* command = static_cast<CommandAdjustCollider*>(m_commandGroup.GetCommand(instanceIndex));
  170. // ColliderConfiguration
  171. if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::ColliderConfiguration>(), classData->m_azRtti, nullptr))
  172. {
  173. const Physics::ColliderConfiguration* colliderConfig = static_cast<Physics::ColliderConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  174. if (elementData->m_nameCrc == AZ_CRC("CollisionLayer", 0x39931633))
  175. {
  176. command->SetCollisionLayer(colliderConfig->m_collisionLayer);
  177. }
  178. if (elementData->m_nameCrc == AZ_CRC("CollisionGroupId", 0x84fe4bbe))
  179. {
  180. command->SetCollisionGroupId(colliderConfig->m_collisionGroupId);
  181. }
  182. if (elementData->m_nameCrc == AZ_CRC("Trigger", 0x1a6b0f5d))
  183. {
  184. command->SetIsTrigger(colliderConfig->m_isTrigger);
  185. }
  186. if (elementData->m_nameCrc == AZ_CRC("Position", 0x462ce4f5))
  187. {
  188. command->SetPosition(colliderConfig->m_position);
  189. }
  190. if (elementData->m_nameCrc == AZ_CRC("Rotation", 0x297c98f1))
  191. {
  192. command->SetRotation(colliderConfig->m_rotation);
  193. }
  194. if (elementData->m_nameCrc == AZ_CRC_CE("MaterialSlots"))
  195. {
  196. command->SetMaterialSlots(colliderConfig->m_materialSlots);
  197. }
  198. if (elementData->m_nameCrc == AZ_CRC("ColliderTag", 0x5e2963ad))
  199. {
  200. command->SetTag(colliderConfig->m_tag);
  201. CommandSimulatedObjectHelpers::ReplaceTag(actor, colliderType, /*oldTag=*/command->GetOldTag().value(), /*newTag=*/colliderConfig->m_tag, m_commandGroup);
  202. }
  203. }
  204. // Box
  205. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::BoxShapeConfiguration>(), classData->m_azRtti, nullptr))
  206. {
  207. const Physics::BoxShapeConfiguration* boxShapeConfig = static_cast<Physics::BoxShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  208. if (elementData->m_nameCrc == AZ_CRC("Configuration", 0xa5e2a5d7))
  209. {
  210. command->SetDimensions(boxShapeConfig->m_dimensions);
  211. }
  212. }
  213. // Capsule
  214. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::CapsuleShapeConfiguration>(), classData->m_azRtti, nullptr))
  215. {
  216. const Physics::CapsuleShapeConfiguration* capsuleShapeConfig = static_cast<Physics::CapsuleShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  217. if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a))
  218. {
  219. command->SetRadius(capsuleShapeConfig->m_radius);
  220. }
  221. if (elementData->m_nameCrc == AZ_CRC("Height", 0xf54de50f))
  222. {
  223. command->SetHeight(capsuleShapeConfig->m_height);
  224. }
  225. }
  226. // Sphere
  227. else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid<Physics::SphereShapeConfiguration>(), classData->m_azRtti, nullptr))
  228. {
  229. const Physics::SphereShapeConfiguration* sphereShapeConfig = static_cast<Physics::SphereShapeConfiguration*>(parentDataNode->GetInstance(instanceIndex));
  230. if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a))
  231. {
  232. command->SetRadius(sphereShapeConfig->m_radius);
  233. }
  234. }
  235. }
  236. AZStd::string result;
  237. CommandSystem::GetCommandManager()->ExecuteCommandGroup(m_commandGroup, result);
  238. m_commandGroup.Clear();
  239. }
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  241. ColliderWidget::ColliderWidget(QIcon* icon, QWidget* parent, AZ::SerializeContext* serializeContext)
  242. : AzQtComponents::Card(parent)
  243. , m_propertyNotify(AZStd::make_unique<ColliderPropertyNotify>(this))
  244. , m_icon(icon)
  245. {
  246. m_editor = new EMotionFX::ObjectEditor(serializeContext, m_propertyNotify.get(), this);
  247. setContentWidget(m_editor);
  248. setExpanded(true);
  249. connect(this, &AzQtComponents::Card::contextMenuRequested, this, &ColliderWidget::OnCardContextMenu);
  250. }
  251. void ColliderWidget::Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPair& collider)
  252. {
  253. m_actor = actor;
  254. m_joint = joint;
  255. m_colliderIndex = colliderIndex;
  256. m_colliderType = colliderType;
  257. if (!collider.first || !collider.second)
  258. {
  259. m_editor->ClearInstances(true);
  260. m_collider = AzPhysics::ShapeColliderPair();
  261. return;
  262. }
  263. if (collider == m_collider)
  264. {
  265. m_editor->InvalidateAll();
  266. return;
  267. }
  268. const AZ::TypeId& shapeType = collider.second->RTTI_GetType();
  269. m_editor->ClearInstances(false);
  270. m_editor->AddInstance(collider.second.get(), shapeType);
  271. m_editor->AddInstance(collider.first.get(), collider.first->RTTI_GetType());
  272. m_collider = collider;
  273. AzQtComponents::CardHeader* cardHeader = header();
  274. cardHeader->setIcon(*m_icon);
  275. if (shapeType == azrtti_typeid<Physics::CapsuleShapeConfiguration>())
  276. {
  277. setTitle("Capsule");
  278. }
  279. else if (shapeType == azrtti_typeid<Physics::SphereShapeConfiguration>())
  280. {
  281. setTitle("Sphere");
  282. }
  283. else if (shapeType == azrtti_typeid<Physics::BoxShapeConfiguration>())
  284. {
  285. setTitle("Box");
  286. }
  287. else
  288. {
  289. setTitle("Unknown");
  290. }
  291. setProperty("colliderIndex", QVariant::fromValue(colliderIndex));
  292. setExpanded(true);
  293. }
  294. void ColliderWidget::Update()
  295. {
  296. if (!m_actor || !m_joint)
  297. {
  298. return;
  299. }
  300. setVisible(HasDisplayedNodes());
  301. }
  302. void ColliderWidget::SetFilterString(QString filterString)
  303. {
  304. m_editor->SetFilterString(filterString);
  305. Update();
  306. }
  307. bool ColliderWidget::HasDisplayedNodes() const
  308. {
  309. return m_editor->HasDisplayedNodes();
  310. }
  311. void ColliderWidget::OnCardContextMenu(const QPoint& position)
  312. {
  313. const AzQtComponents::Card* card = static_cast<AzQtComponents::Card*>(sender());
  314. const size_t colliderIndex = card->property("colliderIndex").value<size_t>();
  315. QMenu* contextMenu = new QMenu(this);
  316. contextMenu->setObjectName("EMFX.ColliderContainerWidget.ContextMenu");
  317. QAction* copyAction = contextMenu->addAction("Copy collider");
  318. copyAction->setObjectName("EMFX.ColliderContainerWidget.CopyColliderAction");
  319. copyAction->setProperty("colliderIndex", QVariant::fromValue(colliderIndex));
  320. connect(copyAction, &QAction::triggered, this, &ColliderWidget::OnCopyCollider);
  321. QAction* pasteAction = contextMenu->addAction("Paste collider");
  322. pasteAction->setObjectName("EMFX.ColliderContainerWidget.PasteColliderAction");
  323. pasteAction->setProperty("colliderIndex", QVariant::fromValue(colliderIndex));
  324. connect(pasteAction, &QAction::triggered, this, &ColliderWidget::OnPasteCollider);
  325. const QClipboard* clipboard = QGuiApplication::clipboard();
  326. const QMimeData* mimeData = clipboard->mimeData();
  327. const QByteArray clipboardContents = mimeData->data(ColliderHelpers::GetMimeTypeForColliderShape());
  328. pasteAction->setEnabled(!clipboardContents.isEmpty());
  329. QAction* deleteAction = contextMenu->addAction("Delete collider");
  330. deleteAction->setObjectName("EMFX.ColliderContainerWidget.DeleteColliderAction");
  331. deleteAction->setProperty("colliderIndex", QVariant::fromValue(colliderIndex));
  332. connect(deleteAction, &QAction::triggered, this, &ColliderWidget::OnRemoveCollider);
  333. QObject::connect(contextMenu, &QMenu::triggered, contextMenu, &QObject::deleteLater);
  334. if (!contextMenu->isEmpty())
  335. {
  336. contextMenu->popup(position);
  337. }
  338. }
  339. void ColliderWidget::OnCopyCollider()
  340. {
  341. QAction* action = static_cast<QAction*>(sender());
  342. const size_t colliderIndex = action->property("colliderIndex").value<size_t>();
  343. emit CopyCollider(colliderIndex);
  344. }
  345. void ColliderWidget::OnPasteCollider()
  346. {
  347. QAction* action = static_cast<QAction*>(sender());
  348. const int colliderIndex = action->property("colliderIndex").toInt();
  349. emit PasteCollider(colliderIndex);
  350. }
  351. void ColliderWidget::OnRemoveCollider()
  352. {
  353. QAction* action = static_cast<QAction*>(sender());
  354. const int colliderIndex = action->property("colliderIndex").toInt();
  355. emit RemoveCollider(colliderIndex);
  356. }
  357. void ColliderWidget::InvalidateEditorValues()
  358. {
  359. m_editor->InvalidateValues();
  360. }
  361. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  362. AddColliderButton::AddColliderButton(const QString& text, QWidget* parent, PhysicsSetup::ColliderConfigType copyToColliderType, const AZStd::vector<AZ::TypeId>& supportedColliderTypes)
  363. : QPushButton(text, parent)
  364. , m_supportedColliderTypes(supportedColliderTypes)
  365. , m_copyToColliderType(copyToColliderType)
  366. {
  367. setIcon(MysticQt::GetMysticQt()->FindIcon("Images/Icons/ArrowDownGray.png"));
  368. connect(this, &QPushButton::clicked, this, &AddColliderButton::OnCreateContextMenu);
  369. }
  370. void AddColliderButton::OnCreateContextMenu()
  371. {
  372. QMenu* contextMenu = new QMenu(this);
  373. contextMenu->setObjectName("EMFX.AddColliderButton.ContextMenu");
  374. AZStd::string actionName;
  375. for (const AZ::TypeId& typeId : m_supportedColliderTypes)
  376. {
  377. actionName = AZStd::string::format("Add %s", GetNameForColliderType(typeId).c_str());
  378. QAction* addBoxAction = contextMenu->addAction(actionName.c_str());
  379. addBoxAction->setProperty("typeId", typeId.ToString<AZStd::string>().c_str());
  380. connect(addBoxAction, &QAction::triggered, this, &AddColliderButton::OnAddColliderActionTriggered);
  381. }
  382. SkeletonModel* skeletonModel = nullptr;
  383. SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
  384. // Add the copy from option
  385. contextMenu->addSeparator();
  386. if (m_copyToColliderType != PhysicsSetup::ColliderConfigType::Unknown)
  387. {
  388. for (int i = 0; i < PhysicsSetup::ColliderConfigType::Unknown; ++i)
  389. {
  390. const PhysicsSetup::ColliderConfigType copyFromType = static_cast<PhysicsSetup::ColliderConfigType>(i);
  391. if (copyFromType == m_copyToColliderType)
  392. {
  393. continue;
  394. }
  395. const char* visualName = PhysicsSetup::GetVisualNameForColliderConfigType(copyFromType);
  396. QAction* copyColliderAction = contextMenu->addAction(QString("Copy from %1").arg(visualName));
  397. copyColliderAction->setProperty("copyFromType", i);
  398. const bool canCopyFrom = ColliderHelpers::CanCopyFrom(skeletonModel->GetSelectionModel().selectedIndexes(), copyFromType);
  399. if (canCopyFrom)
  400. {
  401. connect(copyColliderAction, &QAction::triggered, this, &AddColliderButton::OnCopyColliderActionTriggered);
  402. }
  403. else
  404. {
  405. copyColliderAction->setEnabled(false);
  406. }
  407. }
  408. }
  409. contextMenu->setFixedWidth(width());
  410. if (!contextMenu->isEmpty())
  411. {
  412. contextMenu->popup(mapToGlobal(QPoint(0, height())));
  413. }
  414. connect(contextMenu, &QMenu::triggered, contextMenu, &QMenu::deleteLater);
  415. }
  416. void AddColliderButton::OnAddColliderActionTriggered()
  417. {
  418. QAction* action = static_cast<QAction*>(sender());
  419. const QByteArray typeString = action->property("typeId").toString().toUtf8();
  420. const AZ::TypeId& typeId = AZ::TypeId::CreateString(typeString.data(), typeString.size());
  421. emit AddCollider(typeId);
  422. }
  423. void AddColliderButton::OnCopyColliderActionTriggered()
  424. {
  425. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  426. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  427. if (!selectedRowIndicesOutcome.IsSuccess())
  428. {
  429. return;
  430. }
  431. const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  432. if (selectedRowIndices.empty())
  433. {
  434. return;
  435. }
  436. QAction* action = static_cast<QAction*>(sender());
  437. const PhysicsSetup::ColliderConfigType copyFromType = static_cast<PhysicsSetup::ColliderConfigType>(action->property("copyFromType").toInt());
  438. ColliderHelpers::CopyColliders(selectedRowIndices, copyFromType, m_copyToColliderType);
  439. }
  440. AZStd::string AddColliderButton::GetNameForColliderType(AZ::TypeId colliderType) const
  441. {
  442. if (colliderType == azrtti_typeid<Physics::BoxShapeConfiguration>())
  443. {
  444. return "box";
  445. }
  446. else if (colliderType == azrtti_typeid<Physics::CapsuleShapeConfiguration>())
  447. {
  448. return "capsule";
  449. }
  450. else if (colliderType == azrtti_typeid<Physics::SphereShapeConfiguration>())
  451. {
  452. return "sphere";
  453. }
  454. return colliderType.ToString<AZStd::string>();
  455. }
  456. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  457. // Align the layout spacing with the entity inspector.
  458. int ColliderContainerWidget::s_layoutSpacing = 13;
  459. ColliderContainerWidget::ColliderContainerWidget(const QIcon& colliderIcon, QWidget* parent)
  460. : QWidget(parent)
  461. , m_colliderIcon(colliderIcon)
  462. {
  463. m_layout = new QVBoxLayout();
  464. m_layout->setMargin(0);
  465. m_layout->setSpacing(s_layoutSpacing);
  466. setLayout(m_layout);
  467. m_commandCallback = new ColliderEditedCallback(this, /*executePreUndo*/false);
  468. CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAdjustCollider::s_commandName, m_commandCallback);
  469. }
  470. ColliderContainerWidget::~ColliderContainerWidget()
  471. {
  472. CommandSystem::GetCommandManager()->RemoveCommandCallback(m_commandCallback, /*delFromMem=*/true);
  473. }
  474. void ColliderContainerWidget::Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPairList& colliders, AZ::SerializeContext* serializeContext)
  475. {
  476. m_actor = actor;
  477. m_joint = joint;
  478. m_colliderType = colliderType;
  479. const size_t numColliders = colliders.size();
  480. size_t numAvailableColliderWidgets = m_colliderWidgets.size();
  481. setVisible(numColliders > 0);
  482. // Create new collider widgets in case we don't have enough.
  483. if (numColliders > numAvailableColliderWidgets)
  484. {
  485. const int numWidgetsToCreate = static_cast<int>(numColliders) - static_cast<int>(numAvailableColliderWidgets);
  486. for (int i = 0; i < numWidgetsToCreate; ++i)
  487. {
  488. ColliderWidget* colliderWidget = new ColliderWidget(&m_colliderIcon, this, serializeContext);
  489. connect(colliderWidget, &ColliderWidget::RemoveCollider, this, &ColliderContainerWidget::RemoveCollider);
  490. connect(colliderWidget, &ColliderWidget::CopyCollider, this, &ColliderContainerWidget::CopyCollider);
  491. connect(colliderWidget, &ColliderWidget::PasteCollider, this, [this](size_t index) { PasteCollider(index, true); } );
  492. m_colliderWidgets.emplace_back(colliderWidget);
  493. m_layout->addWidget(colliderWidget, 0);
  494. }
  495. numAvailableColliderWidgets = m_colliderWidgets.size();
  496. }
  497. AZ_Assert(numAvailableColliderWidgets >= numColliders, "Not enough collider widgets available. Something went wrong with creating new ones.");
  498. for (size_t i = 0; i < numColliders; ++i)
  499. {
  500. ColliderWidget* colliderWidget = m_colliderWidgets[i];
  501. colliderWidget->Update(m_actor, m_joint, i, m_colliderType, colliders[i]);
  502. colliderWidget->show();
  503. }
  504. // Hide the collider widgets that are too much for the current node.
  505. for (size_t i = numColliders; i < numAvailableColliderWidgets; ++i)
  506. {
  507. m_colliderWidgets[i]->hide();
  508. m_colliderWidgets[i]->Update(nullptr, nullptr, InvalidIndex, PhysicsSetup::ColliderConfigType::Unknown, AzPhysics::ShapeColliderPair());
  509. }
  510. }
  511. void ColliderContainerWidget::Update()
  512. {
  513. for (ColliderWidget* colliderWidget : m_colliderWidgets)
  514. {
  515. colliderWidget->InvalidateEditorValues();
  516. colliderWidget->Update();
  517. }
  518. }
  519. void ColliderContainerWidget::Reset()
  520. {
  521. Update(nullptr, nullptr, PhysicsSetup::ColliderConfigType::Unknown, AzPhysics::ShapeColliderPairList(), nullptr);
  522. }
  523. void ColliderContainerWidget::SetFilterString(QString filterString)
  524. {
  525. for (auto* widget : m_colliderWidgets)
  526. {
  527. widget->SetFilterString(filterString);
  528. }
  529. }
  530. bool ColliderContainerWidget::HasVisibleColliders() const
  531. {
  532. return AZStd::any_of(
  533. m_colliderWidgets.begin(),
  534. m_colliderWidgets.end(),
  535. [](auto w)
  536. {
  537. return !w->isHidden();
  538. });
  539. }
  540. void ColliderContainerWidget::contextMenuEvent(QContextMenuEvent* event)
  541. {
  542. const QMimeData* mimeData = QGuiApplication::clipboard()->mimeData();
  543. const QByteArray clipboardContents = mimeData->data(ColliderHelpers::GetMimeTypeForColliderShape());
  544. int ypos = event->globalY();
  545. int index = 0;
  546. int curpos = mapToGlobal({0,0}).y();
  547. for (const ColliderWidget* card : m_colliderWidgets)
  548. {
  549. if (!card->GetActor())
  550. {
  551. break;
  552. }
  553. curpos = card->mapToGlobal({0,0}).y();
  554. if (curpos > ypos)
  555. {
  556. break;
  557. }
  558. ++index;
  559. }
  560. auto menu = new QMenu(this);
  561. connect(menu, &QMenu::triggered, &QObject::deleteLater);
  562. auto pasteNewColliderAction = new QAction("Paste collider", menu);
  563. pasteNewColliderAction->setEnabled(!clipboardContents.isEmpty());
  564. connect(pasteNewColliderAction, &QAction::triggered, this, [this, index]() { PasteCollider(index, false); } );
  565. menu->addAction(pasteNewColliderAction);
  566. menu->popup(event->globalPos());
  567. event->accept();
  568. }
  569. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  570. ColliderContainerWidget::ColliderEditedCallback::ColliderEditedCallback(ColliderContainerWidget* parent, bool executePreUndo, bool executePreCommand)
  571. : MCore::Command::Callback(executePreUndo, executePreCommand)
  572. , m_widget(parent)
  573. {
  574. }
  575. bool ColliderContainerWidget::ColliderEditedCallback::Execute(MCore::Command* command, const MCore::CommandLine& commandLine)
  576. {
  577. AZ_UNUSED(command);
  578. AZ_UNUSED(commandLine);
  579. m_widget->Update();
  580. return true;
  581. }
  582. bool ColliderContainerWidget::ColliderEditedCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine)
  583. {
  584. AZ_UNUSED(command);
  585. AZ_UNUSED(commandLine);
  586. m_widget->Update();
  587. return true;
  588. }
  589. } // namespace EMotionFX