JointPropertyWidget.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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 <AzQtComponents/Components/Widgets/Card.h>
  9. #include <AzQtComponents/Components/Widgets/CardHeader.h>
  10. #include <AzQtComponents/Components/Widgets/CheckBox.h>
  11. #include <AzQtComponents/Components/Widgets/LineEdit.h>
  12. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  13. #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
  14. #include <EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/ActorInfo.h>
  15. #include <EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeGroupInfo.h>
  16. #include <EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeInfo.h>
  17. #include <Editor/ColliderHelpers.h>
  18. #include <Editor/InspectorBus.h>
  19. #include <Editor/Plugins/ColliderWidgets/JointPropertyWidget.h>
  20. #include <Editor/Plugins/ColliderWidgets/SimulatedObjectColliderWidget.h>
  21. #include <Editor/SkeletonModel.h>
  22. #include <MCore/Source/ReflectionSerializer.h>
  23. #include <QBoxLayout>
  24. #include <QClipboard>
  25. #include <QGuiApplication>
  26. #include <QHeaderView>
  27. #include <QItemSelectionModel>
  28. #include <QLabel>
  29. #include <QLineEdit>
  30. #include <QMimeData>
  31. #include <UI/PropertyEditor/ReflectedPropertyEditor.hxx>
  32. namespace EMotionFX
  33. {
  34. JointPropertyWidget::JointPropertyWidget(QWidget* parent)
  35. : QWidget(parent)
  36. {
  37. auto* mainLayout = new QVBoxLayout;
  38. mainLayout->setMargin(0);
  39. mainLayout->setContentsMargins(0, 5, 0, 0);
  40. mainLayout->setSpacing(0);
  41. auto* propertyCard = new AzQtComponents::Card;
  42. AzQtComponents::Card::applySectionStyle(propertyCard);
  43. propertyCard->setTitle("Node Properties");
  44. mainLayout->addWidget(propertyCard);
  45. // add the node attributes widget
  46. m_propertyWidget = aznew AzToolsFramework::ReflectedPropertyEditor(this);
  47. m_propertyWidget->setObjectName("EMFX.Joint.ReflectedPropertyEditor.PropertyWidget");
  48. propertyCard->setContentWidget(m_propertyWidget);
  49. setLayout(mainLayout);
  50. // Connect to the model.
  51. SkeletonModel* skeletonModel = nullptr;
  52. SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
  53. if (skeletonModel)
  54. {
  55. connect(skeletonModel, &SkeletonModel::dataChanged, this, &JointPropertyWidget::Reset);
  56. connect(skeletonModel, &SkeletonModel::modelReset, this, &JointPropertyWidget::Reset);
  57. connect(&skeletonModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, &JointPropertyWidget::Reset);
  58. }
  59. // create Add Component button
  60. m_addCollidersButton = new AddCollidersButton(propertyCard);
  61. m_addCollidersButton->setObjectName("EMotionFX.SkeletonOutlinerPlugin.JointPropertyWidget.addCollidersButton");
  62. connect(m_addCollidersButton, &AddCollidersButton::AddCollider, this, &JointPropertyWidget::OnAddCollider);
  63. connect(m_addCollidersButton, &AddCollidersButton::AddToRagdoll, this, &JointPropertyWidget::OnAddToRagdoll);
  64. auto* marginLayout = new QVBoxLayout;
  65. marginLayout->setContentsMargins(10, 10, 10, 10);
  66. marginLayout->addWidget(m_addCollidersButton);
  67. mainLayout->addLayout(marginLayout);
  68. m_filterEntityBox = new QLineEdit{ this };
  69. m_filterEntityBox->setPlaceholderText(tr("Search..."));
  70. AzQtComponents::LineEdit::applySearchStyle(m_filterEntityBox);
  71. auto* marginFilterEntityBoxLayout = new QHBoxLayout;
  72. marginFilterEntityBoxLayout->setContentsMargins(10, 10, 10, 10);
  73. marginFilterEntityBoxLayout->addWidget(m_filterEntityBox);
  74. mainLayout->addLayout(marginFilterEntityBoxLayout);
  75. connect(m_filterEntityBox, &QLineEdit::textChanged, this, &JointPropertyWidget::OnSearchTextChanged);
  76. m_clothJointWidget = new ClothJointWidget;
  77. m_hitDetectionJointWidget = new HitDetectionJointWidget;
  78. m_ragdollJointWidget = new RagdollNodeWidget;
  79. m_simulatedJointWidget = new SimulatedObjectColliderWidget;
  80. m_clothJointWidget->CreateGUI();
  81. m_hitDetectionJointWidget->CreateGUI();
  82. m_ragdollJointWidget->CreateGUI();
  83. m_simulatedJointWidget->CreateGUI();
  84. mainLayout->addWidget(m_clothJointWidget);
  85. mainLayout->addWidget(m_hitDetectionJointWidget);
  86. mainLayout->addWidget(m_ragdollJointWidget);
  87. mainLayout->addWidget(m_simulatedJointWidget);
  88. }
  89. JointPropertyWidget::~JointPropertyWidget()
  90. {
  91. EMStudio::InspectorRequestBus::Broadcast(&EMStudio::InspectorRequestBus::Events::ClearIfShown, this);
  92. delete m_propertyWidget;
  93. }
  94. void JointPropertyWidget::Reset()
  95. {
  96. hide();
  97. m_propertyWidget->ClearInstances();
  98. m_propertyWidget->InvalidateAll();
  99. SkeletonModel* skeletonModel = nullptr;
  100. SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
  101. if (!skeletonModel)
  102. {
  103. return;
  104. }
  105. auto* actorInstance = skeletonModel->GetActorInstance();
  106. if (!actorInstance)
  107. {
  108. return;
  109. }
  110. Node* node = nullptr;
  111. SkeletonOutlinerRequestBus::BroadcastResult(node, &SkeletonOutlinerRequests::GetSingleSelectedNode);
  112. if (node && node->GetNodeIndex() != InvalidIndex)
  113. {
  114. m_nodeInfo.reset(aznew EMStudio::NodeInfo(actorInstance, node));
  115. m_propertyWidget->AddInstance(m_nodeInfo.get(), azrtti_typeid(m_nodeInfo.get()));
  116. }
  117. else if (actorInstance && actorInstance->GetActor())
  118. {
  119. m_actorInfo.reset(aznew EMStudio::ActorInfo(actorInstance));
  120. m_propertyWidget->AddInstance(m_actorInfo.get(), azrtti_typeid(m_actorInfo.get()));
  121. }
  122. else
  123. {
  124. return;
  125. }
  126. AZ::SerializeContext* serializeContext = nullptr;
  127. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  128. if (!serializeContext)
  129. {
  130. AZ_Error("EMotionFX", false, "Can't get serialize context from component application.");
  131. return;
  132. }
  133. show();
  134. m_propertyWidget->Setup(serializeContext, nullptr, false);
  135. m_propertyWidget->ExpandAll();
  136. m_propertyWidget->InvalidateAll();
  137. }
  138. void JointPropertyWidget::OnAddCollider(PhysicsSetup::ColliderConfigType configType, AZ::TypeId colliderType)
  139. {
  140. AZ::Outcome<QModelIndexList> indicesOutcome;
  141. SkeletonOutlinerRequestBus::BroadcastResult(indicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  142. if (indicesOutcome.IsSuccess())
  143. {
  144. ColliderHelpers::AddCollider(indicesOutcome.GetValue(), configType, colliderType);
  145. }
  146. }
  147. void JointPropertyWidget::OnAddToRagdoll()
  148. {
  149. AZ::Outcome<QModelIndexList> indicesOutcome;
  150. SkeletonOutlinerRequestBus::BroadcastResult(indicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  151. if (!indicesOutcome.IsSuccess())
  152. {
  153. return;
  154. }
  155. auto indices = indicesOutcome.GetValue();
  156. const AZStd::string groupName = AZStd::string::format("Add joint%s to ragdoll", indices.size() > 1 ? "s" : "");
  157. MCore::CommandGroup commandGroup(groupName);
  158. AZStd::vector<AZStd::string> jointNames;
  159. jointNames.reserve(indices.size());
  160. // All the actor pointers should be the same
  161. const uint32 actorId = indices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>()->GetID();
  162. for (const QModelIndex& selectedIndex : indices)
  163. {
  164. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  165. jointNames.emplace_back(joint->GetNameString());
  166. }
  167. CommandRagdollHelpers::AddJointsToRagdoll(actorId, jointNames, &commandGroup);
  168. AZStd::string result;
  169. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  170. {
  171. AZ_Error("EMotionFX", false, result.c_str());
  172. }
  173. }
  174. void JointPropertyWidget::OnSearchTextChanged()
  175. {
  176. m_filterString = m_filterEntityBox->text();
  177. m_clothJointWidget->SetFilterString(m_filterString);
  178. m_hitDetectionJointWidget->SetFilterString(m_filterString);
  179. m_ragdollJointWidget->SetFilterString(m_filterString);
  180. }
  181. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  182. //
  183. // AddCollidersButton
  184. //
  185. AddCollidersButton::AddCollidersButton(QWidget* parent)
  186. : QPushButton(parent)
  187. {
  188. setText("Add Property \342\226\276");
  189. connect(this, &QPushButton::clicked, this, &AddCollidersButton::OnCreateContextMenu);
  190. }
  191. // TODO subclass ItemModel
  192. enum ItemRoles
  193. {
  194. Shape = Qt::UserRole + 1,
  195. ConfigType = Qt::UserRole + 2,
  196. CopyFromType = Qt::UserRole + 3,
  197. PasteCopiedCollider = Qt::UserRole + 4,
  198. CopyToType = Qt::UserRole + 5
  199. };
  200. struct AddCollidersPallete : public QTreeView
  201. {
  202. public:
  203. AddCollidersPallete(QWidget* parent)
  204. : QTreeView(parent)
  205. {
  206. }
  207. QSize GetViewportSizeHint()
  208. {
  209. return QTreeView::viewportSizeHint();
  210. }
  211. };
  212. void AddCollidersButton::OnCreateContextMenu()
  213. {
  214. SkeletonModel* skeletonModel = nullptr;
  215. SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
  216. if (skeletonModel == nullptr)
  217. {
  218. return;
  219. }
  220. auto selectedIndices = skeletonModel->GetSelectionModel().selectedIndexes();
  221. if (selectedIndices.empty())
  222. {
  223. AZ_Assert(false, "The Add Collider Button in JointPropertyWidget is being clicked on while there is empty selection. This button should be hidden.");
  224. return;
  225. }
  226. delete model;
  227. model = new QStandardItemModel;
  228. QFrame* newFrame = new QFrame{ this };
  229. newFrame->setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
  230. newFrame->setFixedWidth(this->width());
  231. newFrame->move({ this->mapToGlobal({ 0, 0 + height() }) });
  232. auto* treeView = new AddCollidersPallete(newFrame);
  233. treeView->setModel(model);
  234. treeView->setObjectName("EMotionFX.SkeletonOutlinerPlugin.AddCollidersButton.TreeView");
  235. // Hide header for dropdown-style, single-column, tree
  236. treeView->header()->hide();
  237. connect(treeView, &QTreeView::clicked, this, &AddCollidersButton::OnAddColliderActionTriggered);
  238. treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
  239. newFrame->setLayout(new QVBoxLayout);
  240. newFrame->layout()->addWidget(treeView);
  241. AZStd::string actionName;
  242. struct ColliderTypeInfo
  243. {
  244. PhysicsSetup::ColliderConfigType type;
  245. std::string name;
  246. QIcon icon;
  247. };
  248. auto sections = QList<ColliderTypeInfo>{
  249. { PhysicsSetup::ColliderConfigType::Cloth, "Cloth", QIcon(SkeletonModel::s_clothColliderIconPath) },
  250. { PhysicsSetup::ColliderConfigType::HitDetection, "Hit Detection", QIcon(SkeletonModel::s_hitDetectionColliderIconPath) }
  251. };
  252. bool nodeInRagdoll = ColliderHelpers::NodeHasRagdoll(selectedIndices.last());
  253. if (nodeInRagdoll)
  254. {
  255. sections.append({ PhysicsSetup::ColliderConfigType::Ragdoll, "Ragdoll", QIcon(SkeletonModel::s_ragdollColliderIconPath) });
  256. }
  257. for (const auto& section : sections)
  258. {
  259. auto& configType = section.type;
  260. auto* sectionItem = new QStandardItem{QString("%1 Collider").arg(section.name.data()) };
  261. for (auto& shape : m_supportedColliderTypes)
  262. {
  263. if (configType == PhysicsSetup::ColliderConfigType::Cloth)
  264. {
  265. const bool clothSupportedShapes = shape == azrtti_typeid<Physics::SphereShapeConfiguration>() ||
  266. shape == azrtti_typeid<Physics::CapsuleShapeConfiguration>();
  267. if (!clothSupportedShapes)
  268. {
  269. continue;
  270. }
  271. }
  272. auto capitalColliderTypeName = QString{ GetNameForColliderType(shape).c_str() };
  273. capitalColliderTypeName[0] = capitalColliderTypeName[0].toUpper();
  274. auto* item = new QStandardItem{ section.icon, capitalColliderTypeName };
  275. item->setData(shape.ToString<AZStd::string>().c_str(), ItemRoles::Shape);
  276. item->setData(static_cast<int>(configType), ItemRoles::ConfigType);
  277. sectionItem->appendRow(item);
  278. }
  279. model->appendRow(sectionItem);
  280. }
  281. if (!nodeInRagdoll)
  282. {
  283. auto ragdollItem = new QStandardItem{ QIcon{ SkeletonModel::s_ragdollColliderIconPath }, "Add to Ragdoll" };
  284. model->appendRow(ragdollItem);
  285. ragdollItem->setData(PhysicsSetup::ColliderConfigType::Ragdoll, ItemRoles::ConfigType);
  286. }
  287. // Copy from other collider type
  288. for (const auto& section : sections)
  289. {
  290. const auto fromType = section.type;
  291. const bool canCopyFrom = ColliderHelpers::CanCopyFrom(selectedIndices, fromType);
  292. if (!canCopyFrom)
  293. {
  294. continue;
  295. }
  296. for (const auto& innerSection : sections)
  297. {
  298. if (innerSection.type == fromType)
  299. {
  300. continue;
  301. }
  302. const auto toType = innerSection.type;
  303. const char* visualName = PhysicsSetup::GetVisualNameForColliderConfigType(fromType);
  304. const char* visualNameTo = PhysicsSetup::GetVisualNameForColliderConfigType(toType);
  305. actionName = AZStd::string::format("Copy from %s to %s", visualName, visualNameTo);
  306. auto* item = new QStandardItem{ actionName.c_str() };
  307. item->setData(static_cast<int>(toType), ItemRoles::ConfigType);
  308. item->setData(static_cast<int>(fromType), ItemRoles::CopyFromType);
  309. model->appendRow(item);
  310. }
  311. }
  312. // Paste a copied collider
  313. const QMimeData* mimeData = QGuiApplication::clipboard()->mimeData();
  314. const QByteArray clipboardContents = mimeData->data(ColliderHelpers::GetMimeTypeForColliderShape());
  315. if (!clipboardContents.isEmpty())
  316. {
  317. AzPhysics::ShapeColliderPair colliderPair;
  318. MCore::ReflectionSerializer::Deserialize(&colliderPair, mimeData->data(ColliderHelpers::GetMimeTypeForColliderShape()).data());
  319. for (const auto& section : sections)
  320. {
  321. auto pasteNewColliderItem = new QStandardItem(QString{"Paste as %1 Collider"}.arg(section.name.c_str()));
  322. pasteNewColliderItem->setData(true, ItemRoles::PasteCopiedCollider);
  323. pasteNewColliderItem->setData(static_cast<int>(section.type), ItemRoles::CopyToType);
  324. model->appendRow(pasteNewColliderItem);
  325. }
  326. }
  327. newFrame->setFixedWidth(width());
  328. newFrame->show();
  329. connect(treeView, &QTreeView::clicked, newFrame, &QFrame::deleteLater);
  330. treeView->expandAll();
  331. treeView->setFixedHeight(treeView->GetViewportSizeHint().height());
  332. }
  333. void AddCollidersButton::OnAddColliderActionTriggered(const QModelIndex& index)
  334. {
  335. if (!index.isValid())
  336. {
  337. return;
  338. }
  339. AZ::Outcome<QModelIndexList> selectedRowIndicesOutcome;
  340. SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
  341. if (!selectedRowIndicesOutcome.IsSuccess())
  342. {
  343. return;
  344. }
  345. const QModelIndexList selectedRowIndices = selectedRowIndicesOutcome.GetValue();
  346. if (selectedRowIndices.empty())
  347. {
  348. return;
  349. }
  350. if (index.data(ItemRoles::PasteCopiedCollider).value<bool>())
  351. {
  352. auto copyToType = static_cast<PhysicsSetup::ColliderConfigType>(index.data(ItemRoles::CopyToType).toInt());
  353. ColliderHelpers::PasteColliderFromClipboard(selectedRowIndices.last(), 0, copyToType, false);
  354. return;
  355. }
  356. if (index.data(ItemRoles::ConfigType).isNull())
  357. {
  358. return;
  359. }
  360. auto colliderType = static_cast<PhysicsSetup::ColliderConfigType>(index.data(ItemRoles::ConfigType).toInt());
  361. if (!index.data(ItemRoles::CopyFromType).isNull())
  362. {
  363. auto copyFromType = static_cast<PhysicsSetup::ColliderConfigType>(index.data(ItemRoles::CopyFromType).toInt());
  364. ColliderHelpers::CopyColliders(selectedRowIndices, copyFromType, colliderType);
  365. return;
  366. }
  367. auto shape = AZ::TypeId{ index.data(ItemRoles::Shape).toString().toStdString().c_str() };
  368. if (colliderType == PhysicsSetup::ColliderConfigType::Ragdoll && index.data(ItemRoles::Shape).isNull())
  369. {
  370. emit AddToRagdoll();
  371. return;
  372. }
  373. emit AddCollider(colliderType, shape);
  374. }
  375. AZStd::string AddCollidersButton::GetNameForColliderType(AZ::TypeId colliderType) const
  376. {
  377. if (colliderType == azrtti_typeid<Physics::BoxShapeConfiguration>())
  378. {
  379. return "box";
  380. }
  381. else if (colliderType == azrtti_typeid<Physics::CapsuleShapeConfiguration>())
  382. {
  383. return "capsule";
  384. }
  385. else if (colliderType == azrtti_typeid<Physics::SphereShapeConfiguration>())
  386. {
  387. return "sphere";
  388. }
  389. return colliderType.ToString<AZStd::string>();
  390. }
  391. } // namespace EMotionFX